MSR-hacker

MSR-регистры на службе хакера

крис касперски ака мыщъх, a.k.a. nezumi, a.k.a. souriz, no-email

популярные дебагеры (soft-ice, syser, olly) используют базовые отладочные возможности, появившиеся еще в 80386 и совершенно игнорируют тот факт, что процессоры изрядно подросли и обросли целым комплексом отладочных механизмов термоядерного типа, реализованных в виде нестандартных расширений, управляемых посредством специальных MSR-регистров с помощью которых мы можем перехватывать системные вызовы, трассировать ветвления в реальном времени, дампить содержимое регистров общего назначения на каждом такте и делать еще множество других удивительных вещей, существенно упрощающих взлом защищенных программ

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

Виктор Пелевин, «Желтая Стрела»

Количество регистров Pentium-процессоров вплотную приближается к тысяче и хотя значительная часть из них носит сугубо служебный характер, сути дела это не меняет. Часть регистров стандартизирована и реализована во всех моделях (например, EAX, CR3, DR1), часть — специфична для определенной группы процессоров и совместимость с другими процессорами негарантированна, хотя все они продаются под торговой маркой Pentium. Вот о таких специфичных регистрах мы и поговорим.

В технической документации они скрываются за аббревиатурой MSR – Model Specific Register(s), и впервые такие регистры появились (если мне не изменяет память) в P6, более известном как Pentium Pro и его «бюджетном» собрате Pentium-II. Революции не свершилось, но команда RDTSC с тех времен прочно вошла в лексикон всех разработчиков защит. Специальный MSR-регистр каждый такт увеличивает свое значение на единицу, а команда RDTSC позволяет прикладным программам считывать его содержимое. Как результат — при пошаговом прогоне защищенного кода под отладчиком, кол-во «тиков» процессора между двумя соседними замерами резко возрастала и защита палилит хакера только так.

Разработчики Xenon-процессоров (используемых главным образом в серверах и высокопроизводительных рабочих станциях) совершили огромный бросок вперед, сотворив новые отладочные механизмы, глядя на которые обладатели Pentium-II/III только облизывались. Но с появлением Pentium-4 эти возможности, хлынув в бюджетную сферу, стали доступными всем и каждому, к чему создатели отладчиков ни морально, ни физически, ни технически оказались не готовы.

К счастью, большинство дебагеров поддерживают подключаемые модули, позволяя нам дописать весь необходимый функционал самостоятельно, однако, использование MSR-регистров не ограничивается одной лишь отладкой и, как мы увидим по ходу статьи, они интересы и создателям rootkit'ов, а так же разработчикам самих защит.

ASCII

Рисунок 1 мыщъх (a.k.a.Chuha на Хинди) за работой

