Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #133, ЯНВАРЬ 2010 г.

Свой гипервизор ближе к телу! Аппаратная виртуализация на практике

r0064 (r0064@mail.ru)


Ты, конечно, не раз слышал про такую штуку, как аппаратная виртуализация – технологию, позволяющую запускать несколько гостевых ОС на одной машине (так называемом хосте). Исторически первой была программная виртуализация, затем разработчики процессоров призадумались, что неплохо было бы обеспечить аппаратную поддержку этого дела. Итак, с 2006-го года в нашем распоряжении оказались процессоры Intel и AMD с возможностями виртуализации. И пошло-поехало...

Надо сказать, что обзоров и статей, касающихся темы виртуализации, выходило за эти годы немало. Например, рекомендую к прочтению статью «Технологии аппаратной виртуализации», представляющую собой довольно основательный обзор сабжа: ixbt.com/cm/virtualization-h.shtml.

Однако, ни одной статьи, выходящей за рамки аналитики, я так и не встретил. Пора закрыть этот пробел и сконцентрироваться, наконец, на деталях реализации гипервизора. Между тем, уже, наверное, каждому известно про «Голубую пилюлю» (Blue Pill) Рутковской, в которой возможности виртуализации успешно применяются, причем не в самых легитимных целях :). Пока кто-то создает экспериментальные руткиты на базе виртуализации, кто-то активно разрабатывает анти-руткиты. Так, Hypersight Rootkit Detector от North Security Labs – считай готовый анти-руткит для Windows. Для Linux тоже существует подобный проект – HookSafe, который был представлен на конференции ACM по компьютерной и сетевой безопасности (CCS 2009).

Такой интерес к виртуализации далеко не случаен – ведь для хакера это уникальная возможность скрытия своего присутствия в системе (наряду с более сложными, но и не менее красивыми, атаками на SMM и AMT). Конечно, чтобы сделать руткит (даже на базе виртуализации) на 100% недетектируемым придется напрячься, но игра стоит свеч! В конце концов, мы получает возможность тотального контроля над системой. Заинтересовало? Тогда вперед, осваивать такую непростую штуку как программирование гипервизоров.

Прежде чем зарываться в документацию, нужно обрисовать задачу более подробно. Вообще, аппаратная виртуализация в процессорах Intel (именуемая Intel VT) отличается от аналогичной у AMD (AMD-V). Отличается – значит, код гипервизора (aka VMM – Virtual Machine Monitor) для AMD не будет работать на платформе Intel. Поэтому мы начнем с AMD и продолжим о Intel в последующих статьях. Чтобы ты мог уточнить для себя какие-то вещи, тебе потребуется дока от амд (смотри врезку), также можно почитать краткий обзор AMD-V от производителя (amd64.ru/index.php?link=2&addr=6&page=8).

Теоретические основы

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

  • Гостевой режим – по аналогии с защищенным, реальным – режим работы процессора, в котором выполняется гостевая система.
  • Гость – виртуальная ОС, работающая в гостевом режиме под управлением гипервизора
  • VMM (монитор виртуальных машин, гипервизор) – программное обеспечение, перехватывающее события в госте. Гипервизор представляет собой рычаг управления гостевыми системами.
  • Хост (по отношению к гостю)– система, на которой запущен гипервизор.
  • #VMEXIT – переход из режима гостя в режим хоста.

Подробности

Ты - обладатель процессора AMD. Как узнать, есть ли в нем поддержка аппаратной виртуализации? О том, что мы имеем соответствующий функционал, рапортует функция 80000001h инструкции CPUID (второй бит от нуля в регистре ecx, именуемый SVM, должен быть установлен). Функция, возвращающая 0 или 1,если возможности виртуализации недоступны или доступны, соответственно.

IsSVMAvailableProc proc
xor rax,rax
mov eax,80000001h
cpuid
xor rax,rax
bt ecx,2 ; проверяем бит SVM
jnc if_zero ; прыгаем, если бит равен 0
inc rax
if_zero:
ret
IsSVMAvailableProc endp

