Сервисы
Сервисы и драйверы — наиболее практическая часть всего материала.
Сервисы Windows — примерно как демоны в UNIX. Отличаются от обычных программ некоторыми свойствами: в первую очередь, сервисы взаимодействуют не с пользователем, а с другими программами. Главный смысл сервиса в том, что они выступают как расширение функциональности ОС. Например, существуют сервисы для веб-сервера, файлового сервера и т. д.
Сервисы могут запускаться без явного волеизъявления пользователя. Например, при старте системы. Сервисы работают не от имени пользователя, а от имени системы или специального псевдопользователя. Сервисы рассматриваются как доверенная часть ОС, их полномочия почти ничем не ограничены. Иногда службы сами ограничивают свои полномочия, например веб-сервер запускается от имени бесправного пользователя.
Далее только о Windows.
В реестре есть ветка, которая называется база сервисов. Там хранится информация обо всех сервисах, установленных в системе.
HKLM\system\CurrentControlSet\Services
CurrentControlSet указывает на ControlSet###.
ControlSet002 — бекап, создается при выключении.
(В regedit папочки = ключи, файлы справа = значения, имя+данные)
Обязательные значения в описании сервисов:
· Type.
o Kernel driver. Драйвер какого-то устройства, работает в режиме ядра. Является модулем расширения ядра. Может управлять только простым устройством, на котором не поддерживается ФС.
o File system driver. Имеет несколько дополнительных функций. Позволяют в общем дереве ФС (см. ниже) делать новые точки монтирования
o Own process. Это почти обычная программа, работает от имени пользователя (или SYSTEM). Нет оконного интерфейса. Пример: веб-серверы, СУБД и другие тяжеловесные сервисы.
o Share process. Маленькие сервисы, которые неудобно делать отдельными процессами. Dhcp-клиент, netsend и прочее. Каждый процесс создает нагрузку, без share process процессов было бы несколько сотен. Для такие сервисов создаются специальные процессы-контейнеры: services.exe (первый, внутри него работает SCM, диспетчер сервисов) и svchost.exe. В XP их много, т. к. появилась возможность запускать их с разными конфигурациями, например, от имени разных пользователей.
o Interactive. Флаг, может сочетаться с own и share process. До win2003 сервис не может взаимодействовать с графическим интерфейсом. Но, объявив сервис interactive, можно предоставить ему эту возможность. Обычно используется для отладки или хакерами. Начиная с Vista — поддерживается весьма ограничено. Сервис не видит ни один desktop, видит только другие интерактивные сервисы.
· Start — описывает режимы загрузки данного сервиса.
o Disabled. Данный сервис не может запускаться ни при каких обстоятельствах.
o Manual.
o Automatic.
o System (загружаются до automatic)
o Boot (загружаются до system).
(нижние три — только для драйверов)
· ErrorContol — что делать при ошибке.
· Group — сервисы группируются, а для групп назначается порядок загрузки.
· Tag — уточняет режим загрузки внутри группы.
· ImagePath — путь к исполняемому файлу данного сервиса.
· DependOnService — зависимости.
· Любая другая запись допустима.
Устройство ФС (справедливо для win2000):
· \
o Device
§ Cdrom0
§ HardDiskVolume1
o ??
§ C: (ссылка)
§ D: (ссылка)
o Registry
Управление сервисами.
Изначально в Windows была утилита net:
net start
net stop
Начиная с XP (а может и раньше) появилась утилита sc. Она сложна, поэтому при написании софта часто используются специальные загрузчики.
Кратко перечислим основные функции, которые используются для управления сервисами программно.
OpenSСManager()
Возвращается SC_HANDLE или NULL. Закрывается функцией
CloseServiceHandle()
Необходимо указать доступ. Обычно указывают доступ
SC_MANAGER_ALL_ACCESS
Создается новый сервис функцией
CreateService()
Открыть существующий сервис с помощь этой функции нельзя, открывается он функцией
OpenService()
Удаляются сервисы функцией
DeleteService()
Функция примечательна тем, что нужен handle для удаления, т. е. нужно открыть сервис. Но открытый сервис удалить нельзя. Поэтому сервис всего лишь помечается для удаления, а удалится только тогда, когда на него закроется последний handle.
ControlService()
позволяет остановить, обновить конфигурацию и т. д.
srvany — программа позволяет запускать в качестве сервиса любую программу. Сервис должен сообщать системе о том, что функционирует он нормально, обычная программа будет закрыта секунд через 20.
В сервисе должен быть обработчик запросов. Эта функция в ходе инициализации сервиса должна быть зарегистрирована в системе.
Сервис должен иметь функцию
ServiceMain()
(Функция main() или winMain() все равно должна быть)
Эта функция делает 2 вещи. Во-первых, она регистрирует обработчик управляющих сигналов, во-вторых, она вызывается функцию
SetServiceStatus()
в которой сообщает ОС, что сервис стартован (а также говорит, какие сигналы готов обрабатывать сервис — например, никакие).
И третье, самое важное, вызывается функция
StartServiceCtrlDispatcher(),
которая никогда не возвращает управления. В вечном цикле внутри она вызывает ServiceMain(), а затем ждет сигнала, чтобы вызывать обработчик.
· Запустить отладчик из-под Администратора, сделать attach to process.
· Системный вызов DbgBreakPoint().
Драйверы
Драйвер — особый вид сервиса. Драйвер работает в нулевом кольце защиты.
Начиная с 256-го процессора, существуют функции защиты памяти. Главная идея в том, что не каждая программа имеет доступ ко всей памяти, ко всем портам, т. е. ко всем аппаратным ресурсам.
Имеется 2 кольца защиты. Кольцо защиты привилегированных программ и непривилегированных программ. Непривилегированные программы (user mode) не могут обращаться к портам аппаратуры, не могут узнавать и модифицировать таблицы прерываний, они не могут управлять распределением памяти и могут обращаться только к той памяти, которая им явно позволена.
Kernel mode — обычно в нем работает только ядро ОС. Программы в kernel mode делают все, что хотят.
В процессорах Intel — 4 кольца защиты. Два кольца между kernel и user mode — сейчас не используются (используется в OS/2).
«Драйвер принтера» не является драйвером, он является библиотекой. Драйвер принтера не работает в kernel mode, а драйвером называется по чисто исторической причине.
Зачем нужны драйверы? Во-первых, чтобы управлять нестандартным железом. Чтобы управлять «железкой», необходимо соответствующим образом реагировать на сигналы, посылать сигналы, понимать формат данных и т. д. Есть где-то около пятидесяти стандартных операций.
Для некоторых видов железок существуют соглашения, для таких устройств не нужны драйвера (приводы, мыши и т. д.).
Иногда рассматривать некую виртуальную сущность, будто бы это была железка (разделы жесткого диска, большинство сетевых протоколов). Отсюда возникла концепция драйвер логического устройства. Также, в Windows есть понятия физической и логической клавиатуры. Смысл физически различных клавиатур инкапсулируется в понятия логической клавиатуры. В современных ОС логических устройств обычно больше чем физических.
Драйверы могут образовывать цепочки. В цепочке более низкие драйверы обслуживают запросы более высоких. Ниже самого низкого драйвера лежит железо.
Пример:
Прикладные программы |
Ядро |
Логический диск |
Физический диск |
Железка |
Существует множество видов драйверов.
Есть два интерфейса для работы с драйверами. Первый — интерфейс сервисов. Поддерживает команды «инсталлировать», «стартовать», «остановить» и т. д.
Каждый драйвер обязан поддерживать хотя бы одно устройство.
CreateFile()
Можно вместо файла указать устройство и оно откроется (создавать устройства таким образом нельзя).
FILE_SHARE_READ | FILE_SHARE_WRITE
Позволить другим программа использовать устройство.
SetFilePointer()
Эта функция перемещает указатель текущей позиции в файле. (Не все устройства это поддерживают, только накопительные.)
DeviceIoControl()
Всё, что не чтение и не запись — управление (извлечь диск, отформатировать хард, прочитать геометрию жесткого диска).
Как писать драйвер.
Физически драйвер представляет собой извращенный вид библиотеки. Он загружается в адресное пространство ядра.
Структура адресного пространства процесса Win32:
User memory | Kernel memory |
Всего 4 ГБ. 64 КБ после и до NULL — запрещены.
У всех процессов вторая половина (kernel memory) — одинакова. Ядру не нужно думать, в адресном пространстве какого процесса оно сейчас находится.
Драйвер грузится во вторую половину. Драйверы имеет некоторые отличия от стандартных библиотек:
· Драйверы не могут использоваться WinAPI, только NTAPI. Весь код WinAPI грузятся в user memory.
o NTAPI — в ntdll.dll. Поверх, по сути, эмулятор Windows.
WDK (DDK) —продукт для создания драйверов (маны, примеры и т. д.). Есть build, утилита для компиляции драйверов.
Главный заголовочный файл — ntddk.h.
Отлаживать драйвер следует на виртуальной машине. Фатальная ошибка — смерть всей системы целиком (синий экран).
Windbg (из пакета Debugging tools) — для отладки. В виртуальной машине создается COM-порт. В boot.ini дописывается /debugport=com1 и /baudrate=115200 к записи соответствующей операционной системы.
Для драйверов есть функция для вывод информации на консоль отладчика:
DbgPrint();
Начиная с Vista — только DbgPrintEx(). (Старая функция не работает.)
Все драйверы начинаются с:
NTSTATUS DriverEntry(PDRIVER_OBJEACT pDriverObject,
PUNICODE_STRING RegistryPath);
ntstatus. h — коды ошибок.
PUNICODE_STRING:
2: длина буфера | 2Б: длина строки | 4Б: указатель | строка |
Не обязана заканчивать нулевым символом, более того, может содержать нулевой символ в середине.
\Registry\Machine\System\ — конфигурации драйверов.
Каждый драйвер должен иметь хотя бы одно устройство. Усройство создается с помощью
IoCreateDevice()
Параметры:
· Имя: \Device\имя
· Device extension, нужен, когда драйвер может обслуживать несколько разных устройств (например, драйвер ФС).
· выдается DEVICE_OBJECT.
Следует делать IoDeleteDevice().
Чтобы обращаться к устройство через WinAPI нужно создать линк \??\link.
IoCreateSymbolicLink()
Delete()
Запросы, которые все драйверы обязаны обрабатывать:
· Create
o Например, обеспечивает монопольный доступ.
· Close
Часто, эти обработчики пустые.
Еще:
· Read
· Write
· DeviceControl
Прототип:
NTSTATUS XDispatchY (PDEVICE_OBJECT pDeviceObject, PIRP pirp)
IRP — пакет.
IoStatus. Status. Information
следует возвращать ноль.
IoCompleteRequest
завершает обработку запроса.
Есть такое понятие — IRP stack. Необходим, когда в обработке запроса участвуют несколько драйверов.
Перехват информационных потоков.

