Комментарии 63
Вообще спасибо за статью, и отдельное спасибо за нормальный перевод.
Ну скорее использовали существующие наработки))
- адрес загрузки ntdll.dll в разных процессах Windows в рамках одной загрузки одинаковый
- плюс запрет прямого вызова (в обход системных библиотек) системных вызовов
2. О двух концах палка. Та же работа с сетью теперь станет медленнее: сначала прыгаем в vdso, а уже оттуда в ядро, вместо прямого прыжка в ядро. Или они откроют vdso прямую работу с железом в юзерленде?
Та же работа с сетью теперь станет медленнее: сначала прыгаем в vdso, а уже оттуда в ядро, вместо прямого прыжка в ядро.
Накладные расходы, безусловно, будут присутствовать. Насколько они серьезно будут отражаться на реальной работе (учитывая, что ОС не позиционируется, как серверная) — нужно замерять.
Или они откроют vdso прямую работу с железом в юзерленде?
Сомнительно
Вопросов два — из-за последних архитектурных уязвимостей (мелтдаун и иже с ними) все ОС не используют/чистят кэши TLB при переключении контекста в ядро. Из-за этого вызов разделяемых всеми процессами «ядерных» библиотек оказывается медленнее, чем вызов «обычных». А учитывая, что уязвимости не ясно как исправлять и в ближайшее обозримое время ничего не изменится в этом плане, то есть мнение, что в реальности смена контекста старыми добрыми специализированными командами с единой точкой входа может оказаться быстрее.
И, конечно же, если будет найдена уязвимость в vdso, то это разом рушит вообще всю безопасность. А если городить какие-то дополнительные проверки и трамплины, то это сводит на нет все преимущества vdso.
Иначе зачем вот это вот всё? Доверенный компилятор байт-кода дарта и так ничего не пропустит.
Конечно. А можно и свой user mode полностью написать для Zircon.
Но и JIT'инг байт-кода в машинный до сих пор эксплуатируется. И реализация виртуальной машины тоже никогда не станет идеальной (в рамках отсутствия уязвимостей).
Если известен адрес vDSO (или смещений внутри), то проше вызывать публичное ABI. Ставка на рандомизацию адреса загрузки в каждом процессе.
Тогда мне не понятны выкладки про «ограниченный vDSO». Чем отсутствие кода в самом vDSO поможет от вызова «запрещенных» системных вызовов?
Бред какой-то.
Современные ОС c ASLR не запрещают совершать системный вызов из произвольного кода: будь то ntdll, ROP-цепочка из произвольного кода или внедренный шелкод.
Вот мне и непонятно, как
Раздел "Поддержка со стороны ядра", пунтк 2: "Проверка адресов возврата для функций системных вызовов."
Оригинал: https://fuchsia.googlesource.com/zircon/+/master/docs/vdso.md#Enforcement
и зачем можно запретить сисколы откуда-то кроме vDSO?
Подробнее тут: https://habr.com/post/435482/#comment_19598458
Это отличный инструмент уменьшения поверхности атаки.
However, potential kernel bugs can be mitigated somewhat by enforcing that each kernel entry be made only from the proper vDSO code— не корректно. В наших процессорах (x86/ARM) нет способа проверить какой код до этого исполнялся, он историю переходов не хранит. В нем есть только текущее состояние. Поэтому тот факт, что PC указывает на какое-то смещение внутри vDSO совершенно не означает, что перед этим отработал код самого vDSO, а не зловред, который подготовил в регистрах/памяти значения для атаки на ядро.
Судя по всему, проверяют они тупо PC (RIP) на иструкции syscall.
On entry to the kernel for a system call, the kernel examines the PC location of the syscall instruction on x86 (or equivalent instruction on other machines). It subtracts the base address of the vDSO code recorded for the process at vmar_map() time from the PC, and passes the resulting offset to the validity predicate for the system call being invoked.
Никто не обещал, что до vDSO каким-то мифическим образом можно будет проверить код.
Судя по всему, проверяют они тупо PC (RIP) на иструкции syscall.
Вероятно это мой промах и авторов оригинальной статьи, что мы не смогли донести эту простую мысль сразу
https://habr.com/post/435482/#comment_19598754
Что мешает этому коду получить адрес vDSO, а это легкодоступная информация, и даже прочитать vDSO и получить адреса и коды системных функций?Мешает незнание адреса vDSO, так как это не легкодоступная информация. Не должно быть фиксированного адреса или регистра, по которому можно было бы узнать адрес vDSO.
И все равно какой код до этого выполнялся узнать нельзя, только откуда был последний call. Сделайте jmp (b) :)
А то я знаю, что такое Program Status Register, а вот что за MSR/PSR стэки и что за сброс PC — не слышал даже
Но зачем для этого vDSO?
Стандартные сисколы требуют только знания индекса системной функции.
Вызов функции из vDSO требует знания ее адреса, который специально рандомизируется в адресном пространстве.
Тут постарался раскрыть мысль: https://habr.com/post/435482/#comment_19598458
Более того «сискол» это абстракция уровня компилятора, грубо говоря.
Сама же реализация сискола может быть разной. Можно собрать Linux в котором сискол будет операцией передачи управления на случайную шлюзовую страницу (и, если не ошибаюсь, то для ARMx64 оно именно так и работает).
Это лишь вопрос организации переключения контекста.
И концепция vDSO один из вариантов реализации.
Более того «сискол» это абстракция уровня компилятора, грубо говоря.
Мы говорим об одной и той же инструкции процессора syscall/sysenter? Про https://www.codemachine.com/article_syscall.html?
Можно собрать Linux в котором сискол будет операцией передачи управления на случайную шлюзовую страницу
vDSO это не какой-то rocket science, который не повторить в другой ОС, это просто PoC.
Но заложенный принцип в начале разработке ОС не потребует в будущем ломать бинарную совместимость с существующим кодом.
Всегда мучил вопрос, почему не используют этой фичи.
Есть ощущение, что это можно сделать тупо скомпилировав vdso как «position independent».
Вероятнее всего так и сделано, раз vDSO не требует обработку reloc'ов
Всегда мучил вопрос, почему не используют этой фичи.
Внедрение новых механизмов безопасности всегда идет со скрипом (https://habr.com/company/pt/blog/424633/ например), получается сделать это в новой ОС иногда проще.
Допустим, удаленно пробили (RCE) браузер с "широким vDSO". Если не было второй уязвимости раскрытия памяти, то мы не знаем где в памяти расположены системные библиотеки (ни файл прочесть, ни через сокет отравить). Одним из существующих приемов — определить (или угадать) версию ОС заранее, а затем включить в shell-код системные вызовы напрямую (как кусок кода из ntdll.dll той же версии, а на Linux, вроде, номера системных вызовов достаточно стабильны). Здесь же придется сканировать память на предмет поиска ELF заголовка. Насколько это сложно, учитывая то, что нужно корректно обработать отсутствующие страницы виртуальной памяти (организовать SEH) — не знаю, надо изучать внутренности обработки исключений по памяти для Fuchsia.
Либо городить некий промежуточный уровень привилегий, но это не модно, медленно и сомнительно и снова не имеет отношения к vDSO.
Но ведь остаётся атака через чтение и анализ кода самой vDSO.
Как прочитать то, адрес чего мы не знаем?
Легитимный код нити знает, ему аргументом передан адрес vDSO (который расположен с памяти процесса по произвольному случайному адресу).
Произвольных внедренный shell-код не знает об адресном процесса ничего, если, например, в начале не эксплуатировалась еще одну уязвимость утечки адресов в этом же процессе.
У нас пользовательский код знает расположение vDSO? Мы защищаемся от внедрённого кода, который по какой-то причине не атакует пользовательский процесс, ничего о нём не знает, но пытается при этом атаковать ядро, исполняясь из этого самого пользовательского процесса? Нет, такой конечно может случиться, но это крайне редкие ситуации и все современные ОС успешно с этим борются.
Если у вас чужой код уже выполняется в пользовательском процессе, то весь процесс нужно считать скомпрометированным, а не предполагать, что атакующий по какой-то причине будет атаковать только код ядра.
В общем, вижу я противоречие в Ваших словах.
Мы защищаемся от внедрённого кода, который по какой-то причине не атакует пользовательский процесс, ничего о нём не знает, но пытается при этом атаковать ядро, исполняясь из этого самого пользовательского процесса?
Да. Злоумышленник шлет вредоносный pdf, который обрабатывается уязвимым ридером:
- выделяются блоки памяти в куче, куда копируется содержимое файла вместе с вредоносным машинным кодом (shell-кодом)
- используя уязвимость, злоумышленнику удается передать управление на shell-код
Shell-код ничего не знает о процессе, в который он загружен, кроме того, что он сидит где-то в heap'е этого процесса. Адреса хипов тоже рандомизируются. До ASLR он просто вызывал функции системных библиотек по захардкоженным (известным заранее) адресам. Потом ввели ASLR, и атакующий (для исполнения полезных действий) стал в сам shell-код встраивать инструкции syscall'а (которым нужен только индекс функции, а не адрес в 64-х битном адресном пространстве).
NtReadFile:
mov eax,6
syscall
Нет, такой конечно может случиться, но это крайне редкие ситуации и все современные ОС успешно с этим борются.
С переменным успехом, стоимость эксплуатации уязвимостей растет. В Zircon следующий шаг: давайте не будем позволять произвольному коду вызывать syscall'ы.
Никто и не позиционирует этот механизм, как серебряная пуля против эксплоитов:
Это отличный инструмент уменьшения поверхности атаки.
Что мешает этому коду получить адрес vDSO, а это легкодоступная информация, и даже прочитать vDSO и получить адреса и коды системных функций и сделать прямой вызов в ядров, сымитировав вызов из DSO, подделав адрес возврата в стеке (благо, что юзерспейс контролирует свой стек)?
У вас работающий вредоносный код в юзерспейс.
Да
Что мешает этому коду получить адрес vDSO, а это легкодоступная информация, и даже прочитать vDSO и получить адреса и коды системных функций?
Мешает незнание адреса vDSO, так как это не легкодоступная информация. Не должно быть фиксированного адреса или регистра, по которому можно было бы узнать адрес vDSO.
ASLR — это способ борьбы с передачей управления на шелл-код/ROP. Если управление уже у него, ASLR, как и vDSO с рандомным адресом ничем не поможет.
Разницы между переключением контекста через syscall или pagefault на выделенной страничке я не вижу.
На стеке лежит гора разных указателей, через которые можно выползти на vDSO.
А можно обрушить текущий процесс, если это не указатель, а просто число, похожее на адрес.
Разницы между переключением контекста через syscall или pagefault на выделенной страничке я не вижу.
Да, можно и на 0 делить, а в регистре индекс передавать.
Статья не про какой-то экзотический способ передачи управления ядру ОС.
Правильно, можно и делить. Просто до того как я выяснил, что syscall у них все равно есть, обсуждалось переключение контекстов через разные места.
Шелл-код написан под конкретный процесс, а не под абстрактный. Он знает всю структуру стека, так как знает какую функцию эксплуатирует и где в программе она вызывается.
Не всегда: часто уязвимости подвержено огромное множество версий продукта. Привязать уязвимость к конкретной структуре стека (для конкретной версии ПО) сильно ухудшает, как мне кажется, результативность. Особенно для попыток эксплуатации в слепую, например через массовые рассылки электронной почтой.
Мы с вами говорим про малоизвестную, нераспространенную и специализированную ОС. В моем понимании, все ожидаемые атаки будут целевыми.
Ну так можно сказать и что ОС атакующему неизвестна :)
Узнать версию ОС (точнее не версию, а семейство, в рамках которого индексы syscall'ов стабильны) перед атакой проще, чем точную версию Adobe Acrobat'а. Например иногда сливается инфа, что была массовая закупка лицензий Windows 10 для всего предприятия (или даже так https://habr.com/post/408177/, или так https://habr.com/post/63081/).
Мы с вами говорим про малоизвестную, нераспространенную и специализированную ОС. В моем понимании, все ожидаемые атаки будут целевыми.
Если профессиональная целенаправленная атака — согласен.
Но скоро же Google заменит на всех телефонах Android на Fuchsia :) Вот тогда-то и пригодятся все средства борьбы с нетаргетированными угрозами.
используя уязвимость, злоумышленнику удается передать управление на shell-код
Не удаётся. Куча неисполняемая. Раньше можно было сделать её исполняемой, сформировав ROP/JOP для вызова NtVirtualProtect (и сотоварищи, типа MapViewOfFile). Но современные веянья идут к запрету таких способов. Например exec в SELinux. А полноценный эксплоит на ROP задача, мягко говоря, нетривиальная, и мне неизвестны полные реализации.
придется сканировать память на предмет поиска ELF заголовка. Насколько это сложно, учитывая то, что нужно корректно обработать отсутствующие страницы виртуальной памяти (организовать SEH)
в *nix не так уж сложно: один сисколл создаёт пару пайпов. Затем сканируем память записью в пайп (ещё один сисколл). Если запись вернула ошибку — памяти по этому адресу нет. Если запись прошла без ошибок — содержимое у нас в пайпе, даже если через миллисекунду оно ВДРУГ стало уже недоступным. Если содержимое нас не волнует — всё равно следует время от времени уничтожать содержимое пайпа операцией чтения (ещё один сисколл), чтобы процесс не упёрся в лимиты.
Например: можно запретить драйверам порождать дочерние процессы или обрабатывать проецирование областей MMIO.
Т.е. ядро с ядром у них тоже через vDSO общается? Это как? Или они драйвера в user mode утащили? А что с прерываниями делают?
update: нашел вашу ссылку, поглядел. Да, правда в user mode. Видимо правда все будет медленно но верно :)
ОС не поддерживает горячее изменение количества процессоров:
Это, как минимум, косвенно указывает на то, что ОС не позиционируется, как серверная.
На многих ARM'ах (на телефонах и на платах типа raspberry pi) есть governor hotplug, который полностью отключает ядра.
G3212:/sys/devices/system/cpu # ls
cpu0 cpu2 cpu4 cpu6 cpufreq cputopo kernel_max offline possible present uevent
cpu1 cpu3 cpu5 cpu7 cpuidle isolated modalias online power rq-stats
G3212:/sys/devices/system/cpu # cat online
0
G3212:/sys/devices/system/cpu # cat offline
1-7
G3212:/sys/devices/system/cpu # cat present
0-7
Она только в user space и используется только пользовательским кодом. Можно иметь несколько разных libc в системе или вообще скомпилировать её статически и вообще никакой libc не будет, кстати (есть суровые нюансы, правда).
Все необходимые функции libc портированы непосредственно в ядро. И есть мнение, что это один из основных недостатков монолитного ядра, (потенциально) ограничивающий бинарную совместимость между ядрами и их модулями.
И в Windows похожий подход практикуется:
.text:0000000078E76670 RtlQuerySystemTime proc near ; CODE XREF: ZwQuerySystemTimej
.text:0000000078E76670
.text:0000000078E76670 ; FUNCTION CHUNK AT .text:0000000078EB3981 SIZE 00000005 BYTES
.text:0000000078E76670
.text:0000000078E76670 mov rax, ds:7FFE0014h
.text:0000000078E76678 mov [rcx], rax
.text:0000000078E7667B jmp short $+2
.text:0000000078E7667D ; ---------------------------------------------------------------------------
.text:0000000078E7667D
.text:0000000078E7667D loc_78E7667D: ; CODE XREF: RtlQuerySystemTime+Bj
.text:0000000078E7667D xor eax, eax
.text:0000000078E7667F rep retn
.text:0000000078E7667F ; ---------------------------------------------------------------------------
.text:0000000078E76681 align 10h
.text:0000000078E76681 RtlQuerySystemTime endp
Но статься не только (и не столько) об этом
Изюминка Zircon: vDSO (virtual Dynamic Shared Object)