Как самому написать драйвер для устройства
1. Создание директории проекта
Установив DDK, на Вашем компьютере в директории C:\WINDDK\2600.1106\ должны появиться файлы DDK. В этой директории создадим папку, в которой будут храниться наши проекты. Назовем ее, например, MyDrivers.
2. Подготовка файлов проекта
В папке FirstDriver создайте пустой текстовый файл и переименуйте его под именем FirstDriver.c
При попытке переименовки со сменой расширения файла, появляется следующее предупреждение:
Не обращаем внимания на это предупреждение, и нажимаем Да. При этом наш файл примет вид:
Теперь нам надо добавить еще два очень важных файла в наш проект, без которых драйвер нам не сделать. Они называются makefile и sources (обратите внимание, у них нет расширения). Их можно создать самим, но мы сделаем проще: скопируем готовые из какого либо примера проекта драйвера из DDK. Например, возьмем их из C:\WINDDK\2600.1106\src\general\cancel\sys\. Итак, копируем из указанной директории эти два файла и вставляем их в нашу папку проекта FirstDriver. Эти файлы управляют процессрм компиляции драйвера. Файл makefile оставляем без изменений, а вот sources надо подредактировать.
Открываем этот файл с помощью блокнота, и удаляем из него все что там есть и вставляем туда текст, представленный ниже. Сохранияем файл.
Первым параметром идет TARGETNAME, которому мы присвоили Port. Это значит, что когда DDK откомпилирует наш код и создаст драйвер, имя этого файла будет Port.sys Следующем параметром идет TARGETPATH, которому мы указали путь к папке нашего проекта. Если Вы устанавливали DDK в другое место, или создали пупку проекта в другой директории, здесь Вам надо это поправить на тот путь, который у Вас. Параметр TARGETTYPE пока оставлю без комментариев. В параметре SOURCES указываем, из каких файлов будет компилироваться драйвер. У нас это файл FirstDriver.c, вот мы его и указали.
Драйвер — это просто
Многие считают что самому создать драйвер для Windows это что-то на грани фантастики. Но на самом деле это не так. Конечно, разработка драйвера для какого-то навороченного девайса бывает не простой задачей. Но ведь тоже самое можно сказать про создание сложных программ или игр. В разработке простого драйвера нет ничего сложного и я попытаюсь на примерах это показать.
Сперва нам нужно определится в чем мы же будем создавать наш первый драйвер. Поскольку материал ориентирован на новичков, то язык программирования был выбран один из простых, и это не Си или ассемблер, а бейсик. Будем использовать один из диалектов бейсика — PureBasic. Из коробки он не обучен создавать драйверы, но у него удачный набор файлов, используемых для компиляции и небольшое шаманство позволяет добавить эту возможность. Процесс компиляции состоит из нескольких этапов. Если кратко, то он происходит следующим образом: Сначала транслятор «перегоняет» basic-код в ассемблер, который отдается FASM’у (компилятор ассемблера), который создает объектный файл. Далее в дело вступает линкер polink, создающий исполняемый файл. Как компилятор ассемблера, так и линкер могут создавать драйверы и если немного изменить опции компиляции, то получим не исполняемый файл, типа EXE или DLL, а драйвер режима ядра (SYS).
Скачать немного модифицированную бесплатную демо версию PureBasic 4.61 x86 можно на файлопомойке, зеркало.
Если нужно создать драйвер для x64 системы, качайте эту версию, зеркало.
Дистрибутивы имеют небольшие размеры, около 3 МБ каждый. С помощью этой версии можно создавать только драйвера.
Скачиваем, распаковываем и запускаем, кликнув по файлу «PureBasic Portable». При этом запустится IDE и вылезет окошко с сообщением что это демо-версия и списком ограничений. Из него наиболее существенным является ограничение числа строк кода, равное 800, а для создания простых драйверов этого может хватить. Остальные ограничения в нашем случае, не существенны.
Окно IDE с загруженным кодом драйвера показано на скрине.
Компиляция драйвера выполняется через меню «Компилятор» (это если кто не понял).
Теперь определимся что будет делать наш первый драйвер. Обычно при изучении программирования начинают с простых вещей, скажем, выполнения математических операций и вывода результата. Вот пусть наш драйвер делает тоже самое, ведь банальная математика производимая в режиме ядра это очень круто!
Может показаться что это куча бессмысленного кода, но это не так.
У каждого драйвера должна быть точка входа, обычно у нее имя DriverEntry() и выполнена она в виде процедуры или функции. Как видите, в этом драйвере есть такая процедура. Если посмотрите на начало кода, то в первых строках увидите как ей передается управление. В этой процедуре происходит инициализация драйвера. Там же назначается процедура завершения работы драйвера, которая в нашем случае имеет имя UnloadDriver(). Процедуры CreateDispatch() и CloseDispatch() назначаются обработчиками соединения и отсоединения проги из юзермода.
Процедура DeviceIoControl() будет обрабатывать запросы WinAPI функции DeviceIoControl(), являющейся в данном драйвере связью с юзермодом. В конце кода расположена так называемая ДатаСекция (DataSection), в которой находятся имена драйвера, сохраненные в формате юникода (для этого использована одна из фишек ассемблера FASM).
Теперь рассмотрим как драйвер будет взаимодействовать с внешним миром. Это происходит в процедуре DeviceIoControl(). В ней отслеживается одно сообщение, а именно — #IOCTL_MyPlus, которое отправляет юзермодная прога, когда ей нужно сложить два числа в режиме ядра (круто звучит, правда?). Когда такое сообщение получено, то считываем из системного буфера, адрес указателя на структуру со слагаемыми, производим сложение и результат помещаем в системный буфер. Собственно это основная задача нашего первого драйвера.
Видите сколько понадобилось кода для выполнения простейшей математической операции — сложения двух чисел?
А теперь рассмотрим программу, работающую с этим драйвером. Она написана на том же PureBasic.
При старте программы вызывается функция OpenDriver(), которая загружает драйвер. Для упрощения, имя драйвера, имя службы и описание службы заданы одинаковыми — «pbDrPlus». Если загрузка неудачная, то выводится соответствующее сообщение и программа завершает свою работу.
Процедура Plus() осуществляет связь с драйвером. Ей передаются хэндл, доступа к драйверу и слагаемые числа, которые помещаются в структуру и указатель на указатель которой, передается драйверу. Результат сложения чисел будет в переменной «Result».
Далее следует код простейшего GUI калькулятора, скопированного из википедии.
Когда закроют окно, то перед завершением работы программы, закрывается связь с драйвером и производится его деинсталляция из системы.
Результат сложения чисел 8 и 2 на скриншоте.
Исходные коды драйвера и программы, можно найти в папке «Examples», PureBasic на файлопомойке, ссылку на который давал в начале статьи. Там так же найдете примеры драйвера прямого доступа к порам компа и пример работы с памятью ядра.
PS.
Помните, работа в ядре чревата мелкими неожиданностями аля, BSOD (синий экран смерти), поэтому экспериментируйте осторожно и обязательно всё сохраняйте перед запуском драйвера.
За возможную потерю данных, я ответственности не несу!
Пишем свой первый Windows-драйвер
Итак, после моей предыдущей статьи я понял что тема про программирование драйверов Windows интересна хабровчанам, поэтому продолжу. В этой статье я решил разобрать простую программу-драйвер, которая делает только то, что пишет отладочное сообщение «Hello world!» при старте драйвера и «Goodbye!» при завершении, а также опишу те средства разработки, которые нам понадобятся для того, чтобы собрать и запустить драйвер.
Итак, для начала приведем текст этой несложной программы.
Итак, теперь сначала разберемся, что делает каждая инструкция. Перво-наперво мы подключаем заголовочный файл ntddk.h. Это один из базовых подключаемых файлов во всех драйверах: в нем содержатся объявления типов NTSTATUS, PDRIVER_OBJECT, PUNICODE_STRING, а также функции DbgPrint.
Далее идет объявление двух функций: DriverEntry и UnloadRoutine. Расскажу о первой поподробнее. Итак, как уважаемые читатели знают, в каждой программе есть точка входа, в программах на языке C это функция main или WinMain. В драйвере роль точки входа выполняет функция DriverEntry, которая получает на вход указатель на структуру DriverObject, а также указатель на строку реестра, соответствующую загружаемому драйверу.
Структура DriverObject содержит множество полей, которые определяют поведение будущего драйвера. Наиболее ключевые из них — это указатели на так называемые вызываемые (или callback) функции, то есть функции, которые будут вызываться при наступлении определенного события. Одну из таких функций мы определяем: это функция UnloadRoutine. Указатель на данную функцию помещается в поле DriverUnload. Таким образом при выгрузке драйвера сначала будет вызвана функция UnloadRoutine. Это очень удобно, когда драйвер имеет какие-то временные данные, которые следует очистить перед завершением работы. В нашем примере эта функция нужна только чтобы отследить сам факт завершения работы драйвера.
Для того, чтобы выводить отладочные сообщения мы используем функцию DbgPrint, которая имеет синтаксис, аналогичной функции printf из пользовательского режима (userspace).
В этом простом примере мы использовали также директивы #pragma alloc_text(INIT, DriverEntry) и #pragma alloc_text(PAGE, UnloadRoutine). Объясню что они означают: первая помещает функцию DriverEntry в INIT секцию, то есть как бы говорит, что DriverEntry будет выполнена один раз и после этого код функции можно спокойно выгрузить из памяти. Вторая помечает код функции UnloadRoutine как выгружаемый, т.е. при необходимости, система может переместить его в файл подкачки, а потом забрать его оттуда.
Вы можете задуматься, мол ну с первой-то директивой понятно, типа оптимизация и все такое, но зачем мы используем вторую директиву, зачем помечать код как возможный к выгрузке в файл подкачки? Поясню этот вопрос: каждый процесс в системе имеет такой параметр, как IRQL (подробнее читаем по ссылке Interrupt request level ибо это материал отдельной статьи), то есть некоторый параметр, отвечающий за возможность прерывания процесса: чем выше IRQL тем меньше шансов прервать выполнение процесса. Возможности процесса так же зависят от IRQL: чем выше IRQL тем меньше возможности процесса, это вполне логично, т.е. такой подход побуждает разработчиков выполнять только самые необходимые операции при высоком IRQL, а все остальные действия делать при низком. Вернемся к основной теме, о том, почему мы делаем для функции UnloadRoutine возможность выгрузки в файл подкачки: все опять же сводится к оптимизации: работа с файлом подкачки недоступна при высоком IRQL, а процедура выгрузки драйвера гарантированно выполняется при низком IRQL, поэтому мы специально указываем руками что код функции выгрузки драйвера можно поместить в своп.
Ух, вроде как с обсуждением кода этой, казалось бы небольшой программки закончено, теперь разберемся как скомпилировать и запустить наш драйвер.
Теперь последовательность действий: сначала мы пишем два файла, один называется MAKEFILE, с таким содержимым
а второй называется sources и содержит в себе следующее:
Эти файлы нужны для сборки драйвера. Да, забыл сказать, что в WDK нет встроенной среды разработки, поэтому и нужен текстовый редактор, чтобы набирать текст драйверов. Для этой цели можно использовать и Visual Studio (некоторые даже интегрируют возможность сборки драйверов из VS), и любой другой текстовый редактор.
Данная команда соберет нам драйвер TestDriver.sys и положит его в папку «objchk_wxp_x86\i386».
Теперь нам нужно запустить программу DbgView чтобы увидеть сообщения, которые будет выдавать драйвер. После запуска данной программы нам нужно указать, что мы хотим просматривать сообщения из ядра (Capture->Capture Kernel).
Теперь запукаем программу KmdManager, указываем путь к нашему драйверу (файл TestDriver.sys) нажимаем кнопку Register, затем Run. Теперь драйвер зарегистрирован в системе и запущен. В программе DbgView мы должны увидеть наше сообщение «Hello World!». Теперь завершаем работу драйвера кнопкой Stop и убираем регистрацию драйвера кнопкой Unregister. Кстати, в DbgView дожна появиться еще одна строка.
Итак, чего же мы достигли: мы написали, скомпилировали и запустили свой первый Windows-драйвер! Добавлю только, что при написании сложный драйверов для отладки используется двухмашинная конфигурация, когда на одном компьтере ведется написание драйвера, а на другом — запуск и тестирование. Это делается из-за того, что неправильно написанный драйвер может обрушить всю систему, а на ней может быть очень много ценных данных. Часто в качестве второго компьютера используется виртуальная машина.
Статья Создание драйвера под Windows. Часть 1: Введение
Mod. Ethical Hacking
Предыстория
Для очередного проекта возникла необходимость написать простенький софтверный драйвер под Windows, но так как опыта в написании драйверов у меня примерно столько же, сколько и в балете, я начал исследовать данную тему. В таких делах я предпочитаю начинать с основ, ибо если кидаться сразу на сложные вещи, то можно упустить многие базовые понятия и приёмы, что в дальнейшем только усложнит жизнь.
Книгу я приобрёл в бумажной версии, чтобы хоть и немного, но поддержать автора. Безупречное качество, несмотря на то, что она издается в мягком переплете. Хорошие плотные листы формата А4 и качественная краска. (книга без проблем пережила вылитую на нее кружку горячего кофе).
Пока я сидел на балконе и читал четвёртую главу книги, в голову пришла мысль: а почему бы не сделать ряд статей на тему «Программирования драйвера под Windows», так сказать, совместить полезное, с еще более полезным.
И вот я здесь, пишу предысторию.
Как я вижу этот цикл статей и что от него ожидать:
Это будут статьи, которые будут базироваться на вышеупомянутой книге, своеобразный вольный и сокращенный перевод, с дополнениями и примечаниями.
Базовые понятия о внутреннем устройстве Windows (Windows Internals)
Для того, чтобы начать разрабатывать Драйвер под Windows, то есть работать на уровне с ядром ОС, необходимо базовое понимание того, как эта ОС утроена. Так как я хочу сосредоточиться на написании драйвера, а не на теории об операционных системах, подробно описывать понятия я не буду, чтобы не растягивать статью, вместо этого прикреплю ссылки для самостоятельного изучения.
Следовательно, нам стоит ознакомиться с такими базовыми понятиями как:
Программирование драйверов устройств для чайников
#1 dE fENDER
#2 dE fENDER
В этой теме я планирую дать серию уроков, которые позволят любому человеку получить базовое представление о том, как устроена система взаимодействия прикладного ПО и драйверов ОС Windows, научиться читать исходные коды существующих драйверов устройств или написать свой собственный драйвер, работающий с его собственным устройством.
Также эти уроки могут помочь аудитории владельцев видеокарт с графическими чипами, произведенными компанией 3dfx, разобраться в доступных исходных кодах драйверов этих видеокарт. Упомянутые исходники могут быть с наименьшим количеством переделок откомпилированы под Windows XP/Windows 2003 Server, которые и будут являться основной целевой системой. В более современной Windows Vista/7 несколько изменилась модель драйверов, наиболее сильно это затронуло графическую подсистему. Это означает, что для их корректной работы придется переписать весьма значительную часть кода. Кроме того, под х64 версиями Vista/7 и более старших операционных систем просто так нельзя загрузить драйвер, не имеющей цифровой подписи. Подпись стоит порядка 500 долларов в год (150, если найти купон на скидку), их не любят давать частным лицам и даже небольшим организациям. Кроме того, если в подписанном драйвере обнаружится уязвимость и ей воспользуются хакеры – то подпись данного [почти всегда юридического] лица аннулируется и у него возникают проблемы с получением новой. Еще есть такая процедура, как WHQL-сертификация, но ее я вообще не собираюсь пока здесь обсуждать.
Весьма приветствуется, если другие люди будут давать здесь свои собственные уроки, не выходящие за пределы темы. Также здесь можно задавать любые вопросы, предлагать свои идеи для следующих уроков и отписываться об своих удачных/неудачных результатах.
Для начала работы требуется иметь некоторое знакомство с любым языком программирования. Думаю, для этого вполне достаточно школьного / вузовского курса для непрограммистов. Если же в современных программах образования знакомство с программированием не было предусмотрено/за давностью лет курс забыт или просто что-то непонятно – отписывайтесь в теме, я напишу другие уроки, подробней раскрывающие непонятную тему.
#3 White
#4 dE fENDER
Урок 0. Установка и настройка программного обеспечения
Список используемого программного обеспечения:
Установка Visual Studio
Я буду использовать Visual Studio 2010 Ultimate. В принципе, ставить ее не обязательно, так как все необходимые инструменты, включая компилятор и заголовки есть и в WDK. Но для написания прикладных программ, в особенности для новичка, она намного удобней вызова компилятора из консоли. Если ее все же планируется установить, то лучше делать это перед установкой WDK.
Берем диск со студией в iso-формате, монтируем его в Daemon Tools, либо подключаем как компакт-дисковод в VMWare. Запускаем с диска setup.exe, выбираем «Install Microsoft Visual Studio 2010». На следующем шаге под Windows 2003 может появится сообщение, что требуется Windows Imaging Component. Щелкаем по этой строке, запускается браузер – скачиваем подходящий по языку операционной системы (у меня английский, значит wic_x86_enu.exe), устанавливаем. Заново запускаем setup.exe с диска. Нажимаем Next, соглашаемся с лицензионным соглашением, затем идет выбор типа установки. Выбираем Custom, изменяем путь установки – для избежания ошибок Visual Studio и все любые другие инструменты следует ставить по максимально короткому пути без русских букв и пробелов. Мой вариант – C:\Programs\VS10. Следующая страница – выбор пакетов. Устанавливаем в соответствии с рисунком:
Рис 0.1 – Выбор пакетов в установщике Visual Studio
Кроме показанных, можно также выбрать «Visual C#». C# бывает полезен для расширения возможностей самой Visual Studio и написания небольших утилит. Нажимаем Install. Кроме самой студии ставится куча бесполезной, ненужной и вредной дряни. Если на компьютере была старая версия Windows Installer’а – потребуется перезагрузка. После установки – Finish, Exit.
Установка WDK
Монтируем скачанный iso-образ аналогично тому, как это было сделано с Visual Studio. Запускаем KitSetup.exe, на этапе выбора пакетов устанавливаем галочки в соответствии с рисунком:
Рис 0.2 – Выбор пакетов в установщике WDK
Устанавливать Visual Studio и WDK не обязательно на той же машине, на которой планируется проверять работу программ. Для проверки я поставил их на реальной машине с Windows 7 х64 и откомпилировал драйвер, который заработал под Windows 2003 Server х86.
Создаем на диске каталог для проектов, учитывая вышеописанные рекомендации (без пробелов и русских букв), например C:\Projects. Запускаем Visual Studio, открываем настройки (меню Tools/Option), выбираем в дереве «Projects and Solutions/General», в параметре Projects location выбираем/указываем созданный каталог.
Также настоятельно рекомендую установить Far Manager 2 и работать через него. Его преимущество над Windows/Total Commander’ами и Explorer’ами в том, что через него нормально видно содержимое консоли (команда ctrl + O), куда выдается полезная информация при компиляции и запуске консольных программ. Путь установки желательно записать в переменную PATH.
В дальнейшем рассмотрим и драйверы для Windows 9x. Для этого потребуется другое программное обеспечение, но программирование драйверов для Windows 9x проще.