Автор: (c)Крис Касперски ака мыщъх
Этот обзор еще более необычен, чем предыдущий и посвящен он... ошибкам процессоров, обнаруженных за последнюю пару месяцев и существенно облегчающих нашу хакерскую жизнь. Даже если операционная система не содержит дыр (например, OpenBSD), удаленная атака все равно остается возможной благодаря дефектам проектирования процессора!
В июне 2008 года сотрудники тестовой лаборатории корпорации Intel обнаружили ошибку в процессоре Core 2 Extreme Processor QX9775, связанную с некорректной обработкой невыровненных условных переходов, пересекающих 16-байтовые границы и высаживающие ЦП на измену, результаты которой варьируются от генерации исключения "machine check exception" до полного зависания системы. Баг возникает только на определенных временных диаграммах, а потому чаще всего он проявляется именно в компактных вложенных циклах (short nested loops), для выполнения которых достаточно... запустить на машине жертвы специальный JavaScipt и тогда (при активном Java-компиляторе, компилирующем код в память), мы получим тотальный отказ в обслуживании, т.е. DoS. Назвать эту ошибку новой нельзя, т.к. впервые она была обнаружена еще в феврале этого же года в процессоре Core 2 Extreme Processor QX9000, а спустя неделю - еще в куче других и пока Intel рассылала разработчикам BIOS'ов рекомендации по устранению обозначенного дефекта, в руки тестеров попал новый (едва ли не новейший, а потому жутко дорогой) Core 2 Extreme Processor QX9775, содержащий ту же самую ошибку, полученную по "наследству".
Данный дефект выявлен в следующих процессорах: Dual-Core Xeon E3110, Quad-Core Xeon 3300, Dual-Core Xeon 5200, Quad-Core Xeon 5400, Core Extreme QX9775, Core 2 Extreme Quad-Core QX6000, Core 2 Quad Q6000, Core 2 Extreme QX9000, Core 2 Quad Q9000, однако ошибка может присутствовать и в более младших процессорах, уже снятых с производства, а потому и не тестируемых, но все еще широко используемых на рабочих станциях и серверах.
Исходный текст exploit'а, написанный мыщъхем на ассемблерных вставках на MS Visual C++ приведен ниже. При выполнении на указанных процессорах с дряхлой версией BIOS'а наступает крах (зависон). Для переноса exploit'а на Java/JavaScript необходимо знать особенности Java-транслятора конкретного браузера, а потому готовые решения здесь не приводятся.
int bar; __declspec(naked) foo() { __asm{ MOV EDI, EDI MOV bar, ESP MOV ESP, offset bar+666h POPAD MOV ESP, bar LX: MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI inc eax jz LX MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI NOP inc ebx jz LX MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI NOP inc ecx jz LX mov esi, eax mov edi, ebx mov ebp, ecx L1: NOP MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI dec esi jnz L1 L2: MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI NOP dec edi jnz L2 L3: MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI NOP dec ebp jnz L3 MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI MOV EDI, EDI NOP dec edx jnz LX retn } }
Листинг 1. Исходный ассемблерный код exploit'а, завешивающего многие процессоры.
Обновить прошивку BIOS, если производитель материнской платы исправил этот дефект процессора (что вовсе не факт!).
Рисунок 1. В мыщъхином exploit'е условные переходы вложенных циклов нарочно выровнены так, чтобы они преднамеренно рассекали 16-байтовые границы.
В июне 2008 года в процессорах Core 2 Extreme Quad-Core QX6000 и Core 2 Quad Q6000 была обнаружена ошибка в менеджере виртуальных машин (Virtual Machine Manager или, сокращенно, VMM), позволяющая зловредному коду, исполняющемуся на нулевом кольце, "убивать" текущую виртуальную машину. Линейку NT-подобных систем этим не удивишь, т.к. с нулевого кольца легко устроить BSOD даже безо всяких ошибок в ЦП, но вот Linux/BSD некорректным модулем ядра завалить труднее, к тому же если виртуальная машина предусматривает автоматический рестарт гостевой оси при возникновении каких-то "терок" (что часто встречается на виртуальных серверах, работающих на "автопилоте"), то убийство виртуальной машины превращается в реальную угрозу. Как ее реализовать? Оказывается, достаточно задействовать опцию IA32_DEBUGCTL.FREEZE_WHILE_SMM_EN, которую можно активировать посредством установки бита FREEZE_WHILE_SMM_EN в MSR-регистре IA32_DEBUGCTL. И все! Виртуальная гостевая машина аварийно завершится с кодом 80000021h. Хана, короче! Подробнее об этой хане можно прочитать в разделе "VM-Entry Failures During or After Loading Guest State" руководства "Intel 64 and IA-32 Architectures Software Developer's Manual Volume 3B: System Programming Guide, Part 2".
В настоящее время дефект обнаружен и подтвержден в кристаллах Core 2 Extreme Quad-Core QX6000 и Core 2 Quad Q6000, за остальные процессоры пока ничего неизвестно, но вполне возможно, что они также содержат эту ошибку.
Исходный текст exploit'а, написанный мыщъхем на ассемблере, приведен ниже (чтобы его использовать, необходимо подставить фактический номер MSR-регистра в его имя, воспользовавшись соответственным заголовочным файлом от Intel или сделать это своими руками - номера MSR и всех их атрибутов содержатся в документации).
MOV ECX, IA32_DEBUGCTL RDMSR ; читаем содержимое IA32_DEBUGCTL в EDX:EAX OR EAX, 4000h ; взводим бит FREEZE_WHILE_SMM_EN WRMSR ; обновляем MSR-регистр
Листинг 2. Убийство гостевой виртуальной машины четырьмя ассемблерными командами.
Intel предлагает как решение на уровне BIOS'а, так и программные "костыли", требующие модификации кода существующей виртуальной машины. Однако ни разработчики виртуальных машин, ни Microsoft (со своим Server'ом 2008, в состав которого входит виртуализатор) никак не отреагировали на ситуацию. Самое смешное, что даже сама Intel, выпустившая виртуализатор VirtualBox (бесплатный, кстати) не стала править код. Так что... защита нам только снится, а истина, как водится, в траве.
Рисунок 2. Зеленый цвет процессоров семейства Intel (мобильная версия) наводит на мысли о траве.
В конце марта 2008 года в процессоре Dual-Core Xeon 7000 был обнаружен мелкий, но весьма противный дефект варварского типа, "поджидающий" кристалл в узких переходах между 64-битным режимом основной (host) операционной системы и 32-битным режимом гостевой виртуальной машины. При задействованном режиме Hyper-Threading процессор (при стечении определенных обстоятельств) с некоторой (впрочем, довольно незначительной) вероятностью либо выбрасывает IERR#, либо уходит в глухой зависон, отправляя в небытие не только виртуальные машины, но и основную операционную систему. А вот это уже нехорошо! Подробности этого увлекательного круиза смерти можно найти в официальном обновлении спецификаций "Specification Update" http://download.intel.com/design/xeon/specupdt/309627.pdf (внимание! Intel любит менять номера pdf-файлов, поэтому данная ссылка через некоторое время может перестать действовать, тогда заходим на сайт Intel и находим нужный нам документ по имени процессора).
В настоящее время данная ошибка обнаружена и подтверждена для Dual-Core Xeon 7000, но вполне возможно, что она имеется и в других процессорах данного семейства.
Руками не трогать! Оно и само упадет ;-) Со временем... А чтобы помочь ему упасть, достаточно выполнять побольше переходов между 32-разрядной виртуальной гостевой машиной и 64-битной операционной системой. А как их выполнять? Да очень просто - достаточно, например, создать шторм TCP/IP-пакетов, на физическом уровне обрабатываемый сетевой картой основной операционной системы, а значит - переключающий контекст выполнения на ее драйвер для обработки очередного прерывания. Пройдет совсем немного времени, как случиться глобальный завис!
По утверждению Intel, проблему можно решить на уровне BIOS'а и фирма уже разослала ведущим производителям BIOS'ов и материнских плат рекомендации по обходу бага, однако что-то производители не спешат реагировать... и обновленные прошивки BIOS'а к этому дефекту процессора совершенно перпендикулярны, ну а отдуваются, как всегда - конечные пользователи...
Рисунок 3. Процессор Dual-Core Xeon 7000 на сайте Intel вместе со всей документацией и обновлениями спецификаций с перечнем обнаруженных дыр.
Поздравляем! Intel веников не вяжет! 14 мая 2008 года в новейшем кристалле Core 2 Extreme QX9000, обнаружен древний баг, известный еще с декабря 2005! Очень красивый, элегантный и чертовски полезный баг, затрагивающий множество процессоров, выпущенных корпорацией Intel за последние несколько лет, что позволяет использовать его для защиты программного кода от всяких там дизассемблеров, эмулирующих отладчиков и реверсеров, отлаживающих малварь на живых, но слегка устаревших машинах, в то время как легион геймеров всегда в авангарде, то есть - с новым железом! Но все по порядку! Баг связан с инструкцией IRET, традиционно использующейся в обработчиках аппаратных прерываний. Однако эта команда может использоваться и на прикладном уровне для передачи управления на кольцо с идентичным уровнем привилегий. При этом IRET последовательно выталкивает из стека регистр EIP, селектор CS и содержимое флагов, то есть если предварительно сохранить в стеке флаги, текущий CS и указатель на метку label, то мы получим завуалированный аналог jmp label, но только если jmp label распознает любой дизассемблер, то IRET - обламывает ИДУ и по самые помидоры и создавать перекрестные ссылки приходится вручную. Но это - мелочи. Все интересное сидит внутри IRET. Это только с виду она кажется простой командой, но даже в x86-процессорах псевдокод, поясняющий действие IRET (приводимый в мануалах от Intel), занимал несколько страниц (а реальный микрокод и того больше). Поддержка 64-битного режима усложнила IRET в несколько раз. Во-первых, 64-битный режим требует обязательного выравнивания там, где в x86-процессорах оно было опционально, управляясь битом AM регистра CR0 (недоступного с прикладного режима) и битом AC регистра флагов EFLAGS (прекрасно доступным с прикладного режима). А теперь вопрос на засыпку: как должен вести себя процессор, если на момент начала выполнения IRET флаг AC сброшен (контроль выравнивания отключен), стек не выровнен, а в сохраненном значении регистра флагов (который будет извлечен после завершения инструкции IRET) флаг AC взведен? x86-процессоры (и некоторые x86-64) на это реагируют вполне адекватно, то есть врубают контроль выравнивания после того, как IRET закончит свою работу, что вполне логично, поскольку с точки зрения программиста все инструкции атомарны, т.е. неделимы и выполняются за одну абстрактную итерацию. Однако поскольку в действительности мы имеем дело не со сферическими конями в вакууме, а с RISC-ядром, выполняющим микрокод, то из-за ошибок в этом самом микрокоде контроль выравнивания включается во время выполнения инструкции IRET, что приводит к генерации прерывания Alignment Check Exception (#AC). Причем ошибке подвержена только та часть микрокода IRET, которая отвечает за передачу управления с кольца 3 на кольцо 3. Межкольцевой вызов, реализованый в обозначенной ситуации, исключения не вызывает.
Рисунок 4. Core 2 Duo - один из многих процессоров с дырой, официально именуемой "IRET under Certain Conditions May Cause an Unexpected Alignment Check Exception".
Дефект обнаружен и подтвержден в следующих процессорах (пристегните ремни безопасности, прежде чем читать): Dual-Core Xeon E3110, Dual-Core Xeon 3000, Quad-Core Xeon 3200, 64-bit Intel Xeon, Quad-Core Xeon 3300, Dual-Core Xeon 5000, Dual-Core Xeon 5100, Quad-Core Xeon 5300, 64-bit Xeon MP, Dual-Core Xeon 7000, Dual-Core Xeon 7100, Dual-Core Xeon 7200, Quad-Core Xeon 7300, Core 2 Extreme QX9775, Core 2 Extreme Quad-Core QX6000, Core 2 Quad Q6000, Core 2 Extreme QX9000, Core 2 Quad Q9000, а также, возможно, некоторых других.
Как эту дыру можно использовать для атаки? А никак! Если программное обеспечение, используемое жертвой, использует IRET для передачи управления с кольца 3 на кольцо 3 (а зачем ему это делать?!), да еще работает с невыровненным стеком, да еще выталкивает в регистр флагов установленный бит AC - так оно само упадет. Intel даже наблюдала такое поведение на некоторых программах, правда не сказала - каких. Но падает только "неправильное" приложение, а не вся система целиком и чтобы выполнить IRET на атакуемой машине, хакеру нужно иметь хотя бы минимальные правда для выполнения своего кода (исполняемого файла или shell-кода), но в таком случае уронить приложение можно и без всякого IRET'а. Смысл?! А смысл в том, что при данных обстоятельствах на багистном процессоре IRET передаст управление вовсе не туда, куда ожидалось! То есть, мы можем написать очень хитрый код, который в дизассемблере (или под эмулирующим отладчиком) выполняет невинные действия, а вот на живой машине (с багистным процессором) не только передает управление на основное тело, но также использует код исключения для его расшифровки. Если исследователь малвари не в курсе этого бага, ему придется изрядно попыхтеть, не говоря уже за эвристический анализ, который отпадает сразу. Исходный код ассемблерного кода, демонстрирующего эту уязвимость, приведен ниже:
DEC ESP ; делаем стек невыровненным PUSHFD ; сохраняем флаги в стеке POP EAX ; выталкиваем в EAX OR EAX, 40000h ; взводим AC бит PUSH EAX ; сохраняем EAX в стеке PUSH CS ; сохраняем селектор кода PUSH offset my_next ; сохраняем адрес перехода IRET my_next ; сюда предполагается передать управление INC ESP ; возвращаем стек на место
Листинг 3. Ассемблерный текст exploit'а, генерирующего #AC исключение на багистных процессорах и не генерирующего его на правильных ЦП.
А что тут можно сделать?! Нет, ну правда, конечным пользователям - просто расслабиться. Реверсерам малвари - учитывать эту фичу при анализе кода.
Рисунок 5. Дырявый Core 2 Extreme Dual-Core от Intel (стоимость дыр заложена в себестоимость изделия).
Попытка практической реализации proof-of-concept exploit'а поначалу не предвещала никаких неожиданностей. Горизонт был чист, сияло солнце. А между тем, гроза уже висела над мыщъхиной норой, а за ней и градовая туча подоспела. #AC-исключение не генерировалось! Хоть убей. Чего только мыщъх не делал... Курил, точил, долбил, перечитывал errata от Intel столько раз, что выучил наизусть. Под конец даже засомневался в здравости рассудка (своего или инженеров из Intel), но как всегда, виноватой оказалась Microsoft.
Рисунок 6. Вот тут чел из MS открыто признает, что ядро "зажимает" #AC-исключение на x86-системах.
Анализ обработчика исключений, сосредоточенного в ядре, показал, что система самым наглым образом ныкает #AC-исключение, не передавая его на прикладной уровень и потому до SEH/VEH обработчиков оно просто не доходит. "До Штирлица перестали доходить письма из Родины...". Мыщъхиные раскопки ядра подтвердились несколькими независимыми источниками. Так, например, Program Manager (Manager - не в смысле программы, это человек такой по имени Kang Su Gatlin) из группы Microsoft Visual C++ без ложной скромности писал: "On the x86 architecture, the operating system does not make the alignment fault visible to the application" (http://msdn.microsoft.com/en-us/library/aa290049(VS.71).aspx), что испытал на своей шкуре хакер Zahical, цитируя мистера Kang Su Gatlin на форуме, где тусуются сишные программисты, причем довольно хорошо и плотно так тусуются: http://www.tech-archive.net/Archive/VC/microsoft.public.vc.language/2004-12/0199.html.
Уточним - это x86-ядро "зажимает" #AC-исключение, а вот что касается x86-64 - все зависит от того, был ли запущен процесс с флагом SEM_NOALIGNMENTFAULTEXCEPT, переданным API-функции SetErrorMode() и если да, то исключение будет вновь "зажиматься". К счастью, по умолчанию процесс стартует без этого флага и приведенный выше исходный код exploit'а будет работать как часы.
Рисунок 7. Бедный старый MSDN, идущий на диске с MS Visual Studio 6.0 - он по своей наивности считал, что флаг SEM_NOALIGNMENTFAULTEXCEPT применим только к RISC'ам! И не знал, что через несколько лет та же самая фигня будет и на x86-64.
Ладно, оставим x86-64 системы. С ними все слишком просто, да и не очень-то они широко распространены. Вернемся назад к x86 и посмотрим, что тут можно сделать. Итак, ядро зажевало исключение и нам его никак не получить, разве что спуститься на ядерный уровень... Стоп!!! А ведь это шикарный способ передачи управления ядерному модулю! Допустим, у нас есть rootkit, устанавливающий драйвер, с которым взаимодействует прикладной код. Стандартный интерфейс DeviceIoControl() слишком известен и слишком заметен. А вот если драйвер перехватывает вектор #AC-исключения (что на ядерном уровне реализуется без проблем - например, путем прямого хука таблицы дескрипторов прерываний - она же IDT), то IRET с невыровненным стеком заставит процессор сгенерировать исключение, подхватываемое нашим драйвером. Можно до упаду анализировать малварь, но в упор не видеть, что IRET передает управление не с ring-3 на ring-3 (согласно документации), а на... ring-0. И уже нашему драйверу решать, что делать дальше. Можно передать управление обратно на ring-3 по адресу, занесенному в стек, но ведь можно же и не передавать! Или передавать, но не туда. Или (как уже говорилось выше), использовать вектор исключения для расшифровки остального тела rootkita. Конечно, ключ получается какой-то беспонтовый и не слишком криптостойкий, точнее - совсем не криптостойкий, но здесь главное - скрыть сам факт расшифровки. И благодаря ошибке в ЦП он очень даже хорошо скрывается. Конечно, данный код будет работать не на любом ЦП (хотя и на многих), но малварь (в отличие от коммерческих программ) это обстоятельство как-нибудь переживет. Ну, не один компьютер будет заражен, так другой...
Хорошо, а как быть, если у нас драйвера нет, а писать его в лом. Можно ли обнаружить, что исключение имело место быть?! Конечно! Обработка исключений - далеко не самая дешевая операция (в плане процессорных тактов) и замеряя время выполнения IRET, мы легко установим истинное положение дел. "Правильная" IRET занимает не больше нескольких сотен "тиков", легко измеряемых инструкцией RDTSC, а вот скрытая обработка исключения ядром уже тянет на десятки тысяч. Если операция измерения времени выполнения IRET не сильно бросается в глаза, то хакер легко запутает реверсеров, анализирующих малварь. И уж точно обойдет всякие эмулирующие отладчики и многие виртуальные машины.
Учитывая, что Intel фиксить эту багофичу не собирается, стоит взять ее на вооружение, смело кинувшись в бой! Тем более, что это не единственная дыра в процессорах. Есть и другие. Да там их сотни!!! Мыщъх как раз сейчас нарабатывает фактический материал и точит доклад, который (при благоприятном стечении обстоятельств) будет прочитан на хакерской конференции "Hack In The Box Security", проводимой в Малайзии в октябре 2008 года: http://conference.hackinthebox.org/ (для желающих посетить сие мероприятие напоминаю, что Малайзия очень удобна тем, что не требует визы, достаточно одного паспорта).
Рисунок 8. HITBSecConf2008 - крупнейшая азиатская хакерская конференция, проводимая в Малайзии, на которой есть на что посмотреть и есть кого послушать, намотав кучу полезной инфы себе на хвост.