Убедившись, что в нашем распоряжении подходящий процессор, можно приступать к дальнейшему описанию. Забегая вперед, скажу, что включение виртуализации, как и, в общем-то, весь код нашего гипервизора, будет находиться в драйвере и работать в ring-0. В процессе освоения кодинга гипервизора от тебя потребуется представление о программировании драйверов под Windows, знание с/с++ и 64-битного ассемблера на базовом уровне. Хотя я все равно постараюсь объяснить все максимально подробно.

Инструкции управления виртуальными машинами

По своей сути аппаратная виртуализации – это расширение архитектуры ЦП: набор инструкций + новый режим работы процессора. До того, как говорить о наборе команд, нужно разобраться с такой штукой, как VMCB. VMCB (Virtual Machine Control Block) – управляющий блок виртуальной машины. Это основная структура данных, с которой нам предстоит работать. VMCB описывает виртуальную машину, которую мы будем запускать. Сразу о технических деталях: VMCB занимает одну страницу (4 килобайта) в непрерывной физической памяти. VMCB состоит из 2-х частей – область флагов (control area) и область состояния (state-save area). Рассмотрим структуру VMCB подробнее. VMM, как уже упоминалось, перехватывает события, происходящие в госте. Какие это будут события – определяется в области флагов. Мы можем перехватывать:

  1. Чтение/запись контрольных регистров (cr0-cr15). Первые 16 бит структуры VMCB как раз и отводятся на установку перехвата операции чтения для каждого из контрольных регистров. Каждый бит отвечает за свой контрольный регистр. Вторые 16 бит VMCB отвечают за операцию записи в контрольные регистры.
  2. Чтение/запись отладочных регистров (dr0-15).
  3. Инструкции rdmsr/wrmsr для выбранных msr-регистров. Чтобы определить, какие msr подлежат контролю, используется так называемая MSR Permission Map (в переводе - карта разрешения msr, сокращенно - MSRPM). На каждый msr в ней отводится по 2 бита – для контроля операции чтения и записи. Физический адрес начала MSRPM хранится в VMCB. Когда будет произведена запись/чтение в контролируемый msr – произойдет #VMEXIT, а подробная информация о событии запишется в поле exitinfo1 VMCB (оно будет рано 0 – если выход спровоцировала rdmsr, и 1, если wrmsr).
  4. Инструкции работы с портами. Как и в случае с msr-регистрами, за контроль доступа к портам отвечает карта разрешения ввода-вывода (IOPM, I/O Permission Map). Там, конечно, все чуток сложнее, чем с msr. После #VMEXIT информация об исключении будет записана в поле exitinfo1, где будет содержаться информация об инструкции, которая вызвала исключение.
  5. Инструкции чтения/записи регистров ldtr,gdtr, tr,idtr.
  6. Исключения (0-31 векторы в IDT).
  7. Инструкции, отвечающие за аппаратную виртуализацию (VMRUN,VMSAVE,VMLOAD…). То есть, можно контролировать запуск других гипервизоров (они будут вложенными). Кстати, с помощью перехвата этих инструкций Hypersight Rootkit Detector и обнаруживает «Голубую пилюлю».
  8. Сигналы SMI, NMI, INIT …
  9. Еще много различных инструкций, таких как cpuid, iret, rsm и т.п.

Все вышеперечисленные события – это условия #VMEXIT – возвращения из гостевого режима в режим хоста. Каждая причина #VMEXIT имеет свой код, который записывается в поле exitcode области флагов VMCB. Вот некоторые из этих кодов:

  • 62h – физическое SMI
  • 6Eh – произошла инструкция RDTSC
  • 70h – команда PUSHF
  • 71h – POPF
  • 72h – CPUID
  • 7F – гость выключился (Shutdown)
  • 80h – VMRUN
  • 81h - VMMCALL
  • 82h – VMLOAD
  • 83h - VMSAVE
  • 88h – ICEBP (инструкция с опкодом 0xF1)
  • -1 – неверная VMCB

Полную таблицу #VMEXIT-тов можно посмотреть в Appendix C. SVM Intercept Exit Codes в уже упоминаемом мной AMD64 Architecture Programmer's Manual Volume 2.

Часть дефиниции cтруктуры VMCB (из сорцов Xen):

