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

Дилдо для вируса

DEEONI$ (DEEONIS@GMAIL.COM)

Хакер, номер #097, стр. 138

Программим файрвол для системы в домашних условиях

Вирусы, трояны и прочие представители злобного программного обеспечения осаждают нас со всех сторон. Антивирусные базы переполняются известными сигнатурами, проактивная защита трещит от всех подозрительных и не очень действий, а воз…и ныне там? Посмотрим, что же тут можно сделать своими руками.

Идея персонального firewall

Человек, который придумал персональный файрвол, избавил интернетчиков от многих проблем. Изобретение было настолько значимым, что персональному файрволу даже выделили отдельный праздник. Идея очень проста – программа просто спрашивает у пользователя, пускать ли приложение в интернет или нет. Эффективность таких сетевых экранов достаточно велика, так почему же не сделать то же самое для ОС? Принцип работы будет такой же, только пользователь будет разрешать или запрещать доступ не к Сети, а к ресурсам системы, точнее, к ее опасным и интимным местам. Опасными местами, в основном, считается реестр, так как там прописываются программы на автозагрузку, BHO, dll, автоматически подгружаемые к эксплореру, и другое.

Эта идея давно витает в воздухе, и многие ее успешно реализуют. Так, например, в шестой версии Антивируса Касперского сделано нечто подобное. Не буду в очередной раз расписывать достоинства отечественного ПО, а расскажу, как можно сделать файрвол для ОС своими. Образцом для нас будет маленькая, но очень полезная программка Arovax Shield (www.arovax.com). Она проста в использовании и абсолютно бесплатна.

Разбор функционала

Итак, прежде чем писать собственный файрвол для операционной системы, мы должны подробно ознакомится с возможностями программы (Arovax Shield). Допустим, что ПО уже установлено и запущенно, - нас будет больше всего интересовать вкладка «Protection». Там расположено несколько чекбоксов, которые и определяют все возможности утилиты. В списке этих возможностей - мониторинг секции Run в реестре и папке «Автозагрузка», мониторинг BHO объектов и панелей инструментов IE, слежение за файлом hosts и некоторые другие вещи. Если какая-нибудь программа попытается прописать себя в автозагрузку, то Arovax Shield спросит у пользователя, запретить или разрешить это действие. Все предельно просто.

Теперь давай попробуем разобраться, как это все устроено.

Слежение за реестром

Основное, чем занимается Arovax Shield, – это слежение за изменениями в определенных ключах реестра. Самым известным из них является HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun. Как же узнать, произошли ли изменения в этой ветке реестра или нет. Первое, что приходит в голову, - это сделать первоначальный «снимок» этой ветки, а потом постоянно сверять его с оригиналом. Но это не самое лучшее решение. Есть гораздо более эффективный способ. В недрах Windows существует одна маленькая API-функция, которая извещает вызвавшую ее программу об изменении определенного ключа. Вот ее описание:

LONG RegNotifyChangeKeyValue(HKEY hKey, BOOL bWatchSubtree, DWORD dwNotifyFilter, HANDLE hEvent, BOOL fAsynchronous);

Здесь hKey – это хэндл ключа реестра. В нашем случае это будет хэндл ключа HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun. bWatchSubtree должен быть равен TRUE, если мы хотим следить не только за HKLM...Run, но и за всеми его подключами. dwNotifyFilter задает категорию изменений, при которых функция будет срабатывать. Для секции автозагрузки нам следует передать в этом параметре REG_NOTIFY_CHANGE_LAST_SET, вследствие чего мы будем мониторить только изменения параметров соответствующего ключа. hEvent – это хэндл объекта «событие», о котором я расскажу чуть позже. Последний параметр следует установить в TRUE, чтобы реагировать на событие. В случае удачного выполнения функция должна вернуть значение ERROR_SUCCESS.

Получить хэндл ключа автозагрузки можно при помощи функции RegOpenKey.

LONG RegOpenKey(HKEY hKey, LPCTSTR lpSubKey, PHKEY phkResult);

Параметр hKey – это хэндл ветки реестра, где расположен требуемый нам ключ. Для начала можно указать, например, HKEY_CURRENT_USER или HKEY_LOCAL_MACHINE. lpSubKey – указатель на нультерминальную строку, которая содержит имя открываемого ключа в ветке. phkResult – это адрес хэндла открытого ключа, функция запишет сюда какое-то значение. Если вызов этой API завершится удачно, то вернется NULL, в противном случае - любое другое, не нулевое значение.

