вторник, 28 июня 2011 г.

Как поймать то, чего нет?

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

Традиционные способы детекта запуска файла не годились, т.к. они рассчитаны были на то, что сам файл связанный с процессом присутствует на диске. То есть всяческие нотификаторы запуска процесса, детект создания секции, хуки сервисных ф-ций, все это шло лесом, просто изза того, что до них управления не доходило - файла не было, а процесса без файла не существует.

У меня был на руках только путь к перемещенному файлу. Поэтому пришлось придумывать нечто новое. И это новое было придумано.

Решение основывалось на системном механизме, называемом кеш менеджер. Информации по нему достаточно, поэтому описывать целиком не вижу смысла. Основная идея - кеширование файловых потоков, файловые потоки это не только данные файла, но и метаданные ФС, такие как Extended Attributes/Alternate Data Streams (а вот Reparse Point врятли кешируются).

Для моего случая интересен факт того, что каждый раз при 1й записи ( то есть при создании исполняемого файла ), файловая система вызывала
один из 4х интерфейсов кеш менеджера, чтобы закешировать файловые потоки ( предварительно создав и инициализировав CacheMap и прочие внутренние структуры связанные с файлом ). Узнать закеширован ли тот или иной файл можно, посмотрев соот-ю структуру FILE_OBJECT, а точнее поле PrivateCacheMap, если оно не нулевое - файл закеширован.

У кешменеджера есть рабочие потоки выполняющие операции write-behind/read-ahead. Write-behind скидывает содержимое кеша на диск с интервалом 1-3 секунды, если это не сделано явно. То есть можно записать в файл, а данные все равно какоето время будут в кеше, т.е. в виртуальной памяти, а не на диске. Таким образом накапливая данные в кеше, и сбрасывая их спустя время одномоментно, уменьшается количество дисковых операций ввода-вывода(если быть более точным, то уменьшает число позиционирования головок жесткого диска). Собственно это и есть главная ф-ция кешменеджера. Read-ahead в свою очередь выполняет упреждающее чтение файлов, когда инициирована операция чтения.

Также, с кешем связан еще один важный механизм, называемый fast I/O.
Везде, где возможно, чтение и запись в закешированные файлы разруливается через высокоскоростной механизм fast I/O.
Fast I/O означает чтение и запись кешированных файлов без работы по генерации IRP.
Это дает существенное быстродействие в некоторых случаях(пример - memory-mapped files).

Именно fast i/o запросы к ФС при создании процесса для получения метаданных(уже не помню, но скорее всего там идет получение EA) позволили ловить запуск несуществующих процессов.

Конкретно для моей задачи нужно было ловить FastIoQueryOpen в минифильтре.

Эквивалент операции FastIoQueryOpen для минифильтра это IRP_MJ_NETWORK_QUERY_OPEN.
Поэтому регистрируем эту операцию, устанавливаем Pre и Post колбеки.

В Pre колбеке просто возвращаем FLT_PREOP_SUCCESS_WITH_CALLBACK, чтобы вызвался Post колбек, а уже в нем проверяем статус операции
( на момент Post колбека операция завершена ), и если тот равен STATUS_OBJECT_NAME_NOT_FOUND, то просто сравниваем имя файла для данной операции, со списком сохраненных ранее файлов. Если совпало - это запуск процесса.

Таким образом получилось решить эту не совсем стандартную задачу, причем с несильно большим оверхедом по производительности.
Такой способ имеет ограничения, такие как например факт того, что файл может писаться минуя кеш (флаг FILE_FLAG_WRITE_THROUGH), но в моей задаче такого не было.

вторник, 21 июня 2011 г.

hex-rays или собственный декомпилятор?

Наткнулся недавно на ветку http://exelab.ru/f/index.php?action=vthread&forum=3&topic=15481&page=11, в которой веселые ребята собираются реверсить hex-rays (декомпилятор сишного кода ввиде плагина к IDA, если кто не знает).

