Zircon? Что это?


В августе 2016 года, без каких-либо официальных объявлений со стороны Google, были обнаружены исходники новой операционной системы Fuchsia. Эта ОС основана на микроядре под названием Zircon, которое в свою очередь основано на LK (Little Kernel).


Fuchsia is not Linux

Примечания переводчика

Я не настоящий сварщик являюсь разработчиком и/или экспертом Zircon. Тест под катом является компиляцией частичных переводов: официальной документации Zircon vDSO и статьи Admiring the Zircon Part 1: Understanding Minimal Process Creation от @depletionmode, куда было добавлено немного отсебятины (которая убрана под спойлеры). Поэтому конструктивные предложения по улучшению статьи, как и всегда, приветствуются.


О чем пойдет речь в статье?


vDSO в Zircon является единственным средством доступа к системным вызовам (syscalls).


А разве нельзя из нашего кода напрямую вызвать инструкции процессора SYSENTER/SYSCALL? Нет, эти инструкции процессора не являются частью системного ABI. Пользовательскому коду запрещено напрямую выполнять такие инструкции.


Желающих узнать больше деталей о таком архитектурном шаге приглашаю под кат.



Zircon vDSO (virtual Dynamic Shared Object)


Аббревиатура vDSO расшифровывается virtual Dynamic Shared Object:


  • Dynamic Shared Object это термин, используемый для обозначения разделяемых библиотек для формата ELF (.so-файлы).
  • Виртуальным (virtual) этот объект является из-за того, что он не загружается из существующего отдельного файла на файловой системе. Образ vDSO предоставляется непосредственно ядром.

Поддержка со стороны ядра


Поддержка vDSO в качестве единственного контролируемого ABI для приложений пользовательского режима реализуется двумя способами:


  1. Проецирование объекта виртуальной памяти (VMO, Virtual Memory Object).

    Когда zx_vmar_map обрабатывает VMO для vDSO (и в аргументах запрашивается ZX_VM_PERM_EXECUTE), ядро требует, что бы смещение и размер строго совпадали с исполняемым сегментом vDSO. Это (в том числе) гарантирует только одно проецирование vDSO в память процесса. После первого успешного проецирования vDSO в процесс его уже нельзя удалить. А попытка повторного проецирования vDSO в память процесса, попытки удаления спроецированного VMO для vDSO или проецирование с неправильными смещением и/или размером завершаются с ошибкой ZX_ERR_ACCESS_DENIED.
    Смещение и размер кода vDSO еще на этапе компиляции извлекаются из ELF-файла и затем используются в коде ядра для выполнения вышеописанных проверок. После первого успешного проецирования vDSO ядро ОС запоминает адрес для целевого процесса, что бы ускорить проверки.


  2. Проверка адресов возврата для функций системных вызовов.

    Когда код пользовательского режима вызывает ядро, в регистре передается номер низкоуровневого системного вызова. Низкоуровневые системные вызовы являются внутренним (приватным) интерфейсом между vDSO и ядром Zircon. Одни (большинство) напрямую соответствуют системным вызовам публичного ABI, а другие нет.
    Для каждого низкоуровневого системного вызова в коде vDSO есть фиксированный набор смещений в коде, которые совершают этот вызов. Исходный код для vDSO определяет внутренние символы, идентифицирующие каждое такое местоположение. Во время компиляции эти местоположения извлекаются из таблицы символов vDSO и используются для генерации кода ядра, который определяет предикат валидности адреса кода для каждого низкоуровневого системного вызова. Эти предикаты позволяют быстро проверять вызывающий код на валидность, учитывая смещение от начала сегмента кода vDSO.
    Если по предикату определяется, что вызывающему коду не разрешается производить системный вызов, генерируется синтетическое исключение, аналогично тому, как если бы вызывающий код попытался исполнить несуществующую или привилегированную инструкцию.



vDSO при создании нового процесса