struct vmcb_struct {
// область флагов
// первое слово – перехват чтения cr0-15
// второе слово – перехват записи cr0-15
u32 cr_intercepts; /* offset 0x00 */
// первое слово – перехват чтения dr0-15
// второе слово – перехват записи dr0-15
u32 dr_intercepts; /* offset 0x04 */
// поле установки перехватываемых исключений (векторы 0-31 в IDT)
u32 exception_intercepts; /* offset 0x08 */
// INTR, NMI, SMI....IDTR (запись/чтение), GDTR (запись/чтение), LDTR(запись/чтение)
// TR(запись/чтение), инструкции RDTSC, RDPMC, PUSHF, POPF …
u32 general1_intercepts; /* offset 0x0C */
u32 general2_intercepts; /* offset 0x10 */
…
…
// физический адрес карты разрешения ввода-вывода
u64 iopm_base_pa; /* offset 0x40 */
// физический адрес карты разрешения msr
u64 msrpm_base_pa; /* offset 0x48 */
// это поле нужно для команды rdtsc
u64 tsc_offset; /* offset 0x50 */
// идентификатор адресного пространства гостя, связано со сбросом TLB, пока это не нужно
u32 guest_asid; /* offset 0x58 */
u8 tlb_control; /* offset 0x5C */
u8 res07[3];
vintr_t vintr; /* offset 0x60 */
u64 interrupt_shadow; /* offset 0x68 */
// после #VMEXIT здесь окажется код причины выхода
u64 exitcode; /* offset 0x70 */
u64 exitinfo1; /* offset 0x78 */
u64 exitinfo2; /* offset 0x80 */
…
eventinj_t eventinj; /* offset 0xA8 */
// используется для вложенного страничного преобразования (nested paging) – об этом расскажу в
//другой раз
u64 h_cr3; /* offset 0xB0 */
lbrctrl_t lbr_control; /* offset 0xB8 */
// оставшееся место – 832 байта - заполняется нулями – оно зарезервировано для дальнейшего //расширения
u64 res09[104]; /* offset 0xC0 pad to save area */
…

Все неиспользуемое пространство обязательно должно быть заполнено нулями. Вторая часть VMCB содержит состояние регистров гостя. Из этой области во время выполнения инструкции VMRUN (о ней скажу позже) загружается информация о состоянии гостя, а при выходе из гостевого режима она (информация о состоянии) сохраняется там же.

// начало области состояния
svm_segment_register_t es; /* offset 1024 */
svm_segment_register_t cs;
svm_segment_register_t ss;
svm_segment_register_t ds;
svm_segment_register_t fs;
svm_segment_register_t gs;
svm_segment_register_t gdtr;
svm_segment_register_t ldtr;
svm_segment_register_t idtr;
svm_segment_register_t tr;
…
…
u64 efer; /* offset 1024 + 0xD0 */
u64 res13[14];
u64 cr4; /* loffset 1024 + 0x148 */
u64 cr3;
u64 cr0;
u64 dr7;
u64 dr6;
u64 rflags;
u64 rip;
u64 res14[11];
u64 rsp;
u64 res15[3];
u64 rax;
u64 star;
u64 lstar;
u64 cstar;
u64 sfmask;
u64 kerngsbase;
u64 sysenter_cs;
u64 sysenter_esp;
u64 sysenter_eip;
u64 cr2;
…
…
// регистры, связанные с трассировкой ветвлений
u64 debugctlmsr;
u64 lastbranchfromip;
u64 lastbranchtoip;
u64 lastintfromip;
u64 lastinttoip;
u64 res16[301]; // далее просто 2408 нулевых байт
}

С VMCB кое-как разобрались. Теперь можно переходить к описанию инструкций.

VMRUN (опкод команды - 0Fh, 01h, 0D8h) – инструкция запуска виртуальной машины. Это основная и самая важная команда в аппаратной виртуализации. VMRUN принимает в качестве аргумента в регистре rax физический адрес управляющего блока виртуальной машины (VMCB), который описывает состояние виртуальной машины. VMRUN доступна только с нулевого кольца (вообще, с третьего кольца из инструкций, составляющих сабжевое расширение архитектуры процессора, доступна только VMMCALL).

