Шесть грехов malware-писателей

       

Грех6 — незаконнорожденные потоки


Число 6 находится у язычников и сатанистов на особом почете, поэтому шестой грех будет самым объемистым, каким, он, в действительности и является.

Чтобы не порождать отдельный процесс, некоторая малварь внедряется в один из уже существующий, порождая в нем свой поток, причем делает это настолько неумело, что сразу же обращает на себя внимание и легко обнаруживается утилитой "Process Explorer" Марка Руссиновича или любым отладчиком (OlyDbg, soft-ice). А все потому, что память, в которой малварь размещает свой код, в 99% случаях выделяется через VirtualAlloc/VirtualAllocEx, то есть берется из динамической памяти, в то время как нормальные потоки вращаются в пределах образов исполняемых файлов или DLL.

Чтобы хоть как-то замаскировать торчащий из норы хвост, малварь должна поместить свое тело в DLL, закинуть ее в системный каталог Windows (или куда-нибудь в другой место) и загрузить внутрь атакуемого процесса через LoadLibrary. Естественно, делать это следует из контекста атакуемого процесса, поскольку, ни сама LoadLibrary, ни ее расширенная версия LoadLibraryEx не принимают обработчик процесса в качестве одного из своих аргументов. То есть, прежде чем загружать DLL, в процесс прежде необходимо как-то внедриться. Проще всего это сделать через уже упомянутую VirtualAllocEx: выделить блок в атакуемом процессе, скопировать туда код загрузчика и, либо вызвать CreateRemoteThread, либо скорректировать контекст активного потока, передав загрузчику управление путем вызова функции GetThreadContext и коррекции регистра EIP.

Получив управление, загрузчик должен вызвать LoadLibrary для загрузки своей DLL, вызвать CreateThread, указав в качестве стартового адреса одну из функции динамической библиотеки, после чего "замести следы": освободить блок памяти, выделенный VirtualAllocEx, завершить поток, порожденный CreateRemoteThread (или вернуть EIP на место). Естественно, поскольку после освобождения региона памяти исполнение оставшегося в нем кода становится невозможным, мы не сможем вызвать TerminateThread не схлопотав исключение, если только… не воспользуется приемом, описанным в листинге 2.
На этот раз он не использует _никаких_ недокументированных возможностей и полностью законен, сохраняя работоспособность даже на машинах с включенным механизмов DEP, препятствующим выполнению кода в стеке. Однако, никакого _кода_ в стеке у нас нет! Одни лишь адреса возврата вместе с аргументами вызываемых функций! Правда, на 64-битных версиях Windows это уже не сработает, поскольку там API-функции принимают аргументы через регистры и стек отдыхает (разумеется, сказанное относится только к 64-битным приложениям! старые, 32-битные приложения, будут работать как обычно)

Кстати, о DEP. Выделяя блок памяти с помощью VirtualAllocEx, присвойте ему атрибуты PAGE_READWRITE, а перед передачей управления смените их на PAGE_EXECUTE через VirtualProtectEx. На машинах без DEP, атрибуты PAGE_READ

и PAGE_EXECUTE

взаимно эквиваленты, поскольку процессоры старого поколения поддерживали только два атрибута страниц: ACCESS (страница доступна для чтения/исполнения или недоступна) и write (страница доступна/недоступна для записи). Атрибут EXECUTE был прерогативой таблиц селекторов (то есть сегментов). Во времена разработки 80386 никому и в голову не могло прийти, что массовая операционная система сведет все три сегмента (кода, стека и данных) в линейную память общего адресного пространства! Фактически, атрибут PAGE_EXECUTE был высосан разработчиками win32 из пальца и не соответствовал реальному положению вещей, но сейчас все изменилось. И хотя по умолчанию DEP включен только для системных процессов (да и то не для всех), привыкать к атрибуту PAGE_EXECUTE следует уже сейчас. А почему мы не установили сразу все три атрибута на страницу PAGE_EXECUTE_READWRITE? Ведь VirtualProtectEx

это позволяет, да и процессор не против. _Сейчас_ да, никто не против и не возражает, но в Microsoft уже вынашивает планы по совершенствованию защиты своей оси, и попытка присвоения всех трех атрибутов будет либо вызывать исключение, либо требовать специальных привилегий.

Но все равно, создание дополнительного протока это как-то слишком заметно.


Может ли малварь без него обойтись? Может! И для этого существует множество путей. Не все из них ведут в рай, но все-таки… Самое простое — внедрившись в атакуемый процесс через VirtualAllocEx

и загрузив свою DLL, установить таймер, воспользовавшись API-функцией SetTimer, и периодически получать таймерные сообщения, обрабатывая их в контексте основного потока, точнее того потока, с которым ассоциирован фокус ввода. Но это уже технические делали, в которые можно не вдаваться. Главное, что новый поток _не_ создается. Правда, остается таймер… А таймер это такая штука… не самая распространенная среди прикладных приложений. Так зачем же лишний раз обращать на себя внимание?

Хитрая малварь действует очень скрытно. Получив дескриптор главного окна программы она вызывает API-функцию GetWindowLong с параметром GWL_WNDPROC, получая адрес оконной процедуры (где происходит обработка сообщений) и тут же меняет его на свой через SetWindowLong. Этим она не только перехватывает все сообщения (в том числе передвижения мыши и нажатия клавиш, что очень полезно для шпионской деятельности), но и гарантированно обеспечивает себя процессорным временем, не создавая ни таймеров, ни новых потоков. Правда, пытливый исследователь, вооруженный soft-ice, может забеспокоиться: с чего бы это, главное окно обрабатывается какой-то там посторонней DLL? Однако, при компонентом подходе к программированию такие случаи встречаются достаточно часто и в легальных программах, особенно, если они написаны на DELPHI.

А вот еще один путь: асинхронные сокеты — практически неиспользуемые, но очень мощные. Главное их достоинство в том, что ожидая подключения клиента или передавая/принимая данные по сети, сокет немедленно возвращает управление, сигнализируя о завершении процесса приема/передачи через специальный CALLBACK. В практическом плане это означает, что малварь может установить асинхронный сокет и тут же возвратить основному потоку управления, будучи при этом абсолютно уверенной, что в нужный момент, операционная система вспомнит о ней и передаст управление CALLBACK-процедуре, расположенной внутри загруженной малварью динамической библиотеке.Внешне все выглядит чики-чики: никаких тебе дополнительных потоков, никаких перехватов чего бы то ни было, вообще ничего подозрительного.



Рисунок 10 под атакой малвари


Содержание раздела