ВступПри адмініструванні інформаційних ресурсів з обмеженим доступом до контенту, наприклад сервери компаній, установ, організацій що містять конфіденційну, комерційну, та ін. секретну інформацію, а також для відслідковування підозрілої вірусної активності в подібних система часто постає задача моніторингу усіх файлових операцій (звернення, створення, копіювання, переміщення, видалення файлів)Мета даної статті – зробити найбільш повний аналіз звернень до файлової системи.I. Перехоплення звернень до файлової системиФайлова система – це спосіб зберігання і організації доступу до даних на інформаційному носії або його розділі. Наявність файлової системи дозволяє визначити, як називається файл і де він знаходиться. Файл - це іменована і впорядкована група окремих послідовностей байтів, які мають постійне місце зберігання[1].1.1.Спосіб фіксації звертання до файлової системиУ результаті дослідження можливостей ОС Windows було знайдено два способи фіксації звертань до файлової системи
1.2.Принцип роботи за допомогою перехоплення виклику API-функційПеред розглядом принципів перехоплення засобів інтерфейсу необхідно коротко і спрощено розглянути принцип виклику функцій, розміщених в DLL. Відомо два базових способи: 1.2.1. Раннє зв'язування (статично імпортовані функції)Цей метод заснований на тому, що компілятору відомий перелік імпортованих програмою функцій. Спираючись на цю інформацію, компілятор формує, так звану таблицю імпорту EXE файлу. Таблиця імпорту – це особлива структура (її розташування та розмір описуються в заголовку EXE файлу), яка містить список використовуваних програмою бібліотек і список імпортованих з кожної бібліотеки функцій. Для кожної функції в таблиці є поле для зберігання адреси, але на стадії компіляції адреса невідома. У процесі завантаження EXE файлу система аналізує його таблицю імпорту, завантажує всі перераховані в ній DLL і заносить до таблиці імпорту реальні адреси функцій цих DLL. У операції раннього зв'язування є істотний плюс – на момент запуску програми всі необхідні DLL є завантаженими, таблиця імпорту заповнена, і все це робиться системою без участі програми. Але відсутність в процесі завантаження зазначеної в його таблиці імпорту DLL (або відсутність в DLL необхідної функції) приведе до помилки завантаження програми. Крім того, дуже часто немає необхідності завантажувати всі використовувані програмою DLL в момент запуску програми. На Рис. 1. показаний процес раннього зв'язування. В момент завантаження відбувається заповнення адрес таблиці імпорту (крок 1). В момент виклику функції з таблиці імпорту береться адреса функції (крок 2) і відбувається виклик функції (крок 3)(рис.1);
Рис.1. Виклик функції
1.2.2. Пізнє зв'язуванняВідрізняється від раннього зв'язування тим, що завантаження DLL проводиться динамічно за допомогою функції API LoadLibrary. Ця функція знаходиться в kernel32.dll, тому, якщо не вдаватися до хакерських прийомів, то kernel32.dll доведеться завантажувати статично. За допомогою LoadLibrary програма може завантажити необхідну їй бібліотеку в будь-який момент часу. Відповідно для отримання адреси функції застосовується функція kernel32.dll GetProcAddress. На Рис.1. крок 4 відповідає завантаженні бібліотеки за допомогою LoadLibrary і визначенню адрес за допомогою GetProcAddress. Далі можна викликати функції DLL (крок 5), але зазвичай при цьому таблиця імпорту не потрібна. Щоб не викликати GetProcAddress, перед кожним викликом функції з DLL програміст може одноразово визначити адреси, функцій, що цікавлять його і зберегти їх в масиві або деяких змінних. Незалежно від методу зв'язування системі необхідно знати, які функції експортує DLL. Для цього у кожної DLL є таблиця експорту - таблиця, в якій перераховані експортовані DLL функції, їх номери (ординали) і відносні адреси функцій (RVA). |

Рис.2. Взаємодія функції-перехоплювача та коду, що викликає API
У цьому випадку модифікується машинний код, відповідальний в прикладній програмі за виклик тієї чи іншої функції API. Це методика складна в реалізації, тому існує безліч мов програмування, версій компіляторів, і програміст може реалізувати виклик API функцій різними методиками. Але теоретично подібне можливе за умови, що впровадження буде йти в заздалегідь задану програму відомої версії. У цьому випадку можна проаналізувати її машинний код і розробити перехоплювач (рис.2).

Рис.3. Схема модифікації таблиці імпорту
Дана методика описана в книзі Ріхтера і є однією з класичних. Ідея методу проста – функція-перехоплювач знаходить в пам'яті таблицю імпорту програми і виправляє адреси необхідних їй системних функцій на адреси своїх перехоплювачів (попередньо зберігаючи правильні адреси). У момент виклику API функції програма зчитує її адресу з таблиці імпорту і передає за цією адресою управління (Рис.3). Методика універсальна, але у неї є істотний недолік (і його добре видно на схемі) – перехоплюються тільки статично імпортовані функції. Але є і плюс – методика дуже проста в реалізації, і є маса прикладів, її реалізації. Пошук таблиці імпорту в пам'яті не є особливо складним, оскільки для цього існують спеціалізовані API функції, що дозволяють працювати з образом програми в пам'яті. Оригінальний текст такого перехоплювача на мові C займає кілька аркушів друкованого тексту;