Для запуска исполнения первой нити (thread) новосозданного процесса используется системный вызов zx_process_start. Последним параметром этого системного вызова (смотри arg2 в документации) передается аргумент для первой нити создаваемого процесса. По принятому соглашению загрузчик программ отображает vDSO в адресное пространство нового процесса (в случайное место, выбранное системой) и передает базовый адрес отображения аргументом arg2 в первую нить (thread) создаваемого процесса. Этот адрес является адресом заголовка ELF-файла, по которому могут быть найдены необходимые именованные функции для совершения системных вызовов.


Карта памяти (layout) vDSO


vDSO это обычная разделяемая библиотека EFL, которая может быть рассмотрена, как любая другая. Но для vDSO намеренно выбрано небольшое подмножество из всего формата ELF. Это дает несколько преимуществ:


  • Отображение такого ELF в процесс является простым и не включает в себя каких-либо сложных граничных случаев, которые требуются для полноценной поддержки ELF программ.
  • Использование vDSO не требует полнофункционального динамического связывания ELF. В частности, vDSO не имеет динамических перемещений (relocations). Проецирование PT_LOAD сегментов ELF файла является единственным требуемым действием.
  • Код vDSO не имеет состояния и реэнтерабелен. Он работает исключительно с регистрами процессора и стеком. Это делает его пригодным для использования в широком разнообразии контекстов с минимальными ограничениями, что соответствует обязательному ABI операционной системы. А так же упрощает анализ и проверку кода на предмет надежности и безопасности.

Вся память vDSO представлена двумя последовательными сегментами, каждый из которых содержит выровненные целые страницы:


  1. Первый сегмент доступен только для чтения и включает в себя заголовки ELF, а также константные данные.
  2. Второй сегмент является исполняемым и содержит код vDSO.

Весь образ vDSO состоит только из страниц этих двух сегментов. Для отображения памяти vDSO требуются только два значения, извлеченные из заголовков ELF: количество страниц в каждом сегменте.


Константные данные времени загрузки ОС


Некоторые системные вызовы просто возвращают значения, которые являются постоянными (значения должны запрашиваться во время выполнения и не могут быть скомпилированы в код пользовательского режима). Эти значения либо фиксируются в ядре во время компиляции, либо определяются ядром во время начальной загрузки (загрузочные параметры и параметры аппаратного обеспечения). Например: zx_system_get_version(), zx_system_get_num_cpus() и zx_ticks_per_second(). На возвращаемое значение последней функции, например, влияет параметр командной строки ядра.


Подождите, количество CPU это константа?

Интересно, что и в описании функции zx_system_get_num_cpus() так же явно указано, что ОС не поддерживает горячее изменение количества процессоров:


This number cannot change during a run of the system, only at boot time.

Это, как минимум, косвенно указывает на то, что ОС не позиционируется, как серверная.


Поскольку эти значения постоянны, то и нет смысла платить за реальные системные вызовы в ядро ОС. Вместо этого их реализация — простые функции C++, которые возвращают данные, считанные из сегмента констант vDSO. Значения, зафиксированные во время компиляции (такие как строка версии системы), просто компилируются в vDSO.


Для значений, определенных во время загрузки, ядро должно изменить содержимое vDSO. Это выполняется с помощью кода, исполняемого на раннем этапе, который формирует VMO vDSO, прежде чем ядро запустит первый пользовательский процесс (и передаст ему дескриптор VMO). Во время компиляции смещения из образа vDSO (vdso_constants) извлекается из ELF-файла, а затем встраиваются в ядро. А во время загрузки ядро временно отображает страницы, охватывающие vdso_constants, в свое собственное адресное пространство для до-инициализации структуры правильными значениями (для текущего запуска системы).


К чему вся эта головная боль?


Одна из важнейших причин — безопасность. То есть, если злоумышленнику удастся исполнить произвольный (shell-) код, ему придется использовать функции vDSO для вызова системных функций. Первой преградой будет вышеупомянутая рандомизация адреса загрузки vDSO для каждого создаваемого процесса. И поскольку за VMO (virtual memory object) vDSO'а отвечает ядро ОС, оно может выбрать отображение совершенно другого vDSO в конкретный процесс, тем самым запрещая опасные (и не нужные конкретному процессу) системные вызовы. Например: можно запретить драйверам порождать дочерние процессы или обрабатывать проецирование областей MMIO. Это отличный инструмент уменьшения поверхности атаки.


