Однажды я столкнулся с задачей, которая была мягко скажем не стандартна. Нужно было ловить запуск несуществующего процесса.
Некое приложение скидывало исполняемый файл на диск и запускало его. При этом исполняемый файл на момент записи перемещался в другое место, и нужно было поймать факт запуска такого несуществующего уже файла на момент обращения исходного приложения к нему с целью создания процесса.
Традиционные способы детекта запуска файла не годились, т.к. они рассчитаны были на то, что сам файл связанный с процессом присутствует на диске. То есть всяческие нотификаторы запуска процесса, детект создания секции, хуки сервисных ф-ций, все это шло лесом, просто изза того, что до них управления не доходило - файла не было, а процесса без файла не существует.
У меня был на руках только путь к перемещенному файлу. Поэтому пришлось придумывать нечто новое. И это новое было придумано.
Решение основывалось на системном механизме, называемом кеш менеджер. Информации по нему достаточно, поэтому описывать целиком не вижу смысла. Основная идея - кеширование файловых потоков, файловые потоки это не только данные файла, но и метаданные ФС, такие как 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), но в моей задаче такого не было.
Некое приложение скидывало исполняемый файл на диск и запускало его. При этом исполняемый файл на момент записи перемещался в другое место, и нужно было поймать факт запуска такого несуществующего уже файла на момент обращения исходного приложения к нему с целью создания процесса.
Традиционные способы детекта запуска файла не годились, т.к. они рассчитаны были на то, что сам файл связанный с процессом присутствует на диске. То есть всяческие нотификаторы запуска процесса, детект создания секции, хуки сервисных ф-ций, все это шло лесом, просто изза того, что до них управления не доходило - файла не было, а процесса без файла не существует.
У меня был на руках только путь к перемещенному файлу. Поэтому пришлось придумывать нечто новое. И это новое было придумано.
Решение основывалось на системном механизме, называемом кеш менеджер. Информации по нему достаточно, поэтому описывать целиком не вижу смысла. Основная идея - кеширование файловых потоков, файловые потоки это не только данные файла, но и метаданные ФС, такие как 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), но в моей задаче такого не было.