Гипервизор настраивает структуру VMCB, устанавливает в ней перехватываемые инструкции, прерывания и т.д. Переход в режим гостя происходит посредством инструкции VMRUN. Состояние хоста сохраняется в области памяти, на которую указывает содержимое msr регистра VM_HSAVE_PA (PA – Physical Address, то есть здесь мы опять имеем дело с физическим адресом этого региона). В этой области памяти сохраняется минимальная информация, необходимая для возобновления работы хоста после выхода из гостя (регистры cs,rip, efer, cr0, cr3 …). Теперь, когда виртуальная машина успешно запущена, мы вернемся в режим хоста только при возникновении перехваченного гипервизором события (условия #VMEXIT). После #VMEXIT будет выполнена следующая за VMRUN инструкция в гипервизоре. Специально для тебя я сделал обобщающую схему вышеописанного (смотри картинку «Схема работы виртуализации AMD-V»).

Две инструкции VMSAVE (0Fh, 01h, 0DBh) и VMLOAD (0Fh, 01h, 0DAh) дополняют VMRUN и служат для сохранения/загрузки части VMCB.
VMMCALL (0Fh, 01h, 0D9h) – инструкция, позволяющая из гостевого режима перейти в хост. Доступна как на нулевом, так и на третьем кольце. Правда, я лично не понимаю смысла в этой инструкции. Если она не перехватывается, то возникает #UD. То есть, безусловного вызова гипервизора не происходит. Можно было бы, наверное, не вводить дополнительную инструкцию, использовать ту же CPUID (или другую, которую можно перехватить).

Включение возможностей аппаратной виртуализации

Все инструкции работы с аппаратной виртуализацией (за исключением SKINIT, там особый случай) требуют установки бита SVME (он 12-й) в регистре EFER (иначе мы получим исключение #UD – неверная инструкция). Что это за регистр – EFER? Расшифровывается аббревиатура как Extended Feature Enable Register – это msr, который отвечает за включение дополнительных возможностей проца (что видно из расшифровки), и он имеет адрес 0C0000080h. Приведенный ниже код включает возможности аппаратной виртуализации:

sub rcx,rcx
mov ecx, 0C0000080h ; адрес EFER
rdmsr ; читаем EFER
bts eax,12
wrmsr

Кстати, установка бита SVME может быть заблокирована, поэтому после того, как мы записали msr, нужно снова прочитать его содержимое и проверить – установился ли заветный бит.

За блокировку инструкций виртуализации отвечают msr-регистры – VM_CR и SVM_KEY (опционально). Бит SVMDIS, который четвертый в VM_CR, запрещает установку EFER.SVME, а LOCK (бит три) в том же регистре запрещает сброс SVMDIS и LOCK (получается, что LOCK – это защита для защиты). LOCK можно сбросить либо после перезагрузки, либо указать ключ в машинно-зависимом регистре SVM_KEY (если этот ключ был установлен перед блокированием виртуализации). Сама возможность блокировки, к слову, появилась в AMD-V не сразу, а только со второй ревизии (специально для параноиков:)).

Заключение

Первый теоретический рубеж преодолен. Изложение получилось несколько сумбурным, но, я думаю, это тебе не помешало уловить суть. Осталось реализовать полученные знания на практике, что мы и сделаем в последующих статьях. Если у тебя есть какие-то замечания или вопросы – пиши мне на мыло, постараюсь ответить.

WWW

AMD64 Architecture Programmer's Manual Volume 2: System Programming:amd.com/us-en/assets/content_type/white_papers_and_tech_docs/24593.pdf. Теме виртуализации в этом мануале посвящена глава 15, Secure Virtual Machine.

Hypersight Rootkit Detector (для Windows) – анти-руткит на основе аппаратной виртуализации. Фраза на главной странице «Blue Pill перестал быть невидимым» – заставляет познакомиться с сабжем поближе: northsecuritylabs.com/ru.

Проект Blue Pill Джоанны Рутковской – руткит, использующий аппаратную виртуализацию (опенсорс): bluepillproject.org.

В качестве дополнительной литературы можно почитать также ман AMD, целиком и полностью посвященный CPUID. CPUID Specification: amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25481.pdf.

Проект Xen:xen.org/products/projects.html.

HookSafe – не так давно появившийся анти-руткит на основе гипервизора (для Linux). Исследователи работают над версией для Windows: http://discovery.csc.ncsu.edu/pubs/ccs09-HookSafe.pdf.

Содержание