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

Ковыряем Windows по примеру известных руткит-мейкеров

Александр Эккерт, stannic.man@gmail.com


В основе функционирования современных операционных систем лежит виртуальная память. Именно она позволяет адресовать в 32-битных системах 4 гигабайта виртуального адресного пространства. А 64-битная адресация, в свою очередь, позволяет использовать практически неограниченное количество виртуальной памяти.

Сегодня мы поговорим об основном способе организации виртуальной памяти — страничном, при котором единицей отображения виртуальных адресов на физические является регион постоянного размера (так называемая страница). Поддержка такого режима присутствует в большинстве современных процессоров, и он является классическим для почти всех современных ОС, в том числе Windows. В статье мы попытаемся рассмотреть, как этот «классический» аргумент можно использовать в своих грязных целях.

Введение, или о чем это мы?

В большинстве современных операционных систем, в том числе, в семействе ОС Windows виртуальная память организуется с помощью страничной адресации. Если говорить коротко и понятно, то данные в памяти в Windows существуют:

1) в виртуальном виде размером в 4 гигабайта и 2) в проекции этих самых данных в памяти физической и, наконец, в виде так называемых страниц.

Что такое страницы? Это области памяти фиксированной длины (как правило, или 4 Кб, или 4 Мб), которые являются минимальной единицей выделяемой памяти (то есть, даже запрос на 1 байт от приложения приведет к выделению ему страницы памяти). Процесс обращается к памяти с помощью адреса виртуальной памяти, который содержит в себе номер страницы и смещение внутри страницы. Операционная система преобразует виртуальный адрес в физический, при необходимости подгружая страницу с жесткого диска в оперативную память. При запросе на выделение памяти операционная система может сбросить на жесткий диск страницы, к которым давно не было обращений.

Соответствующие единицы в физической памяти образуют страничные кадры (page frames), а система поддержки страничной виртуальной памяти называется пейджингом (paging). Передача информации между памятью и диском всегда осуществляется целыми страницами.

Главное, что надо уяснить - данные, представленные в виртуальной памяти, на самом деле находятся в памяти физической (оперативной или сброшены на жесткий диск) и для доступа к ним процессор использует механизм страничной трансляции. Таким образом, операция записи в память какого-либо процесса в конечном итоге сведется к записи страниц, принадлежащих этому процессу и находящихся в где-то в физической памяти. Если вдуматься, то получается, что данные находятся как бы в дублированном состоянии (хотя это не так): непосредственно сами данные лежат в памяти физической, и они же представлены в памяти виртуальной. А раз основная организация виртуальной памяти есть страничная память, что нам помешает, не трогая виртуальных адресов, найти сами физические страницы, содержащие нужные нам данные и использовать (читай – «перезаписать или подменить») их по своему усмотрению? Да ничего! Ни антивирусы, ни проактивки, ни файрволы нам не помеха!

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

Разбираем устройство виртуальной памяти

Чтобы применять эту технику на практике, конечно же, нужно понять основы — правила организации и механизм работы виртуальной и страничной адресации. Не вдаваясь в подробности механизма страничной адресации (он довольно сложен для понимания), отмечу только, что в его основе используется двухуровневая табличная трансляция линейного адреса в физический. Этот механизм имеет три части: каталог страниц (Page Directory, PDE), таблицы страниц (Page Table, PTE) и собственно страницы (Page Frame). Тем, кто хочет досконально разобраться в механизме страничной трансляции, могу порекомендовать мануалы INTEL'а по системному программированию; их ты можешь найти на сайте www.intel.com или на нашем диске.

Как же можно заполучить доступ к этим каталогам - PDE и PTE? Очень легко! Любителям лаконичности можно посоветовать вот такой способ обретения указателя на таблицы страниц PTE: ((PTE*)(((((ULONG)(VirtualAddress)) >> 12) << 2) + PteBaseAddress)), где PteBaseAddress равен 0xC0000000. Для любителей махровой лаконичности, сокращений и оптимизации могу посоветовать и такой вариант PTE: = ( VirtualAddress >> 10)+0xc0000000. Соответственно, обратный алгоритм поиска адресов виртуальной памяти по известным PTE будет выглядеть: VirtualAddress = PTE << 10.