Замечание: на текущий момент поддержка нескольких vDSO активно разрабатывается. Уже существует реализация концепции (proof-of-concept) и простые тесты, но требуется больше работы для улучшения надежности реализации и определения того, какие варианты будут доступны. Текущая концепция предоставляет варианты образа vDSO, которые экспортируют только подмножество полного интерфейса системных вызовов vDSO.


А что у других операционных систем?

Следуют отметить, что подобные техники уже успешно используются в других ОС. Например, в Windows есть ProcessSystemCallDisablePolicy:


Win32k System Call Disable Restriction to restrict ability to use NTUser and GDI

Комментарии (58)


  1. vanxant
    10.01.2019 02:45
    -1

    Гг переизобрели ntdll.dll, kernel32.dll и прочие там gdiplus.dll ^)
    Вообще спасибо за статью, и отдельное спасибо за нормальный перевод.


    1. kITerE Автор
      10.01.2019 09:10

      Ну скорее использовали существующие наработки))


      • адрес загрузки ntdll.dll в разных процессах Windows в рамках одной загрузки одинаковый
      • плюс запрет прямого вызова (в обход системных библиотек) системных вызовов


      1. vanxant
        10.01.2019 09:45

        1. Ну я про то, что дополнительные функции ОС раскиданы по либам, gui в одной, инет в другой. И если хочется слить что-нибудь в инет, а в атакованной проге он не подключен, то это потребует дополнительных телодвижений.
        2. О двух концах палка. Та же работа с сетью теперь станет медленнее: сначала прыгаем в vdso, а уже оттуда в ядро, вместо прямого прыжка в ядро. Или они откроют vdso прямую работу с железом в юзерленде?


        1. kITerE Автор
          10.01.2019 09:50

          Та же работа с сетью теперь станет медленнее: сначала прыгаем в vdso, а уже оттуда в ядро, вместо прямого прыжка в ядро.

          Накладные расходы, безусловно, будут присутствовать. Насколько они серьезно будут отражаться на реальной работе (учитывая, что ОС не позиционируется, как серверная) — нужно замерять.


          Или они откроют vdso прямую работу с железом в юзерленде?

          Сомнительно


        1. Sap_ru
          10.01.2019 12:47

          Накладные расходы могут вообще не изменится, т.к. при правильной реализации механизм переключение контекста не изменяется. Сам по себе прыжок в vdso практически бесплатный и не отличается от вызова любой другой динамический библиотеки. Может даже чуть быстрее стать, т.к. первичную проверку безопасности вызовов можно в userspace вынести и даже частично реализовать аппаратно (типа, вместо проверок по таблицам перед передачей управления ядру, тупо пытаемся читать адреса, где входные данные, и писать в выходные, в случае ошибки просто падаем с сегфолт), а в контексте ядра так не сделаешь и приходится очень аккуратно всё проверять.
          Вопросов два — из-за последних архитектурных уязвимостей (мелтдаун и иже с ними) все ОС не используют/чистят кэши TLB при переключении контекста в ядро. Из-за этого вызов разделяемых всеми процессами «ядерных» библиотек оказывается медленнее, чем вызов «обычных». А учитывая, что уязвимости не ясно как исправлять и в ближайшее обозримое время ничего не изменится в этом плане, то есть мнение, что в реальности смена контекста старыми добрыми специализированными командами с единой точкой входа может оказаться быстрее.
          И, конечно же, если будет найдена уязвимость в vdso, то это разом рушит вообще всю безопасность. А если городить какие-то дополнительные проверки и трамплины, то это сводит на нет все преимущества vdso.


          1. vanxant
            10.01.2019 16:10

            Про проверки соглашусь. А вот лишний прыжок перед syscall не такой уж бесплатный выйдет, особенно если эта vDSO будет не монолитом, а тонко нарезана на 4К-страницы с теми или иными функциями по 1-2 штуки в каждой. Промахи TLB предвижу я.


            1. kITerE Автор
              10.01.2019 16:17

              Позволю себе заметить, что мы сейчас говорим про ОС Fuchsia у которой:


              Пользовательский интерфейс и приложения Fuchsia написаны с помощью "Flutter"

              Который в свою очередь крутит приложения на Dart'е, который по сути является альтернативой JavaScript :)


              1. vanxant
                10.01.2019 16:31

                Ну значит там не только на дарте предполагается приложения пускать.
                Иначе зачем вот это вот всё? Доверенный компилятор байт-кода дарта и так ничего не пропустит.


                1. kITerE Автор
                  10.01.2019 16:36

                  Конечно. А можно и свой user mode полностью написать для Zircon.


                  Но и JIT'инг байт-кода в машинный до сих пор эксплуатируется. И реализация виртуальной машины тоже никогда не станет идеальной (в рамках отсутствия уязвимостей).


                  1. vanxant
                    10.01.2019 16:40

                    Я больше поверю в 0day уязвимость или закладку в том же ядре, которую кому-надо будут тихо эксплуатировать годами, поплёвывая на все эти JIT over vDSO.


      1. TheDaemon
        10.01.2019 10:15

        Извините, а можно подробнее про «запрет прямого вызова»? ИМХО, валидация адреса возврата ничего не дает. Ничего не мешает сформировать какие-то левые аргументы, положить на стек свой адрес возврата и тупо прыгнуть на корректный адрес в образе vDSO, который делает syscall. Доказать, что перед этим отработал «код системной библиотеки» не получится.


        1. kITerE Автор
          10.01.2019 10:49

          Если известен адрес vDSO (или смещений внутри), то проше вызывать публичное ABI. Ставка на рандомизацию адреса загрузки в каждом процессе.


          1. TheDaemon
            10.01.2019 12:15
            +1

            Так во всех современных ОС есть ASLR/аналоги.
            Тогда мне не понятны выкладки про «ограниченный vDSO». Чем отсутствие кода в самом vDSO поможет от вызова «запрещенных» системных вызовов?


            1. vanxant
              10.01.2019 12:38

              Я так понимаю, ядро будет проверять, кто его вызвал.
              Бред какой-то.


            1. kITerE Автор
              10.01.2019 12:55

              Современные ОС c ASLR не запрещают совершать системный вызов из произвольного кода: будь то ntdll, ROP-цепочка из произвольного кода или внедренный шелкод.


              1. TheDaemon
                10.01.2019 13:57

                Правильно. Вот мне и непонятно, как и зачем можно запретить сисколы откуда-то кроме vDSO?


                1. kITerE Автор
                  10.01.2019 14:34

                  Вот мне и непонятно, как

                  Раздел "Поддержка со стороны ядра", пунтк 2: "Проверка адресов возврата для функций системных вызовов."


                  Оригинал: https://fuchsia.googlesource.com/zircon/+/master/docs/vdso.md#Enforcement


                  и зачем можно запретить сисколы откуда-то кроме vDSO?

                  Подробнее тут: https://habr.com/post/435482/#comment_19598458


                  Это отличный инструмент уменьшения поверхности атаки.


                  1. TheDaemon
                    10.01.2019 16:42

                    Я прочитал это. Даже самое первое утверждение

                    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.


                    1. kITerE Автор
                      10.01.2019 16:49

                      Никто не обещал, что до vDSO каким-то мифическим образом можно будет проверить код.


                      Судя по всему, проверяют они тупо PC (RIP) на иструкции syscall.

                      Вероятно это мой промах и авторов оригинальной статьи, что мы не смогли донести эту простую мысль сразу


                      1. vanxant
                        10.01.2019 16:52

                        Опять же не понятен смысл этой проверки. Что мне мешает, узнав адрес входа в функцию VDSO, сразу прыгнуть на этот адрес+смещение syscall?


                        1. kITerE Автор
                          10.01.2019 16:56

                          https://habr.com/post/435482/#comment_19598754


                          Что мешает этому коду получить адрес vDSO, а это легкодоступная информация, и даже прочитать vDSO и получить адреса и коды системных функций?

                          Мешает незнание адреса vDSO, так как это не легкодоступная информация. Не должно быть фиксированного адреса или регистра, по которому можно было бы узнать адрес vDSO.


                    1. Sap_ru
                      10.01.2019 21:45

                      В ARM есть механизм, кстати. Там аппаратно адрес возврата в стек кладётся.


                      1. TheDaemon
                        11.01.2019 09:32

                        Может вы имеете в виду LR регистр?
                        И все равно какой код до этого выполнялся узнать нельзя, только откуда был последний call. Сделайте jmp (b) :)


                        1. Sap_ru
                          11.01.2019 12:57

                          Я имею в виду MSR/PSR стеки и аппаратный сброс содержимого регистра PC.


                          1. TheDaemon
                            11.01.2019 13:43
                            +1

                            Если вам не сложно, можете дать ссылку, где почитать?
                            А то я знаю, что такое Program Status Register, а вот что за MSR/PSR стэки и что за сброс PC — не слышал даже


                            1. Sap_ru
                              12.01.2019 01:59

                              Даже и не знаю. Это самая база архитектуры ARM. Я доки начального уровня уже очень давно не читаю — есть отличные мануалы от самой ARM, но нужно понимать о чём речь, так что не подскажу, к сожалению. Но вообще-то аналогичного эффекта можно и на Intel добиться используется страницы-трамплины, переключающие контекст. На ARM есть совершенно эквивалентные механизмы, кстати.


            1. Sap_ru
              10.01.2019 12:58

              Переключение контекста на ядро будет происходить по передаче управления на заранее известные адреса, как я понимаю. И некоторые страницы-трамплины можно просто не отображать в пользовательское пространство, что сделает вызов некоторых функций ядра физически невозможным.
              Но зачем для этого vDSO?


              1. TheDaemon
                10.01.2019 13:59
                +1

                Вот и мне непонятно, при чем тут vDSO и в чем сокраментальное отличие от стандартных сисколов.


                1. kITerE Автор
                  10.01.2019 14:21

                  Стандартные сисколы требуют только знания индекса системной функции.
                  Вызов функции из vDSO требует знания ее адреса, который специально рандомизируется в адресном пространстве.


                  Тут постарался раскрыть мысль: https://habr.com/post/435482/#comment_19598458


                  1. Sap_ru
                    10.01.2019 15:30

                    Это лишь вопрос организации переключения контекста. Переключайте контекст на ядро через передачу управления на шлюзовую страницу и делайте адрес страница случайным — легко. И никаких сисколов.
                    Более того «сискол» это абстракция уровня компилятора, грубо говоря.
                    Сама же реализация сискола может быть разной. Можно собрать Linux в котором сискол будет операцией передачи управления на случайную шлюзовую страницу (и, если не ошибаюсь, то для ARMx64 оно именно так и работает).


                    1. kITerE Автор
                      10.01.2019 15:39

                      Это лишь вопрос организации переключения контекста.

                      И концепция vDSO один из вариантов реализации.


                      Более того «сискол» это абстракция уровня компилятора, грубо говоря.

                      Мы говорим об одной и той же инструкции процессора syscall/sysenter? Про https://www.codemachine.com/article_syscall.html?


                      Можно собрать Linux в котором сискол будет операцией передачи управления на случайную шлюзовую страницу

                      vDSO это не какой-то rocket science, который не повторить в другой ОС, это просто PoC.
                      Но заложенный принцип в начале разработке ОС не потребует в будущем ломать бинарную совместимость с существующим кодом.


          1. Sap_ru
            10.01.2019 12:53

            Есть ощущение, что это можно сделать тупо скомпилировав vdso как «position independent». Все современные архитектуры умеют работать с таким кодом. Это в рамках существующих ОС можно сделать. И двигай тогда его по пользовательскому пространству, куда хочешь. Точнее, двигать не нужно — отображай на случайные адреса и всё. Будет совсем небольшое падение производительности за счёт того, что вся адресная арифметика будет косвенной, но все современные суперскалярные архитектуры умеют подобное оптимизировать.
            Всегда мучил вопрос, почему не используют этой фичи.


            1. kITerE Автор
              10.01.2019 13:00

              Есть ощущение, что это можно сделать тупо скомпилировав vdso как «position independent».

              Вероятнее всего так и сделано, раз vDSO не требует обработку reloc'ов


              Всегда мучил вопрос, почему не используют этой фичи.

              Внедрение новых механизмов безопасности всегда идет со скрипом (https://habr.com/company/pt/blog/424633/ например), получается сделать это в новой ОС иногда проще.


  1. vassabi
    10.01.2019 02:46

    с одной стороны — вроде бы да, но с другой стороны, и так понятно, что редкий вредонос нынче поражает все исполняемые файлы. А если 0day уязвимость есть в приложении с доступом к «широкому vDSO», то факт того, что в соседнем приложении «ограниченный vDSO» — уже мало чем поможет…


    1. kITerE Автор
      10.01.2019 09:24

      Допустим, удаленно пробили (RCE) браузер с "широким vDSO". Если не было второй уязвимости раскрытия памяти, то мы не знаем где в памяти расположены системные библиотеки (ни файл прочесть, ни через сокет отравить). Одним из существующих приемов — определить (или угадать) версию ОС заранее, а затем включить в shell-код системные вызовы напрямую (как кусок кода из ntdll.dll той же версии, а на Linux, вроде, номера системных вызовов достаточно стабильны). Здесь же придется сканировать память на предмет поиска ELF заголовка. Насколько это сложно, учитывая то, что нужно корректно обработать отсутствующие страницы виртуальной памяти (организовать SEH) — не знаю, надо изучать внутренности обработки исключений по памяти для Fuchsia.


      1. Sap_ru
        10.01.2019 13:03

        Но ведь остаётся атака через чтение и анализ кода самой vDSO. А если мы при любом вызове vDSO будем контекст переключать, чтобы скрыть её потроха от пользователя, то это сводит на нет всё плюсы — давайте тогда уже сразу в ядро переключаться и ядро рандомизировать при загрузке. Вектор атаки не изменяется — нужно суметь получить доступ на чтение к ядру — а накладные расходы меньше.
        Либо городить некий промежуточный уровень привилегий, но это не модно, медленно и сомнительно и снова не имеет отношения к vDSO.


        1. kITerE Автор
          10.01.2019 13:04

          Но ведь остаётся атака через чтение и анализ кода самой vDSO.

          Как прочитать то, адрес чего мы не знаем?


          1. Sap_ru
            10.01.2019 13:09

            А как будем вызвать то, адрес чего мы не знаем? Как раз в своём адресном пространстве мы vDSO очень даже знаем, где находится.


            1. kITerE Автор
              10.01.2019 13:13

              Легитимный код нити знает, ему аргументом передан адрес vDSO (который расположен с памяти процесса по произвольному случайному адресу).


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


              1. Sap_ru
                10.01.2019 13:38

                Может быть я конечно туплю, но…
                У нас пользовательский код знает расположение vDSO? Мы защищаемся от внедрённого кода, который по какой-то причине не атакует пользовательский процесс, ничего о нём не знает, но пытается при этом атаковать ядро, исполняясь из этого самого пользовательского процесса? Нет, такой конечно может случиться, но это крайне редкие ситуации и все современные ОС успешно с этим борются.
                Если у вас чужой код уже выполняется в пользовательском процессе, то весь процесс нужно считать скомпрометированным, а не предполагать, что атакующий по какой-то причине будет атаковать только код ядра.
                В общем, вижу я противоречие в Ваших словах.


                1. kITerE Автор
                  10.01.2019 14:18

                  Мы защищаемся от внедрённого кода, который по какой-то причине не атакует пользовательский процесс, ничего о нём не знает, но пытается при этом атаковать ядро, исполняясь из этого самого пользовательского процесса?

                  Да. Злоумышленник шлет вредоносный pdf, который обрабатывается уязвимым ридером:


                  • выделяются блоки памяти в куче, куда копируется содержимое файла вместе с вредоносным машинным кодом (shell-кодом)
                  • используя уязвимость, злоумышленнику удается передать управление на shell-код

                  Shell-код ничего не знает о процессе, в который он загружен, кроме того, что он сидит где-то в heap'е этого процесса. Адреса хипов тоже рандомизируются. До ASLR он просто вызывал функции системных библиотек по захардкоженным (известным заранее) адресам. Потом ввели ASLR, и атакующий (для исполнения полезных действий) стал в сам shell-код встраивать инструкции syscall'а (которым нужен только индекс функции, а не адрес в 64-х битном адресном пространстве).


                  NtReadFile:


                      mov          eax,6
                      syscall

                  Нет, такой конечно может случиться, но это крайне редкие ситуации и все современные ОС успешно с этим борются.

                  С переменным успехом, стоимость эксплуатации уязвимостей растет. В Zircon следующий шаг: давайте не будем позволять произвольному коду вызывать syscall'ы.


                  Никто и не позиционирует этот механизм, как серебряная пуля против эксплоитов:


                  Это отличный инструмент уменьшения поверхности атаки.


                  1. Sap_ru
                    10.01.2019 15:32

                    У вас работающий вредоносный код в юзерспейс.
                    Что мешает этому коду получить адрес vDSO, а это легкодоступная информация, и даже прочитать vDSO и получить адреса и коды системных функций и сделать прямой вызов в ядров, сымитировав вызов из DSO, подделав адрес возврата в стеке (благо, что юзерспейс контролирует свой стек)?


                    1. kITerE Автор
                      10.01.2019 15:35

                      У вас работающий вредоносный код в юзерспейс.

                      Да


                      Что мешает этому коду получить адрес vDSO, а это легкодоступная информация, и даже прочитать vDSO и получить адреса и коды системных функций?

                      Мешает незнание адреса vDSO, так как это не легкодоступная информация. Не должно быть фиксированного адреса или регистра, по которому можно было бы узнать адрес vDSO.


                      1. TheDaemon
                        10.01.2019 15:58
                        +1

                        Это скрыть нереально. Шелл-код будет исполнятся в контексте реального потока реальной программы вызывающей ядро. На стеке лежит гора разных указателей, через которые можно выползти на vDSO.
                        ASLR — это способ борьбы с передачей управления на шелл-код/ROP. Если управление уже у него, ASLR, как и vDSO с рандомным адресом ничем не поможет.
                        Разницы между переключением контекста через syscall или pagefault на выделенной страничке я не вижу.


                        1. kITerE Автор
                          10.01.2019 16:05

                          На стеке лежит гора разных указателей, через которые можно выползти на vDSO.

                          А можно обрушить текущий процесс, если это не указатель, а просто число, похожее на адрес.


                          Разницы между переключением контекста через syscall или pagefault на выделенной страничке я не вижу.

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


                          1. TheDaemon
                            10.01.2019 18:58

                            Шелл-код написан под конкретный процесс, а не под абстрактный. Он знает всю структуру стека, так как знает какую функцию эксплуатирует и где в программе она вызывается.

                            Правильно, можно и делить. Просто до того как я выяснил, что syscall у них все равно есть, обсуждалось переключение контекстов через разные места.


                            1. kITerE Автор
                              10.01.2019 19:30

                              Шелл-код написан под конкретный процесс, а не под абстрактный. Он знает всю структуру стека, так как знает какую функцию эксплуатирует и где в программе она вызывается.

                              Не всегда: часто уязвимости подвержено огромное множество версий продукта. Привязать уязвимость к конкретной структуре стека (для конкретной версии ПО) сильно ухудшает, как мне кажется, результативность. Особенно для попыток эксплуатации в слепую, например через массовые рассылки электронной почтой.


                              1. TheDaemon
                                10.01.2019 19:44

                                Ну так можно сказать и что ОС атакующему неизвестна :) мол у нас на Zircon'е ваши виндовые эксплоиты не работают, поэтому все безопасней.

                                Мы с вами говорим про малоизвестную, нераспространенную и специализированную ОС. В моем понимании, все ожидаемые атаки будут целевыми.


                                1. kITerE Автор
                                  10.01.2019 19:52

                                  Ну так можно сказать и что ОС атакующему неизвестна :)

                                  Узнать версию ОС (точнее не версию, а семейство, в рамках которого индексы syscall'ов стабильны) перед атакой проще, чем точную версию Adobe Acrobat'а. Например иногда сливается инфа, что была массовая закупка лицензий Windows 10 для всего предприятия (или даже так https://habr.com/post/408177/, или так https://habr.com/post/63081/).


                                  Мы с вами говорим про малоизвестную, нераспространенную и специализированную ОС. В моем понимании, все ожидаемые атаки будут целевыми.

                                  Если профессиональная целенаправленная атака — согласен.
                                  Но скоро же Google заменит на всех телефонах Android на Fuchsia :) Вот тогда-то и пригодятся все средства борьбы с нетаргетированными угрозами.


  1. TheDaemon
    10.01.2019 10:19

    Например: можно запретить драйверам порождать дочерние процессы или обрабатывать проецирование областей MMIO.

    Т.е. ядро с ядром у них тоже через vDSO общается? Это как? Или они драйвера в user mode утащили? А что с прерываниями делают?
    update: нашел вашу ссылку, поглядел. Да, правда в user mode. Видимо правда все будет медленно но верно :)


    1. kITerE Автор
      10.01.2019 10:53

      "Бесплатные" механизмы безопасности в наше время большая редкость))


  1. rogoz
    10.01.2019 10:58

    ОС не поддерживает горячее изменение количества процессоров:
    Это, как минимум, косвенно указывает на то, что ОС не позиционируется, как серверная.

    На многих ARM'ах (на телефонах и на платах типа raspberry pi) есть governor hotplug, который полностью отключает ядра.


  1. awoland
    10.01.2019 13:24

    А чем (существенно и принципиально) этот vDSO отличается от того-же Эппловского XNU commpage?


    1. kITerE Автор
      10.01.2019 14:37

      За эппловский XNU commpage не скажу (не знаю), но ключевые особенности vDSO:


      • защита содержимого со стороны ядра
      • рандомизируемый адрес загрузки для каждого процесса в системе
      • запрет инструкций syscall'ов для всего остального кода
      • в разработке: разные vDSO для разных процессов


  1. amarao
    10.01.2019 15:52

    vDSO в линуксах давным-давно. Например, gettimdate не переключается в контекст ядра и выполняется очень быстро (потому что время можно прямо из ядра читать, и функция в vDSO это и делает).


    1. vanxant
      10.01.2019 16:04

      а libc.so ещё дольше, и по факту, с точки зрения прикладного программиста, он берёт на себя половину «функций ядра линукс». Также как и ntdll.dll в винде.


      1. Sap_ru
        10.01.2019 21:50

        Эээ… libc нет в kernel space :) Сюрприз!
        Она только в user space и используется только пользовательским кодом. Можно иметь несколько разных libc в системе или вообще скомпилировать её статически и вообще никакой libc не будет, кстати (есть суровые нюансы, правда).
        Все необходимые функции libc портированы непосредственно в ядро. И есть мнение, что это один из основных недостатков монолитного ядра, (потенциально) ограничивающий бинарную совместимость между ядрами и их модулями.


    1. kITerE Автор
      10.01.2019 16:07

      И в 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

      Но статься не только (и не столько) об этом