Для экспериментов, описанных в статье, нам понадобятся следующие вещи, программы и инструменты:

  1. Windows Driver Kit (WDK) для всех систем по Вислу включительно:
    1. http://www.microsoft.com/whdc/DevTools/default.mspx__ (требует регистрации); - Windows Server 2003 SP1 DDK: - http://www.microsoft.com/whdc/devtools/ddk/default.mspx__;
  2. IA-32 Architecture Software Developer's Manual Vol. 3: System Programming Guide:
  3. KmdManager – утилита динамической загрузки/выгрузки драйверов:
  4. Soft-Ice:
    1. www.google.com__;-) ===== чтение и запись MSR-регистров ===== MSR-регистры имеют свои собственные имена (например, IA32_TIME_STAMP_COUNTER), однако, мыщъх'у не известен ни один ассемблер, «переваривающий» их, а потому приходится использовать «безликие» номера, перечисленные в приложении «MODEL-SPECIFIC REGISTERS» руководства Intel «IA-32 Architecture Software Developer's Manual Volume 3: System Programming Guide», откуда, в частности, можно узнать, что MSR-регистр IA32_TIME_STAMP_COUNTER имеет номер 10h. Чтение MSR-регистров осуществляется машинной командой RDMSR, с предварительной загрузкой номера нужного нам MSR-регистра в ECX. После выполнения команды регистровая пара EDX:EAX содержит исходный результат. В EDX помещаются старшие 32-бита значения MSR, а в EAX, соответственно, идут младшие. За запись отвечает команда WRMSR, ожидающая номер MSR-регистра в ECX, а записываемое значение в регистровой паре EDX:EAX. Пример использования обоих команд показан ниже: MOVECX, 10h ; IA32_TIME_STAMP_COUNTER RDMRD; MOV (EDX:EAX), IA32_TIME_STAMP_COUNTER INCEAX WMSR; MOV IA32_TIME_STAMP_COUNTER, (EDX:EAX) Листинг 1 демонстрация чтения и записи MSR-регистров В защищенном режиме обе команды могут вызываться только из нулевого кольца, иначе операционная система сгенерирует исключение. Другими словами, для экспериментов с MSR-регистрами нам необходимо написать драйвер, «скелет» которого представлен ниже: .686 .MMX .model flat, stdcall extern DbgPrint:PROC .code DriverEntry proc NOP NOP INT 03; soft-ice: I3HERE DRV MOV ECX, 10h; IA32_TIME_STAMP_COUNTER RDMSR; MOV (EDX:EAX), IA32_TIME_STAMP_COUNTER MOVEAX, 666h; LOW 32 bits WRMSR; MOV (EDX:EAX), IA32_TIME_STAMP_COUNTER RDMSR; READ AGAIN - just for fun :-) push offset mystring CALL DbgPrint pop eax mov eax, 0C0000182h; STATUS_DEVICE_CONFIGURATION_ERROR ;RET; Four-F says RETN 8; ← haron says DriverEntry endp .data mystring DB «*] MSR [*»,0Dh,0Ah,0 end DriverEntry Листинг 2 исходный текст «скелета» простейшего драйвера MSR-base.asm для экспериментов с MSR-регистрами Для сборки драйвера нам понадобиться NTDDK и командный файл следующего содержания (для настройки которого необходимо прописать путь к библиотекам ядра): @ECHO OFF SET FILE_NAME=MSR-base REM каждый настраивает эту строку под себя! SET ntoskrnl=D:\NTDDK\libchk\i386\ntoskrnl.lib IF EXIST %FILE_NAME%.obj DEL %FILE_NAME%.obj ml /nologo /c /coff %FILE_NAME%.asm IF NOT EXIST %FILE_NAME%.obj GOTO err link /nologo /driver /base:0x10000 /align:32 /out:%FILE_NAME%.sys /subsystem:native %FILE_NAME%.obj %ntoskrnl% GOTO end :err ECHO -ERR! :end Листинг 3 командный файл для сборки драйвера Остается самая малость — загрузить драйвер внутрь системы, передав управление процедуре DriverEntry, которая, выполнив все, что задумано, завершится с кодом STATUS_DEVICE_CONFIGURATION_ERROR, автоматически выгружая драйвер из системы. Существует множество загрузчиков драйверов (на худой конец можно написать и свой, благо в MSDN входит пример с ZwLoadDriver вместе со строгой рекомендацией не использовать эту функцию в серьезных проектах). Перебрав кучу загрузчиков, мыщъх остановился на KmdManager'е, написанным легендарным хакером Four-F, известным своим циклом статей на WASM'е, откуда можно скачать сам загрузчик вместе с исходными текстами: https://wasm.ru/pub/21/files/kmd3.zip (прилагаемый к статье). Но прежде чем загружать драйвер в систему необходимо установить soft-ice (или syser – это кому что больше нравится) и сказать ему «I3HERE DRV», заставив его отлавливать INT 03h в драйверах. Зачем все это?! А зачем, что наблюдать за командами RDMSR/WRMSR удобнее всего из отладчика, а soft-ice —это классика жанра. Рисунок 2 загрузка драйвера посредством KmdManager'a ОК, soft-ice подготовлен к работе, запускаем KmdManager.exe, указываем путь к драйверу, взводим галочку, расположенную между [Register] и [Run] и нажимаем на [Reg'n'Run] — Soft-ice тут же появляется на экране в точке, где мы заботливо воткнули в драйвер команду INT 03h и теперь можем трассировать драйвер сколько нашей душе угодно, а когда надоест — нажать <CTRL-D> или «x;<ENTER>» для выхода. KmdManager, конечно, обругает нас матом, что драйвер загрузить не удалось, но все идет по плану. Именно так оно и было задумано. Зачем загружать драйвер если все, что нам нужно — выполнить несколько команд на уровне нулевого кольца?! Рисунок 3 наблюдение за MSR-регистрами в soft-ice ===== »> врезка чтение/запись MSR с прикладного уровня ===== Для чтение/записи MSR с прикладного уровня можно воспользоваться недокументированной native-API функцией NtSystemDebugControl(), экспортируемой из динамической библиотеки NTDLL.DLL.Готовый к употреблению пример работы с ней лежит на http://www.openrce.org/blog/view/535/Branch_Tracing_with_Intel_MSR_Registers, однако, для этого необходимо: а) обладать правами администратора; б) в последних пакетах обновления под Server 2003 и XP возможности этой функции были существенно урезаны и, по-видимому, политика урезания продолжится и в дальнейшем, так что все-таки без драйвера не обойтись. ===== перехватываем системные вызовы ===== Для перехода с прикладного уровня в режим ядра операционные системы NT 3.х/4.х и W2K использовали прерывание INT 2Eh, которое в изобилие водится в динамической библиотеке NTDLL.DLL, служащей своеобразными вратами в «ад», тьфу, мостом между прикладными и ядерными уровнями. 77F88278 public ZwCreateFile 77F88278 ZwCreateFileproc near; CODE XREF:LdrLoadAlternateResourceModule+29F↓p 77F88278 77F88278 arg_0= dword ptr 4 77F88278 77F88278moveax, 20h; NtCreateFile 77F8827Dleaedx, [esp+arg_0] 77F88281int2Eh; ←- врата в ад 77F88283retn2Ch 77F88283 ZwCreateFileendp Листинг 4 реализация вызова функции ZwCreateFile на W2K Основной недостаток INT 2Eh в том, что выполняется она очень медленно и потому в P6-процессорах появилась пара более быстрых команд: SYSENTER (с прикладного уровня в режим ядра) и SYSEXIT (из режима ядра назад на прикладной уровень), которую Microsoft стала использовать взамен INT 2Eh начиная с XP и Server 2003, в результате чего NTDLL.DLL оказалась чуть ли не полностью переписаной. В частности, в ней появилась пара процедур быстрого вызова ядерных функций и выхода из них обратно на прикладной уровень: ntdll!KiFastSystemCall: 7C82ED50 8BD4MOVEDX,ESP 7C82ED52 0F34SYSENTER ntdll!KiFastSystemCallRet: 7C82ED54 C3RET Листинг 5 процедуры NTDLL.DLL быстрого вызова ядерных функций с прикладного уровня Прерывание INT 2Eh вопреки распространенному заблуждению не было предано анафеме и вместо морга перекочевало в процедуру KiIntSystemCall: ntdll!KiIntSystemCall: 7C82ED60 8D542408LEAEDX,[ESP+0X8] 7C82ED64 CD2EINT2Eh 7C82ED66 C3RET Листинг 6 процедура вызова ядерных функций через INT 2Eh, оставленная в XP/Server 2003 для совместимости Однако, дизассемблирование показывает, что ни та, и другая процедуры реально не используется и NTDLL.DLL исповедует совершенно другой механизм диспетчеризации системных вызовов. В частности, код той же функции ZwCreateFileв Server 2003 выглядит так (сравните его с листингом 4): 77F42467 public ZwCreateFile 77F42467 ZwCreateFile proc near; CODE XREF: LdrLoadAlternateResourceModule-10C↓p 77F42467moveax, 27h; NtCreateFile 77F4246Cmovedx, 7FFE0300h; offset SharedUserData!SystemCallStub 77F42471calledx 77F42473retn2Ch 77F42473 ZwtCreateFile endp Листинг 7 реализация вызова функции ZwCreateFile на Server 2003 Что находится по адресу 7FFE0300h? Призвав на помощь soft-ice («U 7FFE0300h») мы видим (см. рис. 4), что здесь расположена машинная инструкция SYSENTER, что, собственно говоря, и требовалось доказать. Рисунок 4 SYENTER в SharedUserData!SystemCallStub Теперь немного о сути самой диспетчеризации. В таблице дескрипторов прерываний (IDT) для прерывания INT 2Eh имеется своя запись, указывающая по какому адресу и селектору процессор должен передавать управление при ее выполнении. Естественно, на уровне ядра IDT легко модифицировать, установив свой перехватчик системных вызовов (многие rootkit'ы именно так и поступают), однако, это слишком заметно. Целостность IDT проверяют многочисленные антивирусы и прочие защитные механизмы. Операционные системы семейства XP/Вислаи Server 2003/2008 предоставляют в этом плане намного больше возможностей. Во-первых, мы можем изменить содержимое SharedUserData, заменив SYSENTER переходником на свой собственный обработчик. И хотя этот регион формально недоступен для записи с прикладного уровня, он находится в пользовательской области памяти, которая в отличии от ядра все-таки может быть модифицирована с прикладного уровня, пускай и не без извращений, но… эта тема совсем другого разговора. Задумаемся — а куда SYSENTER передает управление? Ведь целевой адрес нигде явным образом не указан. Курим мануал от Intel и выясняем, что SYSENTER принимает три скрытых аргумента, передаваемые через MSR-регистры. - SYSENTER_CS_MSR (174h): - CS регистр для перехода на уровень нулевого кольца; - SYSENTER_ESP_MSR (175h): - ESP регистр для перехода на уровень нулевого кольца; - SYSENTER_EIP_MSR (176h): - EIP регистр для перехода на уровень нулевого кольца; Селектор стека (регистр SS) получается путем сложения константы 08h со значением MSR-регистра SYSENTER_CS_MSR, так что SYENTER позволяет задавать не только CS:EIP, но и SS:ESP.Таким образом, мы имеем в своем распоряжении все четыре необходимых ингредиента для перехвата, хотя на практике достаточно подменить целевой EIP перенаправив его на код нашего хакерского обработчика (ес-но, расположенного внутри ядра, т. е. представляющего из себя драйвер, хотя чисто теоретически можно заставить SYSENTER вызывать обработчик, находящийся на прикладном уровне, но на этом пути слишком много подводных камней, чтобы принимать его всерьез). Весь фокус в том, что за MSR-регистрами еще мало кто следит и подобный rootkit имеет хорошие шансы остаться незамеченным долгое время (или даже не быть замеченным вообще). Ниже приведен фрагмент кода, считывающего MSR-регистры, связанные с командой SYSENTER. На NT 3.х/4.х и W2K мы получим нули, а XP/Висла/Server 2003/2008 покажут нам адрес диспетчера системных вызовов который можно хакнуть через WRMSR: INT 03; for soft-ice MOV ECX, 174H; SYSENTER_CS_MSR RDMSR; MOV (EDX:EAX), SYSENTER_CS_MSR MOV ECX, 175H; SYSENTER_ESP_MSR RDMSR; MOV (EDX:EAX), SYSENTER_ESP_MSR MOV ECX, 176H; SYSENTER_EIP_MSR RDMSR; MOV (EDX:EAX), SYSENTER_EIP_MSR Листинг 8 фрагмент исходного текста драйвера MSR-SYSENTER.asm, определяющего адрес диспетчера системных вызовов под XP/Висла/Server 200x Загружаем драйвер привычным способом (soft-ice должен быть предварительно запущен) и начинаем трассировать программу, наблюдая за значениями, возвращаемыми в регистровых парах EDX:EAX. Рисунок 5 читаем MSR-регистр номер 174h (SYSENTER_CS_MSR) под W2K и видим в EDX:EAX значение 00000000h, т.к. W2K не использует SYSENTER Рисунок 6 читаем MSR-регистр номер 174h (SYSENTER_CS_MSR) под Server 2003 и видим в EAX значение 08h, совпадающее с ядерным селектором CS, что и требовалось доказать ===== халтурный хронометр или хронометраж наоборот ===== Начиная с P6 в Pentium-процессорах появился специальный счетчик производительности, увеличивающий значение MRS-регистра IA32_TIME_STAMP_COUNTER (10h) на единицу каждый такт (на самом деле, это очень большое допущение, документация от Intel гарантирует просто увеличение, оставляя за собой простор для маневров, но это уже дебри технических деталей, в которые лучше не вдаваться). По умолчанию, прикладным программам даровано право читать его содержимое командой RDTSC, возвращающей текущее значение в регистровой паре EDX:EAX, причем, отладчики типа soft-ice _не_ «замораживают» этот счетчик на время своей работы, что позволяет защитным механизмам легко обнаруживать факт трассировки или срабатывания точек останова (обработка которых требует значительного количества процессорного времени). Во времена IBM XT/AT совместимых компьютеров для той же цели использовался системный таймер (ну, грубо говоря, часы) и вот его-то soft-ice как раз и «замораживал», не позволяя защитам обнаружить себя. А сейчас что?! Рисунок 7 флаг TSD в управляющем регистре CR4 Начнем с того, что команду RDTSC очень легко сделать привилегированной. Для этого достаточно взвести TSD флаг (2й бит) в регистре CR4 (доступном только с нулевого кольца), после чего всякая попытка вызова RDTSC с прикладного уровня заставит процессор генерировать общее исключение защиты, перехватываемое отладчиком и мы (перед возвращением управления программе) можем записать в регистровую пару EDX:EAX все, что нам захочется, а конкретно — создать видимость, что выполнение данного участка кода заняло ничуть не больше времени, чем обычно. Фрагмент кода, который взводит TSD бит приведен ниже: MOVEAX, CR4; читаем управляющий регистр CR4 OREAX, 4; взводим TSD флаг MOVCR4, EAX; обновляем CR4 Листинг 9 делаем команду RDTSC привилегированной инструкцией А как быть, если защита реализована на уровне драйвера и наш запрет на чтение IA32_TIME_STAMP_COUNTERей не помеха?! Тогда можно прибегнуть к прямой записи MSR-регистра IA32_TIME_STAMP_COUNTER командой WRMSR, пример использования которой приведен в листинге 1. Правда, здесь есть одно «но». Процессор (вот сцука) записывает только младшие 32-бита MSR-регистра IA32_TIME_STAMP_COUNTER, а остальные сбрасывает в ноль и защита может разоблачить наши махинации, правда, все известные мыщъх'у защиту с этим не заморачиваются, а просто сравнивают результаты двух замеров и если в обоих старшие 32-бита обнулены, то все ОК. Или не ОК? Прикинем насколько нам хватит младших 32-бит. Возьмем процессор с тактовой частотой в 1 ГГц, увеличивающий IA32_TIME_STAMP_COUNTER на единицу каждый такт, тогда младшие 32-бита переполнятся за 4,294967296 сек. Срок в общем-то не такой уж и большой. Впрочем, учитывая многозадачную природу Windows, защиты осуществляют замеры только на коротких «трассах», выполняющихся сотые или даже тысячные доли секунды, т. е. меньше одного кванта (времени, отпущенного процессу системным планировщиком, после которого происходит переключение на другой поток). В XP длительность кванта составляет ~100 мс, поэтому, результаты замера в ~50 мс уже нельзя считать достоверными — поток мог быть прерван планировщиком. Короче говоря, для борьбы с защитами младших 32-бит вполне хватит. Кстати, интересный факт. При записи IA32_TIME_STAMP_COUNTER под VM Ware, старшие 32-бита MSR-регистра _не_ обнуляются, а в младшие записывается немного большая величина, чем замышлялось (издержки эмуляции), следовательно, анти-анти-отладочный механизм должен работать так: - записать что-то в IA32_TIME_STAMP_COUNTER; - прочитать IA32_TIME_STAMP_COUNTER; - если старшие 32-бита равны нулю, мы на живом процессоре и: - записываем в младшие 32-бита фиктивное значение; - если старшие 32-бита не равны нулю, мы под VM Ware и: - записываем в младшие 32-бита значение с поправкой на эмуляцию Таким образом, ломая программы под VM Ware и корректируя значение IA32_TIME_STAMP_COUNTER должным образом, мы надежно скроем присутствие отладчика от всех защит, основанных на замере временных интервалов. ===== трассируем ветвления ===== Взведите бит TF регистра флагов (E)FLAGS (от там 8й по счету, начиная от нуля) и процессор начнет генерировать отладочное прерывание INT 01h после выполнения каждой инструкции (за исключением инструкций, записывающих сегментный регистр SS) — это еще со времен IBM XT (не путать с XP) всем хакерам хорошо известно. Недостаток такого подхода прежде всего в его чрезвычайной медлительности. Даже с учетом быстродействия современных процессоров, обработка исключений обходится _очень_ дорого и «серьезную» программу мы будет трассировать до конца сезона. А смысл?! К трассировке обычно прибегают для реконструкции логики работы машинного кода, мысленно разбивая его на структурные блоки как-то: ветвления, циклы, etc. В частности, при написании универсальных распаковщиков, автоматически определяющих оригинальную точку входа в программу, нам совершенно незачем исполнять _все_ команды, вполне достаточно после каждого прыжка сравнить целевой код с набором сигнатур стартового кода, внедряемых компиляторами в начало программы. Вот если бы мы могли заставить процессор генерировать отладочные прерывания только после ветвлений — скорость распаковки возросла бы в сотни раз. Или вот еще. Сравнивания два прогона защищенной программы до и после завершения испытательного срока, мы легко найдем тот самый условный переход, после которого все пошло по пути «trial expired». Естественно, полный лог трассировки нам ни к чему. Нас интересуют только ветвления. Вот только так их получить?! Рисунок 8 бит BTF в MSR-регистре MSR_DEBUGCTLA (1D9h) Оказывается, процессор позволяет сделать это! Если бит BTF (1й, считая от нуля) MSR-регистра MSR_DEBUGCTLA (1D9h) взведен, то процессор будет интерпретировать стандартный TF флаг как указание генерировать отладочное прерывание _только_ после встречи с ветвлением или исключением, при этом оба флага (BTF и TF) автоматически очищаются. Проведем следующий эксперимент. Уберем из «скелета» нашего драйвера INT 03h, и поместим туда следующий код, взводящий флаг BTF. MOV ECX, 1D9H; MSR_DEBUGCTLA RDMSR; MOV (EDX:EAX), MSR_DEBUGCTLA OR EAX, 2; SET BTF (single-step on branches) flag (bit 1) WRMSR; MOV (EDX:EAX), MSR_DEBUGCTLA Листинг 10 фрагмент драйвера MSR-branch-trace.asm, взводящего BTF флаг Загрузим драйвер в систему и запустив любой отладчик, например, OllyDbg, нажмем <F7> для трассировки одной инструкции. Опс! Отладчик заносит нас черт знает куда, останавливаясь на первом ветвлении (которым в большинстве случав будет инструкция CALL, вызывающая некоторую API-функцию из стартового кода). Флаг BTF при этом сбрасывается и дальше трассировка проходит нормально (то есть, шаг-за-шагом) во всяком случае пока мы вновь не загрузим наш драйвер, устанавливающий BTF-флагили не напишем plug-in для OllyDbg. К статье прилагается исходный код и откомпилированный модуль MSR-branch-trace.sys, устанавливающий BTF-флаг и тут же проверяющий его значение, выводя результат проверки в отладочный поток, содержимое которого можно просмотреть как с помощью soft-ice, так и утилитой DbgView от Марка Руссиновича. Если взвести BTF-флаг не удается, это означает, что на данной платформе он не поддерживается (на P-III Coppermine поддерживается, на P-4 тоже, а вот под VM Ware 4.5 и 5.2 – увы и ах!). Проверить значение BTF-бита поможет другой драйвер (MSR-branch-trace-x.sys), так же прилагаемый к статье (см. рис. 9). Рисунок 9 проверка состояния BTF-флага драйвером MSR-branch-trace-x.sys ===== заключение ===== На этом наше краткое знакомство с MSR-регистрами не заканчивается и заложенный в них потенциал только начинает раскрываться. Как поется в некогда популярной песне «то ли еще будет ой-ой-ой». Мыщъх сейчас работает над созданием трассера реального времени, который на самом деле никаким трассером не является, а представляет собой своеобразный «дампер» истории выполнения команд, сохраняемой процессоров как в MSR-регистрах, так и в специально отведенной для этих целей области памяти. Мы не только получаем колоссальный выигрыш в производительности, но и полностью скрываем свое присутствие от защитных механизмов и все существующие на данный момент антиотладочные механизмы перестают работать. Но это будет не сейчас. Такая обширная тема требует отдельной статьи, в которой MSR-регистры лишь малая часть огромного отладочного механизма, спрятанного в недрах процессора. Короче, ждите прихода и он будет!!! Рисунок X учиться, учиться и учиться!