Стоит, наверное, пояснить. Допустим, у нас есть линейный 32-байтный адрес: 1100000000.0000000101.0000001001.00b (точки использованы для удобства). Глядя на этот адрес, получим PTE с номером 1001b в таблице страниц 101b.

В ядре существует целый набор системных функций, предназначенных для работы с памятью, как физической, так и виртуальной. Все они имеют префикс Mm*. В юзермоде тебе должны быть знакомы функции WriteProcessMemory и ReadProcessMemory, хотя на самом деле они являются «заглушками» для вызова сервисов ядра. Например, функция MmGetPhysicalAddress вернет физический адрес, соответствующий виртуальному адресу, находящемуся в неподкачиваемой памяти. Системная функция ядра - MmIsAddressValid() - всего-навсего проверяет, представлена ли страница в физической памяти, т.е. не сработает ли исключение «Page Fault» при обращении по указанному виртуальному адресу. Если страницы нет, то функция вернет FALSE. Также интересна функция MmIsNonPagedSystemAddressValid, которая в отличие от MmIsAddressValid проверяет то, что переданный ей адрес принадлежит подкачиваемой или неподкачиваемой областям памяти ядра. Так, затрагивая тему работы с виртуальной/физической памятью, хотелось бы заметить, что у страниц в памяти может быть несколько состояний. Перечислим их!

Способ раз, или «Поймай меня, если сможешь!»

В момент обращения страница может присутствовать в физической оперативной памяти, а может быть выгруженной на внешнюю (дисковую) память. При обращении к выгруженной странице памяти процессор генерирует исключение #PF — «Page Fault» или отказ страницы, а программный обработчик исключения (часть ОС) получит необходимую информацию для свопинга — «подкачки» отсутствующей страницы с диска. И самое главное - страницы не имеют прямой связи с логической структурой данных или программ. Абсолютно стандартное поведение менеджера виртуальной памяти! И ведь кто бы мог подумать - эта «особенность» виртуальной памяти ОС Windows используется в руткитостроении. Каким образом?

Если ты знаешь, список системных модулей (читай - драйверов) ядра можно получить, вызвав системную функцию NtQuerySystemInformation c параметром SystemModuleInformation или открыв директорию «\Driver» вызовом ZwQueryDirectoryObject. Также можно пробежаться по системному списку PsLoadedModuleList (расписывать эти способы не буду, тема статьи другая, если интересно - за подробностями в Гугл).

Все известные способы сокрытия драйвером заключались в патче указанных функций или исключении имени модуля из PsLoadedModuleList.

Но вот на свет появился Shadow Walker - руткит уровня ядра. Главной его особенностью является способ сокрытия в ядре нужных хакеру данных - как правило, Shadow Walker скрывает другой драйвер руткита FU. Это, с позволения сказать, революционное решение заключается в следующем. Загруженный драйвер руткита ставит перехват на прерывание INT0E и помечает нужные физические страницы как «не представленные в памяти» - и в случае обращения менеджера виртуальной памяти к этим самым страницам, где лежит драйвер, будет сгенерировано то самое исключение - #PF или «PageFault». Здорово, правда?

Когда произойдет ошибка страницы (при ее чтении) в связи с тем, что PTE помечен как недействительный, Shadow Walker определяет, является ли страница той, которую необходимо скрыть; если да, то подменяются данные на странице. Если происходит выполнение на странице кода руткита, Shadow Walker просто помечает PTE как действительные и дает процессору доступ к странице. Правда, проблема здесь в сложности определения, что именно понадобилось процессору: прочесть данные или выполнить код. Разработчиками Shadow Walker было решено и это; для ознакомления сорцы руткита лежат на диске, думаю, ты сможешь разобраться сам.

Убираем бит представления страницы в памяти

Что ж, поехали!