Рис.4. Перехоплення LoadLibrary і GetProcAddress
Перехоплення функцій LoadLibrary і GetProcAddress може бути виконаний будь-яким методом. У класичній реалізації застосовується методика 2 - модифікація таблиці імпорту. Ідея методики проста, якщо перехопити GetProcAddress, то при запиті адреси можна видавати програмі не реальні адреси необхідних їй системних функцій, а адреси своїх перехоплювачів. Як і в методі 2 програма «не відчує» різниці. При виклику GetProcAddress вона отримує адресу і виконує виклик функції(рис.4). У даного методу є мінус: він не дозволяє перехопити статично імпортовані функції.

Рис.5. Поєднування методик 1.2.3 і 1.2.4
У даній методиці модифікується таблиця імпорту, причому в обов'язковому порядку перехоплюються функції LoadLibrary і GetProcAddress бібліотеки kernel32.dll. У цьому випадку, при виклику статично імпортованих функцій спотворені адреси беруться з таблиці імпорту. При динамічному визначенні адреси викликається перехоплена функція GetProcAddress, яка повертає адреси функцій-перехоплювачів(Рис.5). В результаті, у програми не залишається шансів дізнатися правильну адресу функції.

Рис.6. Модифікація програмного коду API функції
Даний метод складніший в реалізації, ніж підміна адреси. Методика полягає в тому, що функція-перехоплювач знаходить в пам'яті машинний код необхідних йї функцій API і модифікує його. При такому методі перехоплення функції вже немає потреби в модифікації таблиці імпорту запущених програм і передачі програмами перекручених адрес при виклику GetProcAddress. З точки зору виклику функції все залишається «як є», за одним винятком – тепер уже за «правильною» адресою всередині «правильної» DLL знаходиться машинний код функції-перехоплювача (рис.6.).
Найчастіше втручання в машинний код перехоплюваних функцій мінімальне. На початку функції розміщується не більше 2-3 машинних команд, що передають управління основній функції-перехоплювачу. Щоб виконати виклик модифікованих функцій, функція-перехоплювач повинна зберегти вихідний машинний код для кожної модифікованої нею функції (природно, зберігається не весь машинний код функції, а змінені при перехопленні байти). Саме така методика перехоплення реалізована в широко відомому HackerDefender і бібліотеці AFX функція-перехоплювач (www.функція-перехоплювач.com) [1] .
Дана методика полягає в тому, що системна бібліотека модифікується на диску. Методи модифікації аналогічні описаним вище, тільки зміни здійснюються не в пам'яті, а на диску. Подібна методика не отримала широкого розповсюдження.
Для розуміння типових для методики перехоплення функцій в режимі ядра слід розглянути принципи взаємодії бібліотек користувацького режиму і ядра. Розглянемо цю взаємодію на спрощеній схемі:

Рис.7. Перехоплення системних функцій у режимі ядра (Кernel mode)
Основна взаємодія з ядром проводиться через ntdll.dll, більшість функцій якої є перехідниками, які звертаються до ядра через переривання INT 2Eh (слід зауважити, що прикладній програмі ніщо не заважає безпосередньо викликати INT 2Eh). Подальше звернення до функцій ядра заснована на структурі, іменованої KeServiceDescriptorTable (або скорочено SDT), розташованої в ntoskrnl.exe. SDT - це таблиця, що містить адреси точок входу сервісів ядра NT. Опис функцій і методик перехоплення можна знайти в книзі Свена Шрайбера «Недокументовані можливості Windows 2000», там же наведена схема взаємодії, яка є прототипом для нашого випадку. Спрощено можна сказати, що для перехоплення функцій необхідно написати драйвер, який виконає модифікацію таблиці SDT. Перед модифікацією драйверу необхідно зберегти адреси перехоплюваних функцій і записати в таблицю SDT адреси своїх обробників(рис.7). Даний метод чимось нагадує перехоплення переривань в MSDOS або описану вище методику 2.
Цей метод часто називають перехопленням Native API і природно він працює тільки в NT (і, відповідно, W2K, XP, W2003). Слід зазначити, що перехоплення Native API здійснюють не тільки хакерські програми такі як руткіти, існує маса корисних програм, що перехоплюють функції за допомогою редагування SDT – як приклад популярна утиліта RegMon від SysInternals або програма Process Guard.
Слід зазначити, що описаний метод є найбільш простим, але далеко не єдиним. Існує ще ряд способів, зокрема створення драйвера-фільтра. Драйвер-фільтр може застосовуватися як для вирішення завдань моніторингу (класичний приклад - утиліта FileMon отSysInternals), так і для активного втручання в роботу системи. Зокрема, драйвер-фільтр може застосовуватися для маскування файлів і папок на диску. Принцип роботи такого драйвера заснований на маніпуляціях з пакетами запиту вводу-виводу (IRP) [1].
Висновки:
З роботи розглянутих механізмів стає зрозуміло що забезпечити повний моніторинг за операціями файлової системи необхідно здійснити перехоплення файлових операцій на рівні ядра. Для цього необхідно розробити драйвер файлової системи, який буде дублювати усі функції реального драйвера, але містити, окрім основного коду, код операції-дописування тайнопису.
Ось перелік системних функцій, які необхідно перевизначити для написання повноцінної реалізації системи захисту:
Література:
1. Аналіз системи класів. NET Framework, відповідальних за роботу з файловою системою. Посилання: http://progrm.ru/?p=404
2. FileSystemWatcher – класс. Посилання: http://msdn.microsoft.com/ru-ru/library/system.io.filesystemwatcher.aspx
3. RootKit - принципи та механізми роботи. Посилання: http://z-oleg.com/secur/articles/rootkit.php