Monday, November 14, 2011

DbgCb: интеграция приложения/драйвера с удалённым отладчиком ядра

Думаю, для многих программистов и исследователей набор вида "виртуальная машина с Windows + WinDbg в качестве удалённого отладчика режима ядра" давно стал привычным и необходимым инструментом. Для того, что бы выжать из такой связки максимум возможностей и упростить рутинную работу, многие применяют вещи вроде скриптовых движков для отладчика (pykd, IDAPython) или отдельных врапперов для отладочных интерфейсов Windows (PyDbg).

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

Примеры из жизни, где подобные механизмы интеграции приложения/драйвера с отладчиком были бы кстати:
  • Исследование программ и компонентов операционной системы в процессе их работы, в ходе которого может понадобится устанавливать перехваты кода, и делать достаточно сложную обработку полученных данных, для реализации которой попросту не хватит возможностей (или производительности) скрипта на pykd, использующего точки останова.
  • Иногда, для того что бы быстро проверить какую-то идею/технологию, бывает необходимо прямо в "живой" проект добавить код, работающий с какими-либо не экспортируемыми функциями операционной системы (или недокументированными структурами), но вместо жесткого хардкодинга их адресов/смещений было бы удобно "спросить" об этих адресах/смещениях удалённый отладчик, который может извлечь всю необходимую информацию из доступных ему актуальных отладочных символов.
  • В процессе отладки своего кода бывает необходимо с некоторой периодичностью выполнять определённые манипуляции в отладчике (вывести стек вызовов в определённой точке, снять/установить точку останова в момент наступления какого-то события, итд.), без необходимости отвлекаться на, собственно, сам отладчик.
Для решения таких вот небольших повседневных задач я разработал инструмент, под названием DbgCb (Kernel Debugger Communication Engine), который и хочу представить широкому вниманию в данной заметке.

Функционально DbgCb состоит из двух компонентов:
  • Клиентский код, встраиваемый в приложение или драйвер, из которого доступен ряд функций, совершающих полезные действия с удалённым ядерным отладчиком.
  • Расширение для Windows Debugging Tools - выполнено в виде плагина для отладчика WinDbg/KD, предназначено для обработки запросов от клиентского кода.



Взаимодействие этих компонентов реализовано тривиально: плагин для WinDbg использует интерфейс IDebugEventCallbacks, позволяющий ему отслеживать различные события (срабатывание точек останова, изменения состояния отлаживаемой системы, итд.), а клиентский код, при вызове полезной функции, просто генерирует #DB (debug exception), поместив перед этим в регистры процессора специальные "магические" константы, значения которых и сообщают плагину о том, выполнение какой операции от него требуется.

Клиентский код выполнен в виде небольшого фрагмента на языке C, который может быть встроен в произвольное приложение или драйвер, а так же оформлен как библиотека. В текущей версии, в нём реализованы следующие три полезных функции:
 1 /**
 2  * Execute debuuger command (IDebugControl::Execute()).
 3  */
 4 BOOLEAN dbg_exec(PCHAR lpFormat, ...);
 5 
 6 /**
 7  * Evaluate debuuger expression (IDebugControl::Evaluate()).
 8  */
 9 PVOID dbg_eval(PCHAR lpFormat, ...);
10 
11 /**
12  * Get offset of the some structure field
13  */
14 LONG dbg_field_offset(PCHAR lpFormat, ...);

Пример использования API клиентского кода DbgCb в драйвере режима ядра:
 1 NTSTATUS NTAPI DriverEntry(
 2     PDRIVER_OBJECT DriverObject,
 3     PUNICODE_STRING RegistryPath)
 4 {    
 5     DriverObject->DriverUnload = DriverUnload;
 6     
 7     DbgPrint(__FUNCTION__"()\n");    
 8 
 9     // Test debugger command execution.
10     if (dbg_exec(".printf /D \"<b>Hello from " __FUNCTION__ "()</b>\\n\""))
11     {
12         // another DML example
13         dbg_exec(
14             ".printf /D \"<exec cmd=\\\"!drvobj "IFMT"\\\">"
15             "Show _DRIVER_OBJECT information.</exec>\\n\"", 
16             DriverObject
17         );
18 
19         DbgPrint("Breaking into the kernel debugger (check the DML link above)...\n");
20         DbgBreakPoint();
21 
22         // Test symbol querying.
23         PVOID Addr = dbg_eval("nt!KiDispatchException");
24         if (Addr)
25         {
26             DbgPrint("<?dml?><b>nt!KiDispatchException() is at "IFMT"</b>\n", Addr);
27         }        
28         else
29         {
30             DbgPrint(__FUNCTION__"() ERROR: dbg_eval() fails\n");
31         }
32 
33         // Test structure field offset querying.
34         LONG Offset = dbg_field_offset("nt!_EPROCESS::ImageFileName");
35         if (Offset >= 0)
36         {
37             DbgPrint("<?dml?><b>_EPROCESS::ImageFileName offset is 0x%x</b>\n", Offset);
38         }
39         else
40         {
41             DbgPrint(__FUNCTION__"() ERROR: dbg_field_offset() fails\n");
42         }
43     }    
44     else
45     {
46         DbgPrint(__FUNCTION__"() WARNING: dbgcb extension is not loaded\n");
47     }
48     
49     return STATUS_SUCCESS;
50 }

Исходный код DbgCb доступен в репозитории на GitHub, и включает в себя:
  • ./dbgcb/ - Исходные тексты расширения для отладчика.
  • ./dbgcb.dll, ./dbgcb_x64.dll - Исполняемые файлы расширения для отладчика WinDbg/KD.
  • ./common/ - Исходные тексты клиентского кода.
  • ./dbgcb_app/ - Исходные тексты примера использования клиентского кода в приложении.
  • ./dbgcb_app.exe./dbgcb_app_x64.exe - Исполняемые файлы примера приложения.
  • ./dbgcb_drv/ - Исходные тексты примера использования клиентского кода в драйвере.
  • ./dbgcb_drv.sys./dbgcb_drv_x64.sys - Исполняемые файлы примера драйвера.
Перед запуском примера драйвера или приложения, в среде отлаживаемой операционной системы, необходимо выполнить загрузку модуля DbgCb в отладчик, используя команду ".load dbgcb.dll".

При запуске примера драйвера в Command Window отладчика появятся следующие сообщения, свидетельствующие о том, что Kernel Debugger Communication Engine работает:



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

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