Реализация защиты на уровне системных DLL, предоставляющих приложениям различные сетевые интерфейсы
К этим DLLs относятся, в первую очередь, следующие библиотеки DLLs сетевых интерфейсов:
- Netapi32.dll (Net Win32 API DLL), предоставляющая интерфейс NetBIOS и Net-функции.
- Ws2_32.dll (Windows Socket 2.0 32-Bit DLL), msafd.dll (Microsoft Windows Sockets 2.0 Service Provider), wsock32.dll (Windows Socket 32-Bit DLL), являющиеся DLLs интерфейса Windows Sockets.
- Mpr.dll (Multiple Provider Router DLL), предоставляющая сетевой интерфейс Win32 (WNet), и реализующая функции маршрутизатора многосетевого доступа.
- Kernel32.dll (Windows NT BASE API Client DLL) – DLL API ввода/вывода Win32, эта библиотека предоставляет стандартные функции, такие как функции открытия, закрытия, чтения, записи и т.п. Если файл, именованный канал, почтовый ящик, устройство, к которым обращены запросы в этих функциях, находятся на удаленной машине, то эти запросы пройдут по сети.
- Wininet.dll (Internet Extensions for Win32), реализующая функции передачи HTTP запросов, навигации файлов по протоколу FTP, функции удаленного доступа в Internet, URL (Uniform Resource Еоса1ог)-функции и т.п.
- Rpcrt4.dll (Remote Procedure Call Runtime), реализующая вызовы удаленных процедур.
- Rasapi32.dll (Remote Access API), обеспечивающая приложениям удаленный доступ.
- Nddeapi.dll (Network DDE Share Management APIs) и ddeml.dll (DDE management library), реализующие динамический обмен данными по сети.
- Tapi32.dll (Microsoft Windows Telephony API Client DLL), предоставляющая приложениям телефонные сервисы.
- Dlcapi.dll (DLC APIs), обеспечивающая взаимодействие по протоколу DLC.
- Wsnmp32.dll (Microsoft WinSNMP Manager API DLL), mgmtapi.dll (Microsoft SNMP Management API DLL), snmpapi.dll (SNMP utilities DLL), используемые SNMP-приложениями.
Прежде чем перейти к рассмотрению возможностей реализации защиты на уровне этих DLL, ниже будет представлен процесс загрузки ЕХЕ-файла. При запуске ЕХЕ-файла загрузчик операционной системы выполняет следующие действия:
- Отыскивает ЕХЕ-файл, создает новый объект-процесс, создает адресное пространство нового процесса размером 4 Гб. Резервирует регион адресного пространства – такой, чтобы в него поместился заданный ЕХЕ-файл. Желаемое расположение этого региона указывается внутри самого ЕХЕ-файла. По умолчанию базовый адрес ЕХЕ-файла – 0x00400000 (но при компоновке это значение может быть изменено). И отмечает, что физическая память, увязанная с зарезервированным регионом, – ЕХЕ-файл на диске.
- Затем загрузчик просматривает таблицу импорта, содержащуюся в этом файле, и пытается найти и спроецировать на адресное пространство нового процесса все необходимые DLLs. Желаемое расположение региона адресного пространства, куда будет спроецирована конкретная DLL, указывается внутри самого DLL-файла, по умолчанию базовый адрес DLL-файла – 0x10000000. Но у всех стандартных системных DLL модулей (в том числе и вышеперечисленных), входящих в комплект поставки Windows NT, разные базовые адреса. Затем загрузчик отмечает, что физическая память, увязанная с зарезервированным регионом, – DLL-файл на диске.
- Затем загрузчик сохраняет адреса импортируемых идентификаторов (функций и переменных), используемых ЕХЕ-файлом, в особой таблице адресов импорта (Import Address Table). Всякий раз, когда приложение ссылается на один из таких идентификаторов, сгенерированный компилятором код выбирает его адрес из таблицы и предоставляет необходимую связь.
Приведенная выше схема загрузки и исполнения функций DLL-файла позволяет реализовать защиту на уровне системных сетевых DLLs следующими способами:
Первый способ связан с использованием таблиц адресов импорта (Import Address Table). Для того чтобы найти эту таблицу, нужно сначала найти по сигнатуре ".idata" в таблице секций, располагающейся после заголовка файла, описание секции .idata. Секция.idata содержит информацию о функциях и данных, импортируемых приложением из DLLs. В описании этой секции есть поле, содержащее виртуальный адрес, по которому загрузчик отобразил секцию.idata. Содержимое этой секции начинается с массива структур, по одной структуре, содержащей пять элементов, на каждую DLL. Первый элемент каждой структуры указывает на таблицу имен функций, четвертый является указателем на имя DLL, а пятый указывает на таблицу адресов функций.
Найдя по таблице имен функций имя нужной функции, можно узнать ее адрес в соответствующем элементе таблицы адресов функций, и затем заменить этот адрес на адрес собственного обработчика. Собственный обработчик может располагаться в собственной DLL. После завершения необходимой обработки нужно перейти по сохраненному первоначальному адресу импортируемой функции. Для того чтобы собственная DLL загружалась в адресное пространство любого процесса GUI-приложения, нужно в реестре в ключе HKEY_ LOCAL_MACHINE\ SYSTEM\ Software\ Microsoft WindowsNT\ CurrentVersion \ Windows\APPINIT_DLLS включить имя этой DLL.
В этом способе нужно менять таблицы адресов импорта во всех процессах приложений, использующих системные DLLs. Но обычно процессов в системе не так уж много (несколько десятков). Можно разработать приложение, которое будет получать описатели объектов-процессов, соответствующих защищаемым приложениям, а затем осуществлять чтение и запись в их виртуальные адресные пространства, с помощью функций ReadProssMem() и WriteProssMem().
Секция .idata имеет атрибут "для чтения и записи", поэтому запись в нее возможна.
Второй способ – создание собственной DLL, в которой необходимо заменить только часть функций, непосредственно через которые проходят данные пользователя. Имена этих новых функций должны быть сохранены. При этом новая DLL должна иметь то же имя. Или же в ключе реестра HKEY_LOCAL_ MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ KnownDLLs должна быть строка: "имя заменяемой DLL без расширения" "имя собственной DLL с расширением .dll", тогда система загружает вместо первоначальной DLL собственную библиотеку DLL из каталога, указанного в параметре реестра DllDirectory.
Так как все системные DLL имеют определенные базовые адреса, по которым они проецируются в адресное пространство процесса приложения, то преобразованная DLL не должна перекрывать базовый адрес другой системной DLL.
Третий способ основан на изменении части кода функций (необязательно начальной) вышеперечисленных DLL так, чтобы передать управление (с помощью команд call или jmp) собственному коду, реализующему необходимые функции по защите. Этот собственный код может содержаться в собственной DLL. Это изменение может происходить до загрузки DLL (исправляя DLL-файл во время загрузки ОС с помощью приложения-сервиса), или же можно модифицировать функции DLL после того, как DLL загружена. Этот способ основан на том, что все выполняемые приложения, импортирующие функции некоторой DLL, совместно используют один и тот же код этой DLL, загруженный в оперативную память. И если страницы, содержащие код DLL, изменить, то это изменение коснется всех приложений.
Для реализации этого способа нужно разработать собственное приложение (или сервис), которое будет подгружать системные DLLs в свое адресное пространство. В этом приложении нужно получить адрес начала кода требуемой функции (например, с помощью функции GetProcAddress ("HMfl функции")). Затем в том случае, если команда перехода будет находить не в самом начале, вычислить адрес кода, который будет перезаписан командой перехода, и по этому адресу осуществить запись инструкции JMP или CALL на адрес собственного обработчика, при этом надо сохранить перезаписываемые инструкции.
Но запись в секцию кода не так-то просто осуществить, так как эта секция является исполняемой и предназначена для чтения. Один из способов решения этой проблемы состоит в отображении физического адреса, соответствующего виртуальному адресу региона с кодом на другой регион, в который запись разрешена, и затем осуществить в него запись нужных инструкций. (Получится, что двум разным регионам виртуального пространства соответствует одна и та же физическая память). Но подобные махинации можно проводить только в драйвере, следовательно, для реализации защиты потребуется разработка драйвера. Возможно, что можно обойтись и без драйвера, воспользовавшись тем, что физическая память в ОС Windows NT представляется объектом-секцией, содержащимся в пространстве имен диспетчера объектов под именем \Device\PhysicalMemory. Приложение может спроецировать физическую память в свое адресное пространство и затем манипулировать ее содержимым.
Несмотря на то, что вышеперечисленные способы реализации защиты на уровне системных DLLs, кажутся довольно надуманными и неосуществимыми, подобные программные продукты все же существуют, например PRUDENS SPY product series, реализует технологию перехвата вызовов любых DLLs, в том числе и системных.