Как да напишете свой собствен "пясъчник" пример за най-простия "пясъчник"

В първата част на тази статия получихте бързо въведение в драйверите за привилегирован режим. Време е да копаем в нашата пясъчник.
Пример за пясъчник: изграждане и инсталиране на драйвер
Ядрото на нашата пясъчник е драйверът за мини-филтър. Можете да намерите неговия изходен код на srcFSSDKKernelminilt. Предполагам, че използвате WDK 7.x за изграждане на драйвера. За да направите това, трябва да стартирате подходящата среда, да кажете Win 7 x86 проверена и да отидете до директорията на изходния код. Просто напишете "build/c" в командния ред, когато стартирате в средата за разработка и ще получите вградените драйвери. За да инсталирате драйвера, просто копирайте файла * .inf в папката, съдържаща файла * .sys, отидете в тази директория с помощта на Explorer и използвайте контекстното меню на файла * .inf, където изберете елемента "Инсталиране" и драйверът ще бъде инсталиран. Препоръчвам ви да правите всичките си експерименти във виртуална машина, VMware е добър избор. Моля, обърнете внимание, че 64-битовите версии на Windows няма да заредят неподписани драйвери. За да можете да стартирате драйвера във VMware, трябва да активирате програмата за отстраняване на грешки в привилегирования режим в гост OS. Това може да се направи чрез изпълнение на следните команди в cmd, изпълнявани като администратор:
1. bcdedit/отстраняване на грешки
2. bcdedit/bootdebug на
Сега трябва да зададете поименен канал като сериен порт за VMware и да конфигурирате с WinDBG, инсталиран на вашата машина. След това трябва да можете да се свържете с VMware с програма за отстраняване на грешки и да отстраните грешките на драйверите си.
Намерете подробна информация за конфигуриране на VMware за отстраняване на грешки в драйвери в тази статия.
Пример за пясъчник: общ преглед на архитектурата
Нашата проста пясъчник се състои от три модула:
• драйвер за привилегирован режим, който реализира примитивите за виртуализация;
• услуга в потребителски режим, която получава съобщения от драйвера и може да промени поведението на файловата система чрез промяна на параметрите на получените известия от драйвера;
• междинна библиотека fsproxy, която помага на услугата да комуникира с драйвера.
Нека започнем нашата проста пясъчник с драйвер в привилегирован режим.
Пример за пясъчник: писане на драйвер
Докато обикновените приложения обикновено стартират в WinMain (), драйверите го правят с DriverEntry (). Нека започнем да изучаваме драйвера с тази функция.
DriverEntry има няколко ключови функции. Първо, тази функция регистрира драйвера като minifler с функцията FltRegisterFilter ():
Това е масив от указатели за манипулатори на определени операции, които той иска да прихване във FilterRegistration и получава екземпляр на филтър в MfltData.Filter, ако регистрацията е успешна. FilterRegistration се декларира по следния начин:
Както можете да видите, структурата съдържа указател към масив от манипулатори на събития (Callbacks). Това е аналог на процедурите за изпращане в "остарели" драйвери. В допълнение, тази структура съдържа указатели към някои други помощни функции, които ще опишем по-късно. Сега нека се спрем на манипулаторите, описани в масива Callbacks. Те се определят, както следва:
Можете да видите подробно описание на структурата FLT_OPERATION_REGISTRATION на MSDN. Нашият драйвер регистрира само два манипулатора - FSPreCreate, който ще бъде извикан всеки път, когато бъде получена заявка IRP_MJ_CREATE, и FSPreCleanup, който от своя страна ще бъде извикан всеки път, когато се получи IRP_MJ_CLEANUP. Тази заявка идва, когато последният дескриптор на файла е затворен. Можем (и ще) променим реквизита и да изпратим модифицираната заявка надолу по стека, така че долните филтри и драйверът на файловата система да получат модифицираната заявка. Бихме могли да регистрираме така наречените пост-известия, когато транзакцията приключи. За целта нулевият указател, който следва указателя към FSPreCreate, може да бъде заменен с указател към съответния манипулатор. Трябва да завършим нашия масив с елемент IRP_MJ_OPERATION_END. Това е "фалшива" операция, която отбелязва края на масива на манипулатора на събития. Имайте предвид, че не е необходимо да предоставяме манипулатор за всяка операция IRP_MJ_XXX, както би трябвало да правим за "традиционните" филтърни драйвери.
Второто важно нещо, което прави DriverEntry (), е да създаде порт за мини-филтър. Използва се за изпращане на известия от услугата на ниво потребител и получаване на отговори от нея. Това се прави с помощта на операцията FltCreateCommunicationPort ():
Указатели към функциите FSPortConnect () и FSPortDisconnect () се появяват съответно при свързване и изключване на услугата в потребителски режим от драйвера.
И последното нещо, което трябва да направите, е да започнете да филтрирате:
Обърнете внимание, че указателят към екземпляра на филтъра, върнат от FltRegisterFilter (), се предава на тази процедура. Отсега нататък ще започнем да получаваме известия за IRP_MJ_CREATE & IRP_MJ_CLEANUP заявки. Заедно с известията за филтриране на файлове, ние също така искаме ОС да ни информира, когато се зарежда и разтоварва нов процес, използвайки тази функция:
CreateProcessNotify е нашият манипулатор за създаване и завършване на известия.
Пример за пясъчник: манипулатор FSPreCreate
Истинската магия се ражда тук. Същността на тази функция е да се каже кой файл е отворен и с кой процес е стартирано отварянето. Тези данни се изпращат до услугата за потребителски режим. Услугата (услугата) предоставя отговор под формата на команда или за отказ на достъп до файла, или за пренасочване на заявката към друг файл (така всъщност работи пясъчника), или за разрешаване на извършването на операцията. Първото нещо, което се случва в този случай, е да проверим връзката с услугата за потребителски режим чрез комуникационния порт (комуникационен порт), който създадохме в DriverEntry (), и ако няма връзка, няма да се предприемат допълнителни действия. Също така проверяваме дали услугата (услугата) е източникът (инициаторът) на заявката - правим това, като проверяваме полето UserProcess на глобално разпределената структура MfltData. Това поле се попълва в процедурата PortConnect (), която се извиква, когато услуга в потребителски режим се свърже с порта. Също така не искаме да се занимаваме със заявки, свързани с пейджинг. Във всички тези случаи връщаме кода за връщане FLT_PREOP_SUCCESS_NO_CALLBACK, което означава, че сме приключили с обработката на заявката и нямаме обработващ пост-оператор. В противен случай ще върнем FLT_PREOP_SUCCESS_WITH_CALLBACK. Ако беше „традиционен“ драйвер на филтъра, тогава щеше да се наложи да се справим със стековите рамки, които споменах по-рано, с процедурата IoCallDriver и т.н. В случай на мини-филтри, предаването на заявката е доста просто.
Получаваме името на файла от "целевия" файл, за който е получена заявката (например заявка за отваряне на файл), използвайки две функции - FltGetFileNameInformation () и FltParseFileNameInformation ().
След попълване на структурата MINFILTER_NOTIFICATION, ние я изпращаме в потребителски режим:
И получаваме отговора в променливата за отговор. Ако бъдете помолени да отменим операцията, тогава действието е просто:
Ключовите моменти тук са както следва: първо, ние променяме кода за връщане, като връщаме FLT_PREOP_COMPLETE. Това означава, че няма да предадем заявката надолу по стека с драйвери, както например бихме направили, като извикаме IoCompleteRequest () от "традиционен" драйвер, без да извикваме IoCallDriver (). На второ място, попълваме полето IoStatus в структурата на заявките. Задайте код на грешка STATUS_ACCESS_DENIED и полето Информация на нула. По правило полето Информация записва броя на прехвърлените байтове по време на операцията, например по време на операция за копиране се записва броят на копираните байтове.
Ако искаме да пренасочим операцията, тя изглежда по различен начин:
Ключът тук е извикването на IoReplaceFileObjectName
Пример за пясъчник: доставчици на имена
Тъй като променяме имената на файловете, нашият драйвер трябва да внедри манипулаторите на доставчици на имена, които се извикват, когато е поискано име на файл или когато името на файла е "нормализирано". Тези манипулатори са FSGenerateFileNameCallback и FSNormalizeNameComponentCallback (Ex).
Нашият метод за виртуализация се основава на „рестартиране“ на заявката IRP_MJ_CREATE (правим се, че виртуализираните имена са REPARSE_POINTS), а изпълнението на тези манипулатори е доста просто, което е описано подробно тук.
Услуга за потребителски режим
Потребителският режим е в проекта за файлова стена (вижте изходния код за статията) и комуникира с драйвера. Основната функционалност е представена от следната функция:
Извиква се, когато драйверът реши да пренасочи име на файл. Алгоритъмът тук е много прост: ако файл, поставен в пясъчника, вече съществува, заявката просто се пренасочва, като променливата pReply се запълва с ново име на файл - име в папката на пясъчника. Ако такъв файл не съществува, тогава оригиналният файл се копира и само тогава оригиналната заявка се модифицира, за да сочи към ново копирания файл. Как услугата знае кога дадена заявка трябва да бъде пренасочена за определен процес? Това се прави с помощта на правила - вижте изпълнението на класа CRule. Правилата (обикновено единственото правило в нашата демо услуга) се зареждат от функцията LoadRules ().
Тази функция създава правило за процес (или процеси), наречен "cmd.exe" и пренасочва всички операции с * .txt файлове към пясъчника. Ако стартирате cmd.exe на компютър, работещ с нашата услуга, той обезопасява вашите операции. Например можете да създадете txt файл от cmd.exe, да речем, като изпълните командата "dir> files.txt", files.txt ще бъде създаден в C: /sandbox//files.txt, където е текущата директория за cmd.exe. Ако добавите съществуващ файл от cmd.exe, ще получите две копия от него - немодифицирана версия в оригиналната файлова система и модифицирана версия в C:/Sandbox.
Заключение
В тази статия разгледахме основните аспекти на създаването на пясъчник. Някои подробности и проблеми обаче останаха незасегнати.
Например не можете да управлявате правила от потребителски режим, тъй като това значително забавя вашия компютър. Този подход е достатъчно прост за изпълнение и може да се използва за образователни цели, но в никакъв случай не трябва да се използва в търговски софтуер.
Друго ограничение е структурата за уведомяване/отговор с предварително дефинирани буфери за имена на файлове. Тези буфери имат два недостатъка: първо, те са с ограничен размер и някои файлове дълбоко във файловата система няма да бъдат обработени правилно. Второ, значителна част от паметта в режим на ядро, разпределена на името на файла, в повечето случаи не се използва. Ето защо в търговския софтуер трябва да се възприеме по-интелигентна стратегия за разпределение на паметта.
И друг недостатък е широкото използване на функцията FltSendMessage (), която е доста бавна. Той трябва да се използва само когато приложението в потребителски режим трябва да покаже на потребителя заявка и потребителят трябва да разреши или откаже операцията. В този случай тази функция може да се използва, тъй като взаимодействието с човек е много по-бавно от изпълнението на който и да е код. Но ако програмата реагира автоматично, трябва да избягвате прекомерното взаимодействие с кода на потребителския режим.