VOID MarkPageNotPresent( PPTE pPte )
{
__asm
{
mov eax, pPte
and dword ptr [eax], 0xFFFFFFFE
}

Эффективность руткита такова, что до сих пор вычислить его присутствие в оперативной памяти может лишь сигнатурный поиск по всей физической памяти или использование гипервизора. Много ли современных антивирусов способны на подобное? Если учесть, что зачастую данные в силу фрагментированности разбросаны по всему жесткому диску, а гипервизор - неподъемная задача, то руткит оказывается не под силу обычным антивирусам.

Вычислить присутствие этого руткита в оперативной памяти до сих пор может лишь сигнатурный поиск по всей физической памяти или использование гипервизора. Много ли антивирусов способны на подобное?

Существует и программный поиск сокрытых Shadow Walker страниц в памяти; более подробно об этом можно почитать здесь http://www.ht-group.net/32, однако он ненадежен.

Способ два, или «Не верь глазам своим»

Теперь перейдем ко второму, крайне интересному, но практически неизвестному в широких кругах способу манипуляций с содержимым виртуальной памяти. Находясь в ядре, мы всегда сможем попасть в адресное пространство любого процесса, вызвав функцию KeAttachProcess. Что она делает, я думаю, ты понимаешь. Также уверен, что тебе знакома такая фича, когда, находясь в адресном пространстве, ты можешь относительно безопасно писать данные вызовом функции ZwWriteProcessMemory, - ][ уже писал об этом. Но, увы и ах! Всякий мало-мальски нормальный антивирус обычно хукает такую опасную функцию, ибо ее использование стоит на первом месте у злых кодеров. Но теперь (и я хочу, чтобы ты прочувствовал всю значимость момента!), используя форму организации страничной памяти, можно изменять данные в адресном пространстве без записи данных; ведь надо просто подменить PTE нужного нам виртуального адреса! Здесь виртуальная память процесса останется нетронутой, но при обращении к адресу подмены менеджер памяти вернет запрашивающему те данные, которые нам нужно подсунуть! Смотрим код:

Подмена страниц PTE по нужному адресу

DWORD ChangePTEOfTarget(DWORD VirtualAddressOfTarget, DWORD NewVirtualAddress)
{
DWORD vaTargetPTEaddress;
DWORD vaTargetPTE;
DWORD NewVAPTEaddress;
DWORD NewVAPTE;
DWORD source;

source = VirtualAddressOfTarget;
source = source >> 12;
source = source << 2;
vaTargetPTEaddress = 0xC0000000 + source;
vaTargetPTE = *vaTargetPTEaddr;

source = NewVirtualAddress;
source = source >> 12;
source = source << 2;
NewVAPTEaddress = 0xC0000000 + source;
NewVAPTE = *NewVAPTEaddress;
__asm cli
vaTargetPTEaddress = source;
__asm sti
return source;
}

Вот в принципе и все. Если немного постараться, то можно немного сократить и усовершенствовать этот код. Столь простая, но крайне эффективная техника может легко использоваться для подмены данных в адресном пространстве процесса (сколько простора для геймчитеров!) или вызывать напрямую похуканые проактивными защитами важные системные функции.

Путешествие в Зазеркалье

На примере этой статьи можно видеть, что мир процессов - а это игры, антивирусные программы, да все что угодно, что крутится в таскменеджере Windows - довольно зыбок и нельзя однозначно полагаться на то, что ты видишь собственными глазами. В общем, «The matrix has you, Neo!». Мир виртуальной памяти Windows поистине захватывает, и путешествие в его глубины сродни путешествию в Зазеркалье. Приложишь немного усилий - и тебе станет подвластна вся операционная система. Удачного компилирования и да пребудет с тобой Сила!

DVD

На диске ты сможешь найти литературу, которая поможет в отладке и дебаге твоего кода. Обещанные сорцы руткита Shadow Walker ждут тебя там же.

WWW

Хочешь знать все-все о памяти в Windows? Бегом на http://gr8.cih.ms/index.php?entry=entry008 - лучше этой статьи админа форума wasm.ru под ником Great не найти.

Также замечательная статья по управлению памятью в ОС Windows, правда, на английском - http://www.informit.com/articles/article.aspx?p=167857.

INFO

Будь осторожен при опытах с виртуальной памятью!

Содержание