mov eax, номер
mov edx, p
call edc
ret число1
...
p:
sysenter/syscall/int 2E
ret число2
Для sysenter и syscall используется регистр STAR MSR. Указывает на KiSystemService.
Фрагмент:
mov rax, a
sub rax, длина кадра стека
jmp rax
...
mov rax, [rsi+4]
mov [rdi+4], rax
mov rax, [esi]
mov [rdi], rax
Код для копирования N байт сам занимает N байт.
KeServiceDescriptorTable — экспортируемая переменная ядрая. Содержит указатель на SST (System Service Table).
SST
Service Table |
LowCall = 0 |
HiCall |
ServiceTable
... |
... |
(HiCall+1 штука)
Для всех функций в NT API существует два имени: NT... и Zw...
В пользовательском режиме NT=Zw. Однако в режиме ядра NT — функция-сервер, Zw — функция клиент. Когда приходит управление из прикладной программы, вызывается функция-сервер, но для своих нужд вызывается функция-клиент. Zw включает в себя NT. Вызывать надо Zw, перехватывать — NT.
Если драйвер запишет в нужный индекс SST свою функцию, то система вызовет ее. Способы поиска функции в SST:
· Обратиться по имени, выяснить адрес (функция должна быть экспортируема и быть в либах).
· Руками найти адрес функции в памяти (функция должна быть экспортируема).
· Функция может быть не экспортируема. Можно извлечь номер прямо из машинного кода (обычно из клиентской программы)
Программная закладка
Это программа или фрагмент программы, скрытно внедряемый в атакуемую систему, для осуществления НСД к ее ресурсам.
Две четко выраженные стадии: внедрение и функционирование. Этим закладка отличается от других вредоносных программ.
Слово «программа» не означает, что это экзешник, это может и библиотека, и драйвер, и веб-страница, и макросы в Ворде.
НСД мы понимаем расширенно, не только как чтение, но и изменение, удаление и нарушение доступности.
Начиная с 2002 года, НСД без программной раскладки считается извращением (раньше, например, пытались создать пользователя).
Закладки хороши тем, что позволяют не только эксплуатировать существующие уязвимости, но и создавать новые.
Закладки позволяют эксплуатировать кратковременные уязвимости.
Закладка может активно противодействовать методам ее обнаружения.
Классификация (модели):
· Наблюдатель.
o По сути, обычная программа.
o Оказывает воздействия только по команде нарушения.
o Имеется сервер на зараженной системе и клиент на машине нарушителя.
o Умеет:
§ Работа с ФС.
§ Работа с реестром.
§ Управление списком процессов.
o Есть хорошая идея — ядро + плагины. Ядро умеет скачивать, устанавливать, удалять и управлять плагинами.
o TCP. Открытие слушающего сокета служит сигналом для фаервола, поэтому нужно обходить фаерволы или отключать их.
o UDP. Можно эмулировать TCP на более высоком уровне. Впервые UDP использован в Cult of the dead cow. Дырку закрыли, но UDP продолжают использовать.
o NPFS. Надстройка над SMB, используется в файловых и принт-серверах от Микрософта.
o HTTP. Веб-сервер на зараженной машине — подозрительно. Клиент — надо ждать соединений, к тому же палится адрес злоумышленника (эту проблему можно решить созданием ретранслятора, что тоже очень неудобно).
o Недостаток: неудобно работать, особенно с множеством зараженных.
· Перехват.
o Более глубоко интегрирована в систему, перехватывает информационные потоки.
o Является наиболее популярной.
o Перехватывает
§ Аутентификационные данные.
§ Документы
§ Сетевой трафик.
o Перехватчики паролей — древнейший тип. Бывают трех родов:
§ 1 рода — спуффер. Ложное приглашение на ввод пароля.
· XSS — идея в том, что юзеры подсовывают ссылку на один сайт, а он попадает на другой. Или на тот же, но с другой формой.
§ 2 рода — кейлогер.
· В основном под Винду.
· Меняет механизм локализации клавиатуры.
· В Винде механизм основывается на hook’ах.
· Есть ограничения, связанные с мандатным разграничением прав.
· Продвинутая идея — брать не весь клавиатурный ввод, а только ввод в определенные окна определенных программ. Плюс собирать дополнительные сообщения, типа изменения размера окна и т. д. Можно сформировать критерии о том, что сообщение пришло в программу, которая интересует кейлогер.
· Достоинства: легко делается, работает надежно.
· Недостатки: хуки работают только на том же десктопе, пользователи делают опечатки (это больше касается текста, а не паролей), сложно замаскировать.
§ 3 рода. Непосредственный перехват на стадии аутентификации.
· Подмена программы login в Юниксе.
o Перехват документов
§ Перехват информационных потоков — крайне сложен на практике.
§ File mapping — перехват при свопинге, тоже не реализуется нормально.
§ В целом — ничего нормально работающего не существует.
§ Реально работающий способ — просмотр журнала недавно открытых документов (лог пишется функцией shOpenDocument), а также просто просматривать популярные папки, типа Мои документы или Рабочий стол. Обнаружить такую закладку очень сложно, только по сторонним признакам
o Перехват сетевого трафика
§ Снифферы. Практически невозможно скрыть.
§ Несанкционированный перехват трафика. Нормально работает только Микрософтовский фаервол :)
§ Короче, нет механизмов, которые одновременно скрытно и надежны.
§ Частные решения:
· Электронная почта. Можно просто найти хранилище почтовой программы. (В Аутлуке, правда, в файле лежит что-то типа FATоподобной ФС.) Есть MAPI (Messaging Application Programming Interface).
· Клиент ведет лог (аська, твиттер и т. д.)
· Веб-интерфейс. Решается тоже просмотром файлов на диске.
· Искажение
o (Закладки подменяет информацию, выходит не то, что входит. Пример — драйвер, которым мы писали в начале семестра.)
o Основное назначение — стелс-технологии. Закладка скрывает от пользователя свое присутствие в системе. Простейший пример — скрытие файла путем перехвата получения списка файлов директории. Аналогично можно скрывать процессы, ключи реестра и т. д. Причем перехват SST — не лучший вариант.
o Есть некоторые типичные проблемы, которые палят закладку. Причем не всегда понятно, что скрывается, но понятно, что что-то скрывается.
§ Проблема общей статистики. Например, скрыли процесс, в списке его нет. Однако не сойдется сумма по процессорному времени (небольшое отклонение, правда, можно списать на ошибку округления).
Еще ярче эта ситуация проявляется с сетевым трафиком.
Для борьбы с этой проблемой используют «оракулов», которые не дают закладке занимать много ресурсов.
§ Проблема индусских мониторов. Закладка тестируется на стандартных мониторах, однако существует и всякие другие, например, идущие в комплекте с китайским железом. Они используют нестандартные способы снятия статистики.
§ Rootkit revealer — программа для выявления стелсов. Сканирует реестр и ФС дважды — с помощью штатных средств и на более низком уровне. Потом ищет разницу. Противодействие есть — не скрывать закладку от таких программ.
o Режим гипервизора — помещение ядра ОС в песочницу. В некоторым смысле, текущая система становится виртуальной. Таких закладок пока нет, ибо написание таковых по сложности сравнимо с написанием ядра ОС.
o Про сеть. Работает идея проксирования. Закладки встраивается в сетевую подсистему ОС. Весь трафик перенаправляется на специальную машину.
o Несколько историй про перехват экзотических информационных потоков:
§ Девяностые годы, банки. Через ФИДО распространилась «ускорялка» (Турбокриптон) для криптографических преобразований.
§ Система проверки ЭЦП. Закладка сканировала видеопамять и стирала буковки «НЕ». Слово «НЕКОРРЕКТНО» превращалось в «КОРРЕКТНО».
§ Внедрение в браузер. Находим ссылки Google ads и меняем на ссылки другого рекламного ресурса.
§ Повышение привилегий. Юзер заходит на машину с закладкой, дает закладке специальную команду — и ему повышаются права.
Мощная идея: подправить монитор безопасности объекта в ядре, чтобы права доступа контролировались по другим правилам (как зарегистрировался в закладке, тот и имеет права).
Но со временем стало понятно, что лучше сделать так, чтобы закладка исполнялось из под прав суперпользователя, а пользователю вообще не надо заходить в систему стандартными средствами.
Внедрение программных закладок
Если система не имеет уязвимости, то внедрить закладку невозможно.
На практике уязвимости обычно есть. Классы:
· Уязвимость ПО.
o Переполнение буфера. Код затереть сложно, обычно затирают указатели на код.
Борьба:
§ Рэндомизация размещения в памяти.
§ Запрет исполнения кода из области данных.
o Забытые проверки.
· Уязвимости политики безопасности.
o Человеческий фактор.
o Не человеческий фактор.
Теперь о процедуре внедрения. Классификация по методам:
· Маскировка закладки под прикладное ПО. Бывает с полезным функционалом, бывает — и без него. Может быть не только программа, но и документ с макровирусом. Реже встречаются документы с эксплойтом.
o Недостаток — на пользовательском уровне много ограничений. Хотелось бы драйвер или сервис. Например, для стелс-технологии. Борются с этим многоступенчатым внедрением. Закладка сама порождает драйвер, но для этого ей надо запуститься из-под прав администратора. Если прав нет, то можно воспользоваться дыркой. Иначе — FAIL.
· Закладка, внедренная как системное ПО. Может практически все. Необходимы права админа. Обычно используется на очередной стадии внедрения. Занимается стелсом.
· Метод подмены (устарел). Заменяет некий важный файл, например, программу login. Популярен в мире open source. Можно подменять драйвер null, например, это просто даже в системах с закрытым кодом. Можно подменять драйвер bip (пищащий спикером). Две причины смерти метода:
o Раньше было сложно маскировать новое (из-за малого количества), теперь — просто (никто не заметит 71-го драйвер).
o WFP (Windows File Protection). Теперь эту аббревиатуру используют в другом смысле, но неважно. Изначально WFP предназначался для борьбы с dll-hell, когда инсталлер или деинсталлер удаляет или даунгрейдит какую-то dll. Как устроена WFP: каждый пакет обновлений Windows сопровождается пакетом цифровых подписей. Они хранятся с расширением cat в специальной директории. Не работает в безопасном режиме, поэтому кряки иногда его хотят. WFP ждет изменений в контролируемых директориях (типа system32). При каждом изменении WFP просыпается и смотрит, что изменилось. Если это неконтролируемый файл — снова засыпает. Если же контролируемый, то WFP подсчитывает цифровую подпись и сверяет ее. Если файл изменен легально, то cat-файл уже был изменен. Способ пресечения неправильных изменений — взять из system32\dllcache.
Как раз к моменту появления WFP научились писать стелс-драйвер.
На смену пришел WRP (Windows Resource Proteciton). Защищает не только файлы, но и ключи реестра! Устроена совершенно иначе. Есть специальный псевдоюзер TrustedInstaller. Под ним нельзя войти, но это и не группа, в нее нельзя включать пользователей. Это некий голый мандат. Этот мандат имеет только сервис trustedintstaller (он же windows module installer). Теперь изменения вносятся только через windows module installer.
· Ассоциирование. Из двух программ получается одна, которая запускает сначала вторую, потом первую.
o Прямое ассоциирование. На диске с файлами. Основной метод в девяностых (через дискеты). Потом ушел, т. к. появились новые, более простые методы, это случилось где-то в 2001-м году. Макровирусы продержались чуть дольше.
По сути — это подмена.
o Косвенное ассоциирование (инжектирование). Ассоциируется в памяти с загруженными образами программных файлов. Достаточно просто запихнуть код в адресное пространство заражаемого процесса.
Достоинства:
§ Такую закладку крайне сложно найти.
§ Имеет полный доступ к зараженному процессу — это второе назначение данного типа внедрение. Классический пример — инжектирование в браузер.
Недостатки:
§ Работает до перезагрузки. С этим можно не бороться вообще (на сервере это почти не нужно), а можно инжектироваться еще раз (если дырку не закрывают). Однако обычно надо возобновить действие, это делается с помощью многоступенчатой закладки, это позволяет прятать закладку даже без стелс-технологий.
Сегодня используют только для доступа к внутренним данным конкретных программ, но не как самоцель.
Выявление программных закладок
Уровни защиты:
· Организационные меры.
· Принцип минимизации полномочий (не сиди под рутом!).
o Windows: MIC (мандатный контроль целостности). Каждый выполняющийся объект имеет мандатный уровень: обычный, повышенный, высокий. Есть некоторые привилегии, которые доступны только на повышенном и высоком уровне. Например, отладчик без подъема на высокий уровень не сможет себя зарегистрировать как системный.
Обычно переход с уровня на уровень запрещен, подъем происходит только при старте. Есть способ сказать, что программа может быть запущена только на высоком уровне мандатной целостности.
o UAC (контроль учетных записей) — барьер между уровнями (раздражающие сообщения). Окошка UACа защищают по тому же принципу, как и окошко логина. Для UACа выделен отдельный десктоп, обоими которого является скриншот основного десктопа.
· Минимизация ПО. MSBlast работал через файловый сервер, который можно было просто отключить.
· Своевременное обновление. Обычно в момент эпидемии заплатка уже существует. В XP — необходимо, в Висте и Семерке — можно и не включать.
· Пакетный фильтр. Защищает от:
o Левого трафика, идущего извне. Однако не фильтрует то, где пока не обнаружены уязвимости.
o Закладок, уже поселившихся в системе. Это обходится инжектированием в браузер или в другую доверенную программу. Существуют и более жесткие способы, но заточены под конкретный файервол.
В XP простой и тупой файервол, можно пользоваться. В Висте и Семерке файервол на основе используется WFP (Windows Filtering Platform), еще сыроват.
· Антивирус. Нужен, если пользователь неадекватен или есть ценная информация.
o Сканирование. Антивирус осматривает объект и определяет, вирус ли это.
§ Сигнатурное. Сигнатуры — некий критерий, позволяющий определить, является ли данная программа вирусом или зараженной вирусом (некий участок тела вирус а + где искать). Существенным достоинством антивируса является надежность данной операции.
§ (Недостатки):
· Вирусов очень много, на все — не проверишь.
· Обновление баз.
· Порождает небольшие проблемы — лаги.
· Старые вирусы удаляются из баз.
· Раз в несколько лет появляются новые типы вирусов, с которыми антивирусы еще не умеют бороться. Классический пример — макровирусы. Ну, или CodeRed — вирус, который живет только в памяти.
§ Эвристическое.
· E8[call a; a: ] — Касперский сразу подозревает неладное.
· Отладочные функции.
· В ДОСе — константы типа «command.com».
· Полиморфные преобразования.
· Подозрительные упаковщики.
· — Этот способ, на самом деле, почти не работает, однако может найти какой-то новый вирус или вирус, восставший из мертвых. Плюс эвристически анализ можно запутать:
o push c
ret
o xor eax, eax
je a
b: jmp b
a:
o Мониторинг (проактивная защита).
§ Пытается оценивать программы в динамике, т. е. наблюдает за действиями программы, реагируя на определенные информационные потоки. Примеры: форматирование жесткого диска, рассылка писем.
§ Наиболее эффективно работает при инжектировании кода.
§ Эффективный метод, так как нет нормальных путей обхода этого метода.
§ Главный недостаток — ложные тревоги.
o Контроль целостности. Сейчас почти не применяется, т. к. этим занимается сама ОС.
§ Сейчас почти нереально осуществлять: куча файлов, автоапдейты.
§ Более разумная система — контроль целостности конфигурации, а не файлов.
§ Сейчас если и делают контроль целостности программного продукта, то на уровне самого этого продукта. Но это делается редко (ОС, антивирусы, программы, защищенные от копирования).
· Метод программных ловушек (экзотический) — honey pot.
o Делаем одно привлекательное для злоумышленника и защищаем его существенно сильней.
o Продвинутый вариант — виртуальные хосты.
o Требует трудолюбия от администратора.
o Пример: SDInet (ловили немецкого хакера).
Переполнение буфера
Конечная цель — перезапись кода.
Типичный размер — порядка килобайта. Нормальный код туда не разместишь, но всегда можно просто подключиться к серверу и скачать полноценную программу.
Обычно переписывается не сам код, а указатель на код.
Переполнение буфера стека
Кадр стека обычно выглядит так: локальные переменные, старый ebp, адрес возврата, параметры.
Естественно, нас интересует адрес возврата. Переписываем адрес возврата и память далее, причем так, чтобы адрес возврата указывал на следующие за собой данные (на самом деле, уже код).
Нужно узнать абсолютный линейный адрес этого места возврата. Как?
· Иногда можно узнать априорно. Если программа устроена не очень сложно, то ее стек обычно размещается по одним и тем же адресам памяти (если, конечно, в ОС нет специальных средств борьбы). Однако, в зависимости от самого хода работы программы, функция может вызываться на разном уровне вложенности, что будет влиять на адрес в стеке. Поэтому требуется, чтобы программа была максимально простой, например, требуемая функция вызывается из main(), причем в одном месте.
· Если же заранее нельзя ничего предсказать нельзя, то можно действовать перебором возможных вариантов. Варианты — это и есть те самые «волшебные числа». В случае неверного волшебного числа, если есть try, то просто помирает поток, но чаще try нет, а значит, программа падает. Если сервис сам перезапускается — это больше счастье для нарушителя.
· Иногда можно предсказать адрес с точность до нескольких байт. Тогда вставляем много команд nop, а переход делаем в центр области nop’ов.
· Если абсолютно невозможно предсказать адрес в стеке, то адрес возврата устанавливается в какой-то другое левое место, где лежит команда jmp esp. esp как раз указывает на адрес сразу за адресом возврата в стеке. Нормальный программист и компилятор никогда не вставит такую команду (jmp esp), однако для данных такой код (FFE4) вполне обычен. Таких мест в памяти полно, всевозможные таблицы адресов и др.
Таким образом, вместо магических чисел можно использовать адреса, по которым лежит FFE4. Зависимость будет не от программы, а от какой-либо системной библиотеки. Причем надо выбирать такие библиотеки, которые не зависят от локализации.
· Аналогично предыдущему пункту, можно использовать команду call esp.
· Иногда можно предсказать значение регистров. Например, МСБласт использовал jmp ebx (так случайно совпало).
Хорошо бы, чтобы эксплойт завершался корректно.
· Если программа простая, можно восстановить разрушенный стек. Это бывает редко.
· Если стек восстановить нельзя, то лучшее, что можно сделать — завершить текущий поток.
Переполнение буфера в куче
Второй вид переполнения — переполнение буфера в куче.
С точки зрения менеджера памяти, куча представляет собой единый блок памяти. Если в куче переполнится буфер, то никакой ошибкой не произойдет, данные просто затрут то, что идет дальше. Ошибка произойдет, только если мы случайно оказались в конце кучи.
В куче очень редко бывает код, только в извращенных случаях, плюс надо отключить защиту. Но можно найти указатели на код. В основном это происходит при ООП, когда используются виртуальные методы.
Однако при разном запуске программы распределение кучи случайно, поэтому чаще всего эксплуатация уязвимости просто невозможно.
Если программа устроена просто и почти ничего не делает (какой-нибудь богом забытый сервис), то часто можно достаточно точно предсказать распределение памяти такого процесса.
Можно запустить программу специальным образом, чтобы повысить вероятность удачного переполнения.
Чтобы переполнение сработало, требуется переполнить как можно больше.
В кучу кладется поле из nop’ов и волшебных чисел.
Целочисленные переполнения
Массив с указателями на код. Передается out of range индекс. Редко удается.
В пакете сообщается размер буфера неравный размеру буфера. Удается чаще.
Пример целочисленного переполнения в чистом виде: строим из 2 гигов точек многоугольник. Получается ноль вершин, и в мизерный буфер пишется 2 гига информации.
Борьба с переполнениями
Сначала пытались обучить программистов, но это ничем хорошим не кончилось :-).
Первая идея. Security cookie. Только для стека. В программе заводится специальная глобальная переменная Security_cookie, при старте в нее записывается некое случайное число.
В начале функции вычисляется некая функция от SC, esp и ebp. Кладется в стек перед локальными переменными. При переполнении стека SC (точнее, функция от него) точно затрется. Перед ret проверяется, на месте ли SC (точнее, функция от него).
Развитие этой защиты — функция chkstk(), проверка, находится ли esp, ebp в разумных пределах. (Обычно только для дебага.)
Бороться сложно, для этого нужно знать значение глобальной переменной.
Проблема в том, что не все функции не используют security cookie в силу исторических причин. Стек переполняют в функции, которая использует SC, но срабатывает переполнение во вложенной функции, которая SC не использует.
Также не защищает от переполнения в куче.
Data Execution Prevention. Запрет на исполнение данных.
Сейчас все ОС различают методы read и execute.
Однако многие программы падают, если работают DEP. Соответственно, теряется совместимость со старым софтом.
Есть способы обхода.
· Можно вообще не вставлять код в эксплойт, нужно записать в стек всего три числа: адрес LoadLibrary, адрес ExitThread, адрес строки. При ret управление передается в LoadLibrary. Теперь можно загрузить свою библиотеку (но нужно иметь доступ к ФС, чтобы все это имело смысл).
· Переполним так: адрес Virtual Protect, адрес эксплойта, параметры Virtual Protect. Отключаем DEP, вызываем эксплойт.
Рандомизация распредления адресного пространства.
Волшебные числа практически теряют смысл.
Мобильный код
Бинарный машинный код, который сохраняет свою работоспособность, будучи скопированным в любой участок памяти в разумных пределах.
Нужен он в тех случаях, когда создается программная закладка в режиме инжектирования или косвенного ассоциирования. А также когда нужно подсунуть закладку в какой-то процесс (например, заменять рекламу в браузере).
Две подзадачи: найти процесс, внедриться в него.
Процессы в Windows идентифицируются PID’ами. Начиная с 2000-го — кратен четырем. Можно считать, что PID случаен. Имя процесса — его атрибут, а не идентификатор (что логично).
Существует 4 способа поиска PID’а по имени:
· Существует тайный ключ HKEY_PERFORMANCE_DATA. Редактор реестра его не показывает. Это псевдоключ, он не существует в виде файла, а генерируется динамически. Это некий аналог /proc в Линуксе. Работать с этой штукой непросто.
При этом происходит колоссальный расход памяти. Этот способ плохой, хотя и рекомендован Микрософтом.
· Существует системный вызов NtQuerySystemInformation(). Для типа информации «5» выдается полный список процессов. В каждой структуре есть PID (тут он называется handle) и ImageName.
Функция NtQuerySystemInformation() странно ведет себя, если ей дали маленький буфер, причем поведение разнится от версии к версии.
Хедеры Микрософта скрывают некоторые важные поля, лучше найти хакерские реализации.
Это правильный способ.
· Функции EnumProcesses(), EnumProcessModules(). Первая выдает список PID’ов, а вторая выдает список загруженных программных модулей (библиотек и т. д). Можно найти требуемое перебором.
Тяжеловесно, плохо.
· Тупо пытаемся внедрить во все подряд, пока не получит. Несмотря на тупость, этот способ работать. Это единственный способ, который работает в не-NT Windows.
Когда мы узнали PID, делаем OpenProcess() (уазываем PID и права, получаем handle). Так можно открывать только свои программы (за некоторым исключением). Привилегия Debug дает право открывать любой процесс в системе. Rtl???Privilege — можно включить себе привилегию Debug.
Далее. VirtualAllocEx(). Выделяет блок памяти в требуемом процессе. Память выделяется не прицельно, а где получится.
WriteProcessMemory(). Копирование происходит через пространство ядра.
CreateRemoteThread(). Стартует поток в адресном пространстве требуемого процесса.
В мобильном коде нельзя использовать глобальные переменные. Однако существует масса глобальных переменных, которые программисту таковыми не кажутся (например, текстовые строки).
Решения проблем со строковыми константами (в хронологическом порядке):
· abcde[0] = ‘a’;
abcde[1] = ‘b’;
abcde[2] = ‘c’;
В этом случае символ будет засунуть компилятором непосредственно в машинную команду. Но это неудобно и затратно по ресурсам.
Можно завести в стеке структуру GLOBAL_DATA и передавать указатель на нее во все функции. Это работает, если код — однопоточный.
Чтобы не инициализировать так долго (а это еще и компилируется в жуткий машинный код), можно сделать так:
((PDWORD)abcde)[0] = 0x;
((PDWORD)abcde)[1] = 0x65;
· Можно через VirutalAllocEx выделить два буфера — один по код, один под строки. После чего адрес второго буфера как-то записывается в первый.
Еще проблема: адреса статически импортированных функций. Решение простое: использовать только динамический импорт.
Также не следует использовать операторы работы и исключениями, ибо создается глобальная таблица исключений.
Callback’и — функции, которые принимает на вход адреса других функций. Можно в проекте жестко задать адрес загрузки, но это непросто и не всегда будет работать. Чтобы выяснить базу загрузки можно создать там функцию (большинство компиляторов сохраняет порядок функций).


