Содержание статьи
Ты, конечно, не раз слышал про такую штуку, как аппаратная виртуализация –
технологию, позволяющую запускать несколько гостевых ОС на одной машине (так
называемом хосте). Исторически первой была программная виртуализация, затем
разработчики процессоров призадумались, что неплохо было бы обеспечить
аппаратную поддержку этого дела. Итак, с 2006-го года в нашем распоряжении
оказались процессоры Intel и AMD с возможностями виртуализации. И
пошло-поехало...
Надо сказать, что обзоров и статей, касающихся темы виртуализации, выходило
за эти годы немало. Например, рекомендую к прочтению статью "Технологии
аппаратной виртуализации", представляющую собой довольно основательный обзор
сабжа.
Однако, ни одной статьи, выходящей за рамки аналитики, я так и не встретил.
Пора закрыть этот пробел и сконцентрироваться, наконец, на деталях реализации
гипервизора. Между тем, уже, наверное, каждому известно про
"Голубую пилюлю" (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, также можно
почитать краткий обзор
AMD-V
от производителя.
Теоретические основы
Перед тем, как приступать к написанию кода, необходимо получить минимальную
теоретическую базу. Для начала определимся с терминами и аббревиатурами, которые
будут использоваться далее по тексту.
- Гостевой режим – по аналогии с защищенным, реальным – режим
работы процессора, в котором выполняется гостевая система. - Гость – виртуальная ОС, работающая в гостевом режиме под
управлением гипервизора. - 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 (MSRPM). Каждые 2 бита отвечают за отдельный
MSR.
Когда будет произведена запись/чтение в контролируемый msr – произойдет #VMEXIT,
а подробная информация о событии запишется в поле exitinfo1 VMCB (оно будет рано
0 – если выход спровоцировала rdmsr, и 1, если wrmsr).
4. Инструкции работы с портами. Как и в случае с msr-регистрами, за контроль
доступа к портам отвечает карта разрешения ввода-вывода (IOPM, I/O Permission
Map). Там, конечно, все чуток сложнее, чем с msr. После #VMEXIT информация об
исключении будет записана в поле exitinfo1, где будет содержаться информация об
инструкции, которая вызвала исключение.
Формат поля exitinfo1 в VMCB для перехваченных инструкций ввода-вывода
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
С 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").
Схема работы виртуализации 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
MSR-регистр EFER. У него полно различных функций помимо включения аппаратной
виртуализации.
Кстати, установка бита SVME может быть заблокирована, поэтому после того, как
мы записали msr, нужно снова прочитать его содержимое и проверить – установился
ли заветный бит.
За блокировку инструкций виртуализации отвечают msr-регистры – VM_CR и
SVM_KEY (опционально). Бит SVMDIS, который четвертый в VM_CR, запрещает
установку EFER.SVME, а LOCK (бит три) в том же регистре запрещает сброс SVMDIS и
LOCK (получается, что LOCK – это защита для защиты). LOCK можно сбросить либо
после перезагрузки, либо указать ключ в машинно-зависимом регистре SVM_KEY (если
этот ключ был установлен перед блокированием виртуализации). Сама возможность
блокировки, к слову, появилась в AMD-V не сразу, а только со второй ревизии
(специально для параноиков:)).
MSR-регистр VM_CR, позволяющий заблокировать установку бита SVME в EFER.
Заключение
Первый теоретический рубеж преодолен. Изложение получилось несколько
сумбурным, но, я думаю, это тебе не помешало уловить суть. Осталось реализовать
полученные знания на практике, что мы и сделаем в последующих статьях. Если у
тебя есть какие-то замечания или вопросы – пиши мне на мыло, постараюсь
ответить.
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
discovery.csc.ncsu.edu/pubs/ccs09-HookSafe.pdf.