Теперь, как я и обещал, расскажу немного о событиях. Смысл использования события - в уведомлении одного или нескольких ожидающих потоков. События бывают двух типов: сбрасываемые вручную и сбрасываемые ожидаемыми их функциями. Первый тип нужно применять, если события ждут несколько потоков. Мы будем использовать второй тип. Событие может быть в двух состояниях: сигнальном и не сигнальном. Функция RegNotifyChangeKeyValue принимает хэндл события и, в случае изменения ключа или параметра, устанавливает event в сигнальное состояние.

Все просто. Но для начала нужно создать объект события, что делается следующей функцией:

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);

Здесь lpEventAttributes – указатель на атрибут защиты, в нашем случае он не нужен, и его можно установить в NULL. bManualReset – тип сброса события (следует устанавливать TRUE, если событие будет сбрасываться вручную). Мы же установим FALSE. bInitialState – начальное состояние. Если передать TRUE, то начальное состояние будет сигнальное, если FALSE – то не сигнальное. Мы опять должны передать FALSE, так как RegNotifyChangeKeyValue будет сама устанавливать сигнальное состояние. Последний параметр - это указатель на имя события. Имя нам не нужно, поэтому можно передать NULL. Если функция сработала успешно, то вернется дескриптор события, в противном случае - NULL.

Теперь осталось только ждать, когда кто-нибудь попытается прописаться в ключ автозагрузки. Об этом нас известит созданный нами event в сигнальном состоянии. Проверить событие нам поможет функция WaitForSingleObjectEx.

DWORD WaitForSingleObjectEx(HANDLE hHandle, DWORD dwMilliseconds, BOOL bAlertable);

Здесь hHandle – это хэндл некоторого объекта, который ожидает функция. Мы передадим в этом параметре дескриптор созданного нами события. dwMilliseconds – это временной интервал, в течение которого функция будет ожидать наступления события. Если в этом параметре передать INFINITE, то ожидание будет бесконечным. bAlertable сразу можно выставлять в FALSE, он связан с APC пользовательского режима и нам не нужен. Когда объект примет сигнальное состояние, функция должна вернуть WAIT_OBJECT_0.

Теперь нам должно быть достаточно знаний, чтобы организовать какую-либо реакцию на изменение ключа реестра. Последовательность действий проста: создать объект «событие» в не сигнальном состоянии, вызвать RegNotifyChangeKeyValue с соответствующими параметрами, а затем дожидаться установки объекта в сигнальное состояние функцией WaitForSingleObjectEx. Листинг 1 наглядно демонстрирует этот алгоритм.

Надеюсь, с этим все понятно. Но тут перед нами встает еще одна проблема: как определить, что именно изменилось в ветке, и как в случае необходимости предотвратить эти изменения? Скажу честно: готовых решений нет. Самым оптимальным, на мой взгляд, будет создание бэкапа ключа, который мы мониторим. Если функция RegNotifyChangeKeyValue известит нас о том, что кто-то изменил нашу ветку, то мы должны будем сверить ее с резервной копией и передать информацию об изменениях пользователю. Если пользователь решит, что подобные изменения нежелательны, то нам придется восстанавливать ключ при помощи сделанного ранее бэкапа. В противном случае надо будет просто обновить резервную копию ветки. Звучит несложно, но, на самом деле, здесь много подводных камней. В частности, нужно решить проблему с форматом данных, которые будут содержать информацию о ключе. Лучше всего реализовать резервную копию в виде класса, хранящего в себе все необходимые сведения о ветке реестра. Но это уже личное дело каждого.

Слежение за файлами

Теперь разберемся, как следить за файлами на жестком диске. Для примера выберем в качестве цели папку автозагрузки. Windows 98, как и Windows NT, позволяет установить аудит каталога с помощью функции FindFirstChangeNotification. Вот она:

HANDLE FindFirstChangeNotification(LPCTSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter);

С первым параметром все понятно – это путь к каталогу. Флагом управления может быть значение TRUE или FALSE. От него зависит, будут ли события генерироваться только для каталога (FALSE) или для каталога и всех подкаталогов (TRUE). Третий параметр - это флаги, с помощью которых можно установить типы событий, на которых будет генерироваться событие. Он может принимать следующие значения:

Если все пройдет нормально, функция вернет дескриптор папки, в противном случае - значение INVALID_HANDLE_VALUE. Теперь для обнаружения момента изменений в папке нужно воспользоваться уже знакомой нам функцией WaitForSingleObjectEx, только вместо дескриптора события первым параметром будем передавать хэндл папки. После того как обработаем ситуацию с изменением каталога, нужно вызвать FindNextChangeNotification, чтобы продолжить слежение.