Собсно маразм данной идеи даже не в том, что нужно реверсить больше мегабайта бинарного кода, а в том, что это вобще предлагается.
Ведь написать аналог hex-rays в сотни раз проще, чем реверсить бинарь, к тому же необязательно делать декомпилятор ввиде плагина и писать на с++, когда есть IDA-python например, питон для подобных целей гораздо предпочтительнее, т.к. придется работать с довольно сложными конструкциями.

Далее я попытался понять, может быть есть какие-то сложности с построением скажем дерева нодов?
Может быть это какаято архи-сложная задача и я чего-то не понимаю?

Открываем свежие версии IDA/IDA-python, смотрим api, видим классы с заманчивыми названиями FlowChart/BasicBlock/GraphViewer.
Таки да, "все уже украдено до нас" (с)
FlowChart возвращает заботливо построенный за нас список BasicBlock's, а BasicBlock содержит помимо node id, start/end адресов для блока, еще и списки из нодов, на которые ссылается текущий и нодов которые ссылаются на текущий.
Таким образом есть вся достаточная инфа для построения бинарного дерева нодов, которое потребуется впоследствии для свертки оного в Сишные конструкции (if/else/switch и т.д.).

Несколько минут шаманства и готов вывод блоков в окно представленное классом GraphViewer:



Таким образом построение нодов если и вызывала какие-то телодвижения на старых версиях IDA/IDA-python, то сейчас делается буквально в несколько строк.

Какие еще проблемы могут возникнуть при написании декомпиля уровня хекс-рейз?

Свертка нодов в if/else? Да ни разу не проблема
Поиск свитчей/циклов/seh'ов? Тысячи раз обсуждалось, куда статей на эту тему, абсолютно ничего сложного
Отслеживание цепочек live-register's? Элементарно делается
Распарсить type library? Тоже ерундовое дело

Даже не знаю что вызовет затруднение, разве что просто сесть и начать?
Имхо, за неделю будет уже что-то подобное хекс-рейзу версии 1.1.
Да, если писать декомпилятор С++ придется изрядно попотеть.
Но не нужно забывать, что hex-rays и сам то С++ непереваривает, это сишный декомпилятор.

четверг, 16 июня 2011 г.

И не баг и не фича

Что же тогда? Архитектурная особенность! Речь пойдет об уязвимостях.

Каждый кто хотя бы как-то относится к computer security знает, что типов уязвимостей довольно много.

Это buffer/heap/integer overflows, format string vulnerabilities, use after free, race conditions и так далее, имя им легион.

Но всех их объединяет одна вещь - так или иначе, они могут быть выявлены автоматически/полуавтоматически, либо на этапе компиляции( компиляторы, статические анализаторы и т.д. ), либо по методу черного ящика ( fuzzing, binary code analysis и т.д. ).

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

Мне вспомнилось всего два примера таких уязвимостей, это:

1) (MS06-001) уязвимость в Windows Metafile files (.wmf) файлах

"Архитектурная особенность" в данной уязвимости позволяет абсолютно документированно содержать код в .wmf файлах и выполнять его, причем сидела эта ф-ция незамеченной со времен Windows 3.0 с 1990го года по 2006й, 16 лет.

2) (MS10-046) уязвимость в .lnk файлах, которую явил миру Stuxnet.

"Архитектурная особенность" в библиотеке shell32.dll приводит к тому, что открываемый ярлык (.lnk файл) приводит к загрузке и выполнению explorer'ом вредоносной .dll(для так называемые dynamic icons) путь к которой содержится в нем же самом.

Эта же особенность, но уже в самом формате .lnk файла позволяет файлу находиться вобще на удаленном компе ( Lnk File Format => File Location Info => Offset to the network volume table => Network share name ) в расшаренных папках.

Такого рода уязвимости будут всегда находиться только человеком, по крайней мере до тех пор, пока не придумают AI :)

p.s. Если вспомните еще какие-либо уязвимости подобного типа - пишите в комментах.