
Крутой протектор – не беда
Ms-Rem (Ms-Rem@yandex.ru)
Спецвыпуск: Хакер, номер #057, стр. 057-056-1
Технологии взлома сложных программных защит
Как ломаются протекторы? Находим OEP, снимаем дамп, восстанавливаем импорт – все. И это почти стандарт. Конечно, ты уже хорошо освоил это и умеешь распаковывать всякие аспры с закрытыми глазами. Инструменты, которыми ты пользуешься - это SoftIce, IceExt, IDA, OllyDbg, PeTools, LordPE, ImpRec, PEiD. Для снятия большинства протекторов их вполне достаточно. Но каждый крэкер когда-нибудь сталкивается с такой защитой, перед которой все инструменты пасуют. В этой статье я постараюсь рассказать, почему происходит так и что делать в таком случае. Опишу трюки, которые выделывают в самых сложных защитах и, главное, объясню, как обходить их.
Действительно крутых защит в мире немного. Это не ASProtect, не Armadillo, не SVKP, не EXE Stealth, не MoleBox и уж точно не какой-нибудь упаковщик или UPX Scrambler. Справляться со всем этим ты уже, несомненно, научился. Я говорю о защитах уровня hi-end, против которых бессильны стандартные методы и подходы. О StarForce, XtremeProtector (Themida), ExeCryptor и о тех, что привязывают софт к аппаратным ключам (Hasp Envelope, Guardant и т.п.). Об этих защитах и об используемых ими приемах и поговорим.
Все протекторы можно разделить на два класса: это Ring3-протекторы, код которых исполняется только в третьем кольце защиты процессора, и Ring0-протекторы, которые имеют драйверы режима ядра, позволяющие влиять на работу ОС и многократно расширяющие список применяемых защитных приемов. Из hi-end протекторов единственным работающим в Ring3 остается ExeCryptor. Это делает его в некотором роде уникальным, но принципы его работы имеют массу неприятных недостатков. Из новых приемов защиты в hi-end протекторах можно выделить: влияние на работу операционной системы (на API-функции, в частности), применение псевдокода, исполняемого на виртуальных машинах, самомодифицирующийся и перекрывающийся код, недокументированные инструкции процессора (дизассемблирующиеся неправильно), масса размытого и мусорного кода, блокировка отладочных возможностей процессора. И всем этим арсенал hi-end протекторов, естественно, не исчерпывается. Теперь подробнее.
Ring0-протекторы
К этому классу относится большинство hi-end протекторов. Обычно ни отладчики, ни различные дамперы ничего не могут сделать с ними – в этом главная неприятность этих протекторов. Попробуем, например, сдампить с помощью PeTools программу, защищенную xprot'ом. Очень странно, но защищенный процесс отображается в PeTools как [System Idle Process], поэтому заглянуть внутрь и сдампить его у нас не получается. В OllyDbg при попытке приаттачиться к процессу мы не вообще обнаруживаем его в списке (OllyDbg, как и любые другие Ring3-отладчики, непригоден для анализа Ring0-протекторов, так что в дальнейшем мы будем использовать только SoftIce). Большинство начинающих крэкеров, видя такую ситуацию, сразу же бросают ломать программу, считая, что она безнадежна. Но мы не маленькие - нас такими трюками не испугаешь. Если немного подумать головой, то можно прийти к выводу, что, скорее всего, перехвачена одна из функций, имеющих дело с памятью процессов. Для дамперов очень важна работоспособность Native API функций ZwOpenProcess, ZwReadVirtualMemory и ZwWriteWirtualMemory, так как на них построено большинство высокоуровневых функций работы с процессами, в число которых входят OpenProcess, ReadProcessMemory, WriteProcessMemory и EnumProcessModules. Возможен, конечно, перехват этих функций в третьем кольце, но это просто глупо (так как его легко обойти), и я такого еще ни в одной защите не встречал, поэтому лезем сразу в ядро системы. Только для начала следует уяснить, как работает интерфейс системных вызовов Windows NT.
Из схемы, которая расположилась где-то рядом, следует, что вызов функции ядра до его передачи соответствующей Native API проходит довольно сложную обработку. Сначала, в третьем кольце, вызывается соответствующая функция библиотеки ntdll, где в регистр EAX помещается номер вызываемого системного сервиса, а в регистр EDX - указатель на передаваемые параметры. Затем вызывается прерывание 2Eh (в Windows XP - команда sysenter), и происходит переход процесса в нулевое кольцо, где управление передается согласно записанному в IDT шлюзу прерывания. В этом месте происходит переключение окружения третьего кольца на нулевое. Выполняется смена пользовательского стека на стек ядра. Также осуществляется перезагрузка сегментного регистра FS, который в нулевом кольце указывает на совершенно иные структуры, чем в третьем кольце. Затем управление передается обработчику прерывания 2Eh - функции ядра KiSystemService. Она копирует в стек ядра передаваемые системному сервису параметры и производит вызов Native API функции ядра, согласно содержимому ServiceDescriptorTable (SDT). Эта таблица находится в памяти ядра и представляет собой структуру, содержащую четыре таблицы системных сервисов (SST). Первая из этих таблиц описывает сервисы, экспортируемые ядром (ntoskrnl.exe), вторая - графической подсистемой (win32k.sys), а остальные две зарезервированы на будущее и сейчас не используются.
Самый простой способ перехвата Native API в ядре - замена адреса обработчика нужной функции в SDT на "свой". При вызове перехваченной функции происходит проверка параметров, и если производится (например как в протекторе) попытка чтения памяти защищенного процесса, то возвращается отказ в доступе, иначе просто вызывается оригинальный обработчик функции. Просмотреть содержимое SDT мы можем командой SoftIce'а NTCALL, при этом отладчик выводит номер функции, ее имя (если установлены отладочные символы), адрес и имя функции обработчика. Если вместо Ntoskrnl!NtOpenProcess мы видим что-то типа Xprot!.text + 1234h, то функция перехвачена защитой.
Однако патчинг SDT - не единственный способ перехвата Native API. Также может быть перехвачено прерывание int 2Eh в win2k (о чем можно узнать командой SoftIce'а IDT) либо изменен обработчик системного вызова через sysenter в winxp и выше. Установка\снятие обработчика прерывания int 2Eh будет выглядеть так:
void Set2kSyscallHook()
{
TIdt Idt;
__asm
{
cli
sidt [Idt]
mov esi, NewSyscall
mov ebx, Idt.Base
xchg [ebx + 0x170], si
rol esi, 0x10
xchg [ebx + 0x176], si
ror esi, 0x10
mov OldSyscall, esi
sti
}
}
Для многопроцессорных систем, в том числе для процессоров Hyper Threading, этот код существенно усложняется, но рассматривать его сейчас не будем. Если интересно, залезь в исходники IceExt.
Адрес обработчика sysenter вызова определяется содержимым 32-битного MSR-регистра с номером 176h. Читать или записывать в такие регистры мы можем с помощью команд RDMSR/WRMSR, предварительно поместив в ECX номер регистра, а EAX используя как источник или приемник нового значения. Код, перехватывающий обработчик sysenter, будет выглядеть примерно так:
void SetXpSyscallHook()
{
__asm
{
mov ecx, 0x176
rdmsr
mov OldSyscall, eax
mov eax, NewSyscall
wrmsr
}
}
Перехват также может быть осуществлен методом сплайсинга (замена участка кода перехватываемой функции), что легко обнаружить при трассировке перехваченного участка.
Что ж, с методами перехвата разобрались. Осталось научиться бороться с ними. Тут все очень просто. Нужно определить адреса оригинальных обработчиков и самостоятельно пропатчить SDT, убрав перехват, либо заменить участок кода, измененный при сплайсинге, аналогичным участком из оригинального файла ядра системы (ntoskrnl.exe). При этом ты можешь столкнуться с проверкой наличия перехвата протектором, и если ты не знаешь, как бороться с такими вещами, значит, тебе рано браться за ring0-проекторы - потренируйся лучше на аспаке.
Второе, с чем ты обязательно столкнешься в ring0-протекторах - это перехват отладочных прерываний (int 1 и int 3). Этот прием не дает нам трассировать код в отладчике и ставить бряки. Реакция защит на срабатывание этих прерываний банальна - синий экран! Нужно что-то делать с этим, если ты хочешь пользоваться отладчиком.
Если отладочные прерывания просто заблокированы, но не несут при этом никакой смысловой нагрузки, то можно восстановить их оригинальные векторы, отключить проверки целостности перехватов (если они будут) и радоваться жизни. Но, к сожалению, в современных протекторах отладочные прерывания не просто вырублены, а используются для какой-либо работы, без которой защищенная программа функционировать не будет. Здесь есть три варианта: 1) перевести протектор на другие прерывания; 2) перевести отладчик на другие прерывания; 3) заставить и протектор, и отладчик работать на общих прерываниях.
В первом случае нужно разобраться в том, как протектор использует отладочные прерывания, и попробовать заменить их другими, не конфликтующими с отладчиком. Например, если в коде протектора встречается int 1, то заменим его на int 20 и модифицируем вектор 20 прерывания так, чтобы он указывал на обработчик протектора, после этого int 1 можно будет использовать для отладчика.
Второй подход несколько сложнее, так как аппаратная трассировка и точки останова работают только на стандартных отладочных прерываниях. Поэтому нам придется писать плагин к SoftIce'у, который будет вешать всплытие отладчика на свободное прерывание (например 20), а при установке бряка будет пихать в код не CC, а СD20.
Третий подход предполагает написание драйвера, который будет перехватывать отладочные прерывания сам и передавать их отладчику, после чего ты решишь, передавать прерывание защите или нет. Естественно, это тоже потребует написания плагина к SoftIce'у. Я обычно придерживаюсь этого подхода, он особенно удобен при взломе Star Force, который использует int 3 для вызова своей виртуальной машины в ring0. Этот вызов легко отделяется от срабатывания точки останова, что позволяет легко распределять возникающие прерывания между защитой и отладчиком.
А вообще это еще цветочки. В ring0-протекторах можно встретить кучу других более опасных приемов. Например, попробуй трассировать ring0-код при ESP = 0 - получишь синий экран. Это связано с тем, что при возникновении отладочного прерывания адрес возврата заносится в ring0-стек потока, а если по этому адресу не оказывается памяти, то возникает исключение.
Или, например, можно встретить отключение аппаратных прерываний с помощью перепрограммирования чипсетного контроллера прерываний. Если отладчик всплывает при вырубленных прерываниях, то остается только нажать reset, так как компьютер зависнет намертво. Бороться с этим можно опять же написанием драйвера, который включает прерывания перед вызовом отладчика.
К сожалению, такими "сюрпризами" богаты все современные ring0-протекторы, поэтому нужно уметь вовремя распознать и обойти их. Арсенал антиотладочных приемов в ring0 постоянно расширяется, и от них спасет только хорошее знание защищенного режима работы процессора и внимательный анализ кода протектора.
Если захочешь потренироваться во взломе ring0-протекторов, я написал небольшой crackme, который использует все описанные здесь приемы. Распаковывается он просто, но код, проверяющий серийник, спрятан с помощью ring0-штучек. Закейгенить этот пример пока еще никто не смог, так что у тебя есть шанс стать первым. Сrackme найдешь на диске с журналом.
Метаморф и полиморф
Вот пример легко понятного кода:
push eax
push 0
push 0
push ebx
call MessageBoxA
А если вместо этой гениальной простоты ты встречаешь путаницу вроде
xchg [edi], dl
add al, 30h
xlat
call 1234h
call 3456h
or al, 4a
знай, что ты встретился с метаморфом или полиморфом в защите. Полиморф обычно просто добавляет в код мусорные инструкции, чтобы затруднить дизассемблирование и анализ кода, а метаморф старается целиком изменить вид кода, сохраняя при этом оригинальный алгоритм его работы, для чего он заменяет инструкции их синонимами, состоящими в свою очередь из одной или нескольких других инструкций. Большая часть нового кода, производимого метаморфом, обычно нужна для работы программы, доля мусорного кода у него весьма мала. У полиморфа все наоборот. Естественно, декодировать метаморф значительно сложнее. Прежде чем делать это, нужно разобраться, как он работает.
Сердцем любого метаморфа обязательно является дизассемблер. С его помощью происходит разделение защищаемого кода, после чего, как уже говорилось выше, производится замена всех инструкций на синонимы или небольшие куски кода, несущие тот же смысл. Причем замена может делаться неоднократно. Число проходов (циклов замены) морфера называется глубиной морфинга. Чем она больше, тем более запутанным будет выходной код. После морфинга инструкции компилируются обратно в машинный код. Авторы метаморфов считают, что большая глубина морфинга осложнит анализ кода, но мой опыт подсказывает мне обратное. Сложность декодирования метаморфа целиком зависит от первичного алгоритма морфера и от того, сколько комбинаций инструкций он способен выдать на одну оригинальную инструкцию. Увеличение же числа этих комбинаций повышением глубины морфинга ничего хорошего не дает: все равно после написания анализатора можно будет снять метаморф в несколько проходов так же, как он и накладывался.
Для начала рассмотрим простой полиморф и подумаем, как декодировать его. В качестве подопытной программы я взял написанный на ассемблере Hello World и защитил его с помощью DotFix FakeSigner 2.8. В IDA получившийся код выглядит так:
jmp short loc_902003
db 0D8h
jmp short loc_902006
db 0BFh
finit
fprem
inc eax
dec eax
inc eax
dec eax
lea ebx, [ebx+0]
jmp short loc_902015
db 0F6h
xor edx, edx
jmp short loc_902026;
Здесь мы видим пример использования простейшего полиморфа. Инструкции программы разносятся на разное расстояние, соединяются с помощью jmp, а промежутки заполняются мусорными инструкциями. В данном случае весь приведенный код представляет собой мусор, о чем нетрудно догадаться, однако начинающих крэкеров это может сбить с толку. Еще в этом полиморфе можно встретить редкие SEH, но автор даже не пытался их замаскировать - их не увидит только слепой. Для декодирования этого полиморфа необязательно использовать полноценный дизассемблер - достаточно дизассемблера длин и таблицы мусорных шаблонов. Алгоритм такой: проходим дизассемблером длин по инструкциям программы, переходим по jmp и собираем весь код (кроме jmp) где-нибудь в памяти. После этого по шаблонам удаляем мусорные инструкции. Вот и все - полиморф декодирован. FakeSigner, кстати, предназначен для затруднения определения упаковщика, которым сжат исполняемый файл, и, по идее, нельзя определить наличие самого дотфикса. К сожалению, код этого полиморфа настолько специфичен, что написать программу, определяющую его, будет проще простого. Если хочешь потренироваться в разборе полиморфов, рекомендую начать именно с этого.
Несомненно, при взломе сложных защит тебе придется столкнуться с метаморфом, а это штука даже неприятнее, чем простой полиморф. Для декодирования метаморфа нужно написать сложный анализатор, который содержит в себе дизассемблер, разбирающий инструкции, и деморфер, реализующий алгоритм, обратный морферу. Простейший алгоритм разбора метаморфа состоит в анализе близкорасположенных инструкций и определении того, как они в целом повлияют на регистры и память. После этого преобразование повторяется до получения кода наименьшего размера. К примеру, группу add eax, 5/sub eax, 4 можно превратить в inc eax. Этот метод называется многопроходной оптимизацией и применяется для реверсинга простых метаморфов. В более сложных случаях зависимые команды могут быть отделены кучей мусора или перемешаны с кодом протектора. И тут на помощь придет только частичная эмуляция участков кода на виртуальной машине и формирование кода на основе изменений состояния ВМ на определенных отрезках кода.
Формируется таблица измененных состояний и зависимостей между ними, после чего производится перегруппировка элементов таблицы таким образом, чтобы независимые элементы находились в одном месте. Далее к таблице применяется метод многопроходной оптимизации, который значительно сокращает число ее элементов. По сокращенной таблице строится цепочка инструкций, соответствующая изменениям, записанным в ней, и эти инструкции компилируются в машинный код. Это один из алгоритмов, которые можно использовать при анализе действительно сложного метаморфа. Можно поступить и по-другому: писать не эмулятор, а трассировщик, анализирующий путь исполнения программы и строящий таблицу изменений на основе этих данных. Как показывает практика, написать полноценный эмулятор чрезвычайно сложно, поэтому приходится комбинировать эти способы. В некоторых полиморфах и метаморфах можно встретить весьма хитрые методы запутывания кода, связанные с нестандартными опкодами инструкций. Большинство современных дизассемблеров и отладчиков могут неправильно дизассемблировать код, содержащий множественные префиксы либо неверное значение в поле Reg байта MOD r/m для Extended группы опкодов. В качестве примера можешь взять байты C7 F0 34 12. Большинство дизассемблеров (IDA в том числе) дизассемблируют это как mov eax, 1234h, а на самом же деле это INVALID OPCODE. Такие инструкции могут использоваться в защите для неявного вызова обработчика SEH, поэтому при создании дизассемблера следует учесть их. Для анализа таких инструкций в качестве образцового можно использовать мой дизассемблер, который лежит диске (если найдешь в моем детище ошибку, обязательно сообщи мне).
Качественный метаморф ты можешь найти в ExeCryptor'е. Он имеет большой разброс генерируемых инструкций, самомодифицирующийся код и много SEH. Все это приправлено немалой глубиной морфинга. Декодировать его сложно, но в этом нет ничего невозможного. Попробуй.
Анализ виртуальных машин
В самых крутых защитах (в частности StarForce 3 Pro) можно встретить следующий прием: часть кода защищаемой программы и самой защиты переводится в псевдокод и исполняется на виртуальной машине. Для полного снятия такой защиты нужно восстановить этот код, а значит, придется исследовать ВМ и разбираться в алгоритмах ее работы. В общем случае ВМ, применяемые в защитах, делятся на два типа: эмулирующие исполнение псевдокода и использующие вызовы функций ВМ из native-кода.
В первом случае виртуальная машина получает код операции и по своим таблицам определяет производимые действия. Исполнение псевдокода полностью эмулировано в цикле работы машины.
Во втором случае формируется код, который для выполнения каких-либо действий напрямую вызывает функцию ВМ. То есть mov eax, ebx/mov ecx, edx может превратиться в нечто подобное mov eax, 10h/call vm_mov/mov eax, 20h/call vm_mov. Трудно сказать, какой их этих типов ВМ сложнее для взлома, так как все зависит от конкретной реализации. Я считаю, что самой сложной будет комбинированная реализация, при которой часть кода полностью эмулируется ВМ, часть превращается в вызовы ее функций, а остаток подвергается метаморфному преобразованию. По общим методам анализа ВМ никаких рекомендаций тоже не дам, потому что все целиком и полностью зависит от ее реализации. Единственный метод взлома - долгая и упорная медитация над кодом машины и осмысление всего происходящего в ней. И большой запас терпения.
Можешь попрактиковаться, например на VMProtect - простом протекторе. Если захочешь чего-нибудь посложнее, бери Star Force 3 Pro – защиту с ВМ комбинированного типа, которая к тому же работает и в ring3, и в ring0.
Динамическая расшифровка кода
Несомненно, самый старый, но до сих пор применяемый прием - это динамическая расшифровка кода. Его суть в том, что код защищаемой программы расшифровывается не сразу, а по мере его исполнения. Например, Armadillo со включенным CopyMem расшифрует только первую страницу программы и передаст ей управление. Когда та обратится к еще не расшифрованным данным, возникнет исключение, по которому протектор расшифрует еще часть кода, зашифровав старый. Таким образом, весь код программы никогда не присутствует в памяти, а значит, его нельзя сдампить.
Для борьбы с армадилой многим приходилось реверсировать весь код протектора, чтобы заставить его расшифровывать защищенную программу полностью. Но это весьма долгий и тяжелый путь, поэтому я ломал армадилу по-другому. Если процесс нельзя сдампить снаружи, почему не сделать это изнутри? Для этого нужно записать код дампера в адресное пространство процесса с помощью WriteProcessMemory и выполнить его, перенаправив контекст одной из нитей процесса (с помощью SetThreadContext) на наш код.
Дальше внедренный в защищаемую программу дампер должен просто пройтись по секции кода и сбросить ее на диск. На CopyMem можно смело забить болт, так как при отсутствии страницы в памяти протектор, думая, что к ней обратилась сама защищенная программа, будет вынужден расшифровать ее и отдать нашему дамперу в готовом виде, так что Armadillo is fucking yourself. К сожалению, не все протекторы ломаются так же просто, как армадилла. Например, ExtremeProtector расшифровывает код не по обращению к занимаемой им памяти, а по исполнению этого кода, что сильно затрудняет его нахождение. Здесь хочешь не хочешь, но приходится лезть в сам алгоритм работы протектора.
Аппаратные ключи
Существует определенный класс программных продуктов, предназначенных для узкого круга пользователей и при этом очень дорогих. Это, например, корпоративные системы для работы с базами данных, биллинговые системы для провайдеров, специфические программы, применяемые на заводах, в научных лабораториях и т.д. Эти программы стоят десятки или даже сотни тысяч долларов. Убытки автора обычных шароварок от взлома весьма велики. Но у этого класса программ круг пользователей настолько узок, что в условиях российского пиратства и при наличии крэков у авторов не было бы возможности продать даже одну копию. В таких случаях обычно прибегают к аппаратным ключам. Они обеспечивают привязку программы к специальному устройству, подключающемуся к USB или LPT, и при правильной реализации могут сильно усложнить взлом защиты. Несомненно, лидером продаж сейчас являются аппаратные ключи HASP. Фирма Aladdin Software Security выпускает целую линейку ключей, предназначенных как для USB, так и для LPT, и отличающихся принципами своей работы.
Так как же работают аппаратные ключи? В простейшем случае к программе подключается модуль, который проверяет наличие ключа и сообщает программе результат. Кроме проверки наличия, могут также проверяться значения чисел, хранимых в нем, либо они будут использоваться для расшифровки кода программы. Как же бороться с этим? Если применяется простая проверка наличия ключа, эта борьба будет не напряженнее, чем с проверкой серийника: нужно просто найти код, вызывающий проверку, и отключить его.
В более сложных случаях можно заменить код библиотеки, которая используется для работы с ключом, на свой, в котором позже проэмулируешь ответы ключа. Перед этим нужно перехватить исполнение этого кода, получить все значения запрос/ответ, которыми обмениваются ключ и программа, занести их в таблицу, по которой потом будет идти эмуляция. Этот метод называется методом табличной эмуляции. Программы с плохо навешенным хаспом очень хорошо поддаются ему. Но, к сожалению, в реале встречаются далеко не такие простые случаи. Иногда защиту делают программисты, которые в этом деле действительно понимают (это можно увидеть на примере защиты 1C). Копать саму программу, защищенную такими профи, действительно трудно, и проще всего подойти к делу с другой стороны - эмулировать ключ на низком уровне. С ключом обычно идет драйвер, который обрабатывает запросы программы и возвращает ответы ключа. Этот драйвер тоже неплохо защищен, поэтому нужно работать ниже - непосредственно с самим оборудованием. Если попался USB-ключ, то можно считать, что тебе очень повезло, так как защита с USB работает не напрямую, а доверяет это дело системным драйверам. А незащищенные системные драйверы - это хорошее место для внедрения нашего эмулятора. Проще всего будет заменить точки входа в обработчики IRP-драйвера USB так, чтобы направляемые ему запросы попадали нам, после чего можно фильтровать их и эмулировать ответы ключа. Для этого тебе придется изучить работу ядра системы и научиться программировать драйверы - от этого никуда не денешься.
Более сложный случай – LPT-ключ. Драйверы защиты работают с ним напрямую через порты, что сильно затрудняет эмуляцию. Я пока знаю только два рабочих способа эмуляции такого ключа: это изменение кода защитного драйвера и перехват обращений к портам с помощью SMM. Первый способ проще, но требует хорошего знания методов реверсинга, а второй позволяет сделать универсальный эмулятор, но здесь есть неприятность: он будет сильно зависеть от используемого железа. Самое трудное, что может попасться - это последнее поколение аппаратных ключей, которые хранят в себе часть кода защищенной программы, и достать его оттуда программным способом не представляется возможным. Единственное, что делает взлом таких ключей возможным - это низкая скорость работы такого кода. Это означает, что в ключ будут помещать код не критичной для работы программы, а выполняющий какие-либо вспомогательные действия. Такие куски кода можно просто написать самостоятельно.
Пара слов на прощание
Забавно, но во взломе сложных защит практически нет ничего действительно сложного – нужен лишь подход ко всему с умом. Надеюсь, после прочтения этой статьи ты уже не будешь тормозить всякий раз, встретив что-то посложнее аспака, а спокойно и хладнокровно возьмешься ломать. Пробуй, тренируйся, если что – пиши.
Удачного крэка!
Native API
Функции Native API являются базовыми для системы, на них построена работа более высокоуровневого слоя функций kernel32. Они доступны из пользовательского режима через ntdll.dll, но на самом деле функции ntdll являются только переходниками, которые через интерфейс системных вызовов обращаются к соответствующим функциям ядра. При программировании драйверов мы можем использовать те же Native API функции, что и в приложениях третьего кольца, но API более высокого уровня тут недоступен. Также на этом уровне в наши руки попадают многие функции, экспортируемые ядром и предназначенные для использования только в драйверах (прослойка Kernel API). В Native API пользовательского уровня есть пары аналогичных функций, отличающихся только префиксами Zw и Nt. Там они имеют разные названия, но одну и ту же точку входа. На уровне ядра также существуют аналогичные пары функций, но между ними имеется одно различие: функции с префиксом Zw производят перед выполнением действия проверки системы безопасности (прав пользователя), а функции с префиксом Nt - нет.
SMM
SMM (System Managment Mode) - режим работы процессора, появившийся еще в x86-процессорах. Особенности этого режима не документированы и зависят в основном не от процессора, а от чипсета материнской платы. Процессор может войти в этот режим только аппаратно, по сигналу от чипсета, при этом обработчик SMM недоступен даже программам нулевого кольца. По уровню привилегированности этот режим самый высокий из всех, которые вообще возможны на x86-процессорах.
Одна из возможностей SMM, обеспеченных чипсетами - это вызов обработчика при обращении к портам ввода/вывода. Эта возможность предназначена именно для программной эмуляции оборудования, и ее можно использовать для эмуляции аппаратных ключей. Но, к сожалению, на различных чипсетах эта возможность реализована по-своему, а на каких-то просто отсутствует. На некоторых чипсетах (например nForce фирмы Nvidia) вся информация про SMM засекречена и никому не выдается. По умолчанию обработчик SMM устанавливается BIOS'ом и управляет работой кнопки Power на системном блоке. К сожалению, некоторые BIOS'ы блокируют возможность установки своего обработчика SMM, поэтому для работы эмулятора на таких компьютерах потребуется модификация BIOS (о ней читай в июньском "Хакере").
Содержание
|
 |
|