BOOL FindNextChangeNotification(HANDLE hChangeHandle);

Единственным параметром этой функции является дескриптор, полученный в результате вызова FindFirstChangeNotification. При успешном выполнении функция вернет TRUE, в противном случае - FALSE.

Когда наблюдение с каталога будет снято, надо вызвать FindCloseChangeNotification:

BOOL FindCloseChangeNotification(HANDLE hChangeHandle);

Функции передается единственный параметр - такой же, как у FindNextChangeNotification. Все описанное выше представлено в листинге 2.

Если приходится следить сразу за несколькими каталогами, то нужно поочередно вызывать FindFirstChangeNotification для всех целей, а момент изменения отслеживать при помощи функции WaitForMultipleObjectsEx.

DWORD WaitForMultipleObjectsEx(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds, BOOL bAlertable);

Здесь nCount – количество дескрипторов объектов. lpHandles – указатель на массив дескрипторов (количество элементов массива должно совпадать с первым параметром). bWaitAll, установленный в TRUE, заставляет ждать изменения состояния всех объектов в массиве, в FASLE – только одного из них. Последние два параметра совпадают с соответствующими параметрами в функции WaitForSingleObjectEx. Если состояние объекта изменилось, то функция вернет значение WAIT_OBJECT_0 + nCount – 1. Листинг 3 демонстрирует пример слежения за несколькими каталогами одновременно.

К сожалению, отследить, что именно изменилось, с помощью этих API нельзя. Для этого можно воспользоваться механизмом, предложенным мною для веток реестра. На основе слежения за каталогами в Arovax Shield реализовано блокирование нежелательных cookie и защита от изменений файла hosts. Когда пользователь вбивает в браузере адрес какого-либо сайта, система сначала ищет эту строку в файле hosts, а потом уже обращается к DNS-серверам. Изменение этого файла может использоваться для проведения фишинговых атак.

За чем нужно следить?

Теперь давай решим, что нужно мониторить в Windows. В первую очередь это BHO - расширение/плагин для Internet Explorer, которое увеличивает функциональность браузера. Расширение представляет собой DLL-модуль, который загружается в процесс Internet Explorer каждый раз при запуске браузера. BHO - это объект компонентной модели (Component Object Model - COM). Такие компоненты исполняются в том же контексте памяти, что и браузер, и могут выполнять операции над всеми доступными окнами и модулями. Например, BHO может реагировать на типичные события браузера, такие как GoBack, GoForward или DocumentComplete, изменять меню и панели инструментов браузера, создавать новые окна для отображения информации, устанавливать хуки для отслеживания сообщений или запускать другие приложения.

Некоторые BHO легитимны и не повредят компьютеру, например Google Toolbar, Adobe Acrobat IE Helper, Yahoo! Companion и т.д. Но имеется огромное число объектов BHO, единственная цель которых - показывать рекламу или следить за пользователем. Идентификаторы CLSID объектов BHO, которые Internet Explorer загружает, расположены в реестре в следующем ключе:

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionExplorerBrowser Helper Objects

Следующей целью будут панели инструментов IE. Панель инструментов IE очень похожа на BHO - это также расширение/плагин для Internet Explorer, которое увеличивает функциональность браузера. Единственное отличие панели инструментов от BHO в том, что, помимо скрытой работы кода модуля, панель отображает еще и дополнительную панель инструментов под меню и панелью навигации браузера. Одной из самых популярных панелей инструментов является Google Toolbar для Internet Explorer. Идентификаторы CLSID дополнительных панелей инструментов для Internet Explorer расположены в реестре в следующем ключе:

HKEY_LOCAL_MACHINESoftwareMicrosoftInternet ExplorerToolbar

Опасными также являются элементы ActiveX. ActiveX - это компонент (DLL- или OCX-модуль), который предоставляет лучшие средства взаимодействия при просмотре веб-страницы, чем те, которые достигаются при использовании только кода HTML. Например, элемент управления ActiveX может воспроизводить видео, анимацию, звук, отображать 3D-графику и т.д. ActiveX также может загружать и устанавливать дополнительные программы или изменять конфигурацию системы. Идентификаторы CLSID скачанных объектов ActiveX хранятся в следующих ключах:

HKEY_LOCAL_MACHINESoftwareMicrosoftCode Store DatabaseDistribution Units

HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExtStats

Автозагрузка может происходить из следующих мест:

UserStart MenuProgramsStartup;

All UsersStart MenuProgramsStartup;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRun;

HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRun;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRunOnce;

HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRunOnce;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRunServices;

HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRunServices;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRunServicesOnce;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionPoliciesExplorerRun;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRunOnceEx;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionWinlogon, Shell;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionWinlogon, System;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionWinlogon, VmApplet;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionWinlogon, UIHost;

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionWinlogon, Userinit;

HKEY_CURRENT_USERSoftwareMicrosoftWindows NTCurrentVersionWindows, run;

HKEY_CURRENT_USERSoftwareMicrosoftWindows NTCurrentVersionWindows, load;

HKEY_LOCAL_MACHINESoftwareMicrosoftActive SetupInstalled Components;

HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession Manager, BootExecute;

HKEY_CURRENT_USERSoftwareMirabilisICQAgentApps;

win.ini, load;

win.ini, run;

system.ini, shell.

Протоколы (asynchronous pluggable protocol) могут использоваться для обработки дополнительных схем протоколов URL, для фильтрации данных определенного MIME-типа или для перехвата/модификации данных, передаваемых по стандартным протоколам (http, https, ftp). Протокол - это DLL-модуль. Сложные программы-паразиты могут использовать их для наблюдения за интернет-трафиком пользователя. Они хранятся в реестре в ключе HKEY_CLASSES_ROOTPROTOCOLS.

Напоследок

То, что я перечислил, - это далеко не все. Вредоносные программы используют еще много трюков, чтобы закрепиться в системе. Но если вести грамотный мониторинг реестра и файловой системы, можно отказаться от использования классических антивирусов. Я верю, что скоро на смену сканерам придут «системные файрволы», которые станут столь же полезны, как и сетевые.

Листинг 1

LONG err;

HKEY hKey;

HANDLE event;

// получаем дескриптор ключа, который будем мониторить

err = RegOpenKey(HKEY_LOCAL_MACHINE, \SOFTWARE\Microsoft\Windows\ CurrentVersion\Run, &hKey);

if (err != ERROR_SUCCESS)

retu;

// создаем объект «событие»

event = CreateEvent( NULL, FALSE, FALSE, NULL );

// ставим открытую ветку реестра под наблюдение

err = RegNotifyChangeKeyValue( hKey, TRUE, REG_NOTIFY_CHANGE_LAST_SET, event, TRUE );

if (err != ERROR_SUCCESS)

retu;

// в бесконечном цикле ожидаем наступления события

while (1)

{

if ( WaitForSingleObjectEx( event, INFINITE, TRUE ) == WAIT_OBJECT_0 )

{

// если произошли какие-либо изменения, то обрабатываем их здесь

// а затем опять начинаем следить за веткой

err = RegNotifyChangeKeyValue( item.handle, TRUE, filter, event, TRUE );

}

}

RegCloseKey(hKey);

Листинг 2

// переменная, в которой будет храниться путь к директории с файлом hosts

TCHAR hostsDir[MAX_PATH];

HANDLE dwChangeHandle = FindFirstChangeNotification(hostsDir, FALSE,

FILE_NOTIFY_CHANGE_LAST_WRITE );

if ( dwChangeHandle == INVALID_HANDLE_VALUE )

retu;

while ( WaitForSingleObjectEx( dwChangeHandle, INFINITE, TRUE ) == WAIT_OBJECT_0 )

{

// обрабатываем изменения в каталоге

// продолжаем наблюдение за директорией

if ( FindNextChangeNotification( dwChangeHandle ) == FALSE )

break;

}

// завершаем наблюдение

FindCloseChangeNotification( dwChangeHandle );

// количество папок, за которыми следим

int foldersCount;

// массив указателей на строки, содержащие пути к папкам

CString* path = new CString*[foldersCount];

// массив указателей на дескрипторы

HANDLE* handles = new HANDLE[foldersCount];

// заполняем массив path

// в цикле заполняем массив хэндлов

for (int i = 0; i < foldersCount; i++)

{

handles[i] = ::FindFirstChangeNotification(path[i], FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME);

}

DWORD waitResult = 0;

// ожидаем изменения хотя бы одной папки

while ((waitResult = WaitForMultipleObjectsEx((DWORD) foldersCount, handles, FALSE, INFINITE, TRUE)) <= WAIT_OBJECT_0 + foldersCount)

{

int index;

// определяем, какая папка изменилась, и присваиваем ее номер в массиве

// переменной index

// обрабатываем изменения в каталоге

// продолжаем наблюдение

FindNextChangeNotification(handles[index]);

}

// завершаем наблюдение за всеми папками

for (int i = 0; i < foldersCount; i++)

{

FindCloseChangeNotification(handles[i]);

}

Содержание
ttfb: 6.540060043335 ms