Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
FASM

WinDBG - отладчик ядерного уровня

10.05.2019, 13:12. Показов 19946. Ответов 7

Студворк — интернет-сервис помощи студентам
  1. Введение;
  2. Обзор и настройка интерфейса;
  3. Связываем WinDBG и VirtualBox;
  4. Системные механизмы;
  5. В боевых условиях;
  6. Заключение.

**************************************** ***************


Часть 1. Введение (в заблуждение).

Проблема отладки приложений уходит корнями в DOS, где в нашем распоряжении имелся широкий набор инструментов: Soft-ICE, Turbo-Debugger, AVPUtil, CUP386, AFD-Pro, etc. Но вот пришла Win и всё изменилось. Старый софт оказался не у дел, хотя некоторые экземпляры всё-ещё упорно цепляются за жизнь и на данном витке эволюции удовлетворяют потребностям студентов и узкого круга бородатых прогеров.

Защищённый режим прийти-то пришёл, только забыл принести с собой средства отладки и это не удивительно - на то он и защищённый, со-всеми вытекающими. Тут за дело взялись энтузиасты одним из которых стал Олег Ющук и мир увидел его отладчик "OllyDbg". Оля в полной мере оправдывает наши надежды выворачивая наизнанку все/тайные закрома исполняемого РЕ-файла, и имеет кучу доп.плагинов расширяющих стандартные её возможности.

А что ещё нам нужно? На первый взгляд вроде ничего, только есть одна проблема! Это юзер/модный отладчик, для которого вход на ядерный уровень - закрыт. Да, он может показать нам Win32-API, стек и многое/другое. Однако в ядре хранится куча служебных структур прикладной задачи - вот где кроются все секреты! К сожалению, создать универсальный инструмент увы не получится, т.к. Win-приложения не могут работать на обоих уровнях сразу. Причиной тому - разграничение памяти на User и Kernel (ring-0/3), за правами доступа к которой на аппаратном уровне пристально следит сам процессор.

В какой-то мере заполнил свободную нишу мега-отладчик "Soft-ICE", но его поддержка давно прекращена и в последний раз окно этого монстра видели счастливые обладатели систем WinXP-SP1. Так, все кодокопатели ядра дружненько пересели на WinDbg. Это детише мелкософта бурно развивается и на данный момент уверенно себя чувствует на всех/современных платформах, вплоть до 64-битной Win10.

Как-правило, миграция от Оли к WinDbg для программистов проходит болезненно, но её можно сравнить с переходом из юношества во-взрослую жизнь. Огромное кол-во команд даёт нам полную свободу, хотя и требует взамен особой внимательности. Поскольку это отладчик уровня ядра, то функции Win-API уходят тут на второй план, а им взамен приходит Kernel-API со-своими параметрами. Это по-истине увлекательное путешествие "к центру вселенной", которое советую совершить каждому.


Часть 2. Обзор и настройка интерфейса WinDBG.

WinDBG входит в состав множества продуктов: SDK, DDK, WDF, а так-же поставляется как самостоятельный пакет "Debugging Tools for Windows" весом ~20 Мб. Причём версия из пакета обычно самая/свежая, и содержит наибольшее кол-во полезных расширений. После установки отладчика, нас встречает скудный фейс, чем-то напоминающий наскальные рисунки недочеловеков. Но не делайте преждевременных выводов! Возможности WinDBG намного больше, чем это кажется на первый взгляд, просто до них через интерфейсные штучки не подобраться.



Попробуем перекроить дефолтный вид под себя, или воспользоваться уже имеющимися заготовками - т.н. Workspace (рабочее пространство) из папки %WinDbg%\themes. Для этого нужно проделать следующие шаги:
  1. Закрыть отладчик WinDBG.
  2. Запускаем "regedit" и идём по пути: [HKCU\Software\Microsoft\Windbg\..]
  3. Удаляем в этой папке весь раздел "Workspaces", co-всей подноготной.
  4. Теперь из дира [C:\Program Files\Debugging Tools for Windows\themes\] запускаем файл-реестра "standardvs.reg", подтверждая свои намерения буттоном "ОК".
  5. Запускаем WinDBG и видим уже несколько (выстроеных каскадом) окон. Остаётся из меню "View-->Font" выбрать подходящий шрифт (у меня "Courier New") и подвигав за маркеры окон, настроить их в удобное для ваших глаз расположение.
  6. Чтобы запомнить ново/испечённый Workspace нужно сохранить его с расширением *.wew, для чего проследуем в меню "File-->Save_Workspace_to_File" и задав ему имя, сохраним в той-же папке "..\themes". После этого можно загнать его и в локальное хранилише, через "Save_Workspace_As..".
Теперь отладчик будет стартовать в приглядном виде, преобразившись от полного нуля до старшего майора - у меня получилось так:



Судя по всему, WinDbg задумывался как инструмент для отладки драйверов, а всё/остальное прицепили к нему позже. Отладчик способен работать в трёх режимах, а текущий указывается в заголовке ком-строки (см.рис.выше). Последние/введённые команды запоминаются в кольцевом буфере, поэтому их не нужно вводить повторно - после ввода, следующий раз просто жмём Enter. Заголовок ком-строки характеризует следующее:
  • 0:000 > - Отладка программ прикладного уровня;
  • lkd > - Local_Kernel_Debug - отладка ядра локальной машины;
  • kd > - Kernel_Debug - отладка ядра и драйверов на удалённой машине.
Поскольку система разграничения доступа в NT далеко не безупречна, эксперименты с драйверами (не путать с экскрементами) следует проводить на отдельной, полностью изолированной от внешнего мира машине. Традиционно, эти задачи решались путём связывания шнурком двух компьютеров, теперь-же этот кошмар ушёл в прошлое. Мощь современных процессоров и отладчика WinDBG позволяют эмулировать всю Target-машину целиком - но об этом чуть позже, в третьей части сие повествования.

Отладочные символы. (инструкция к метле)

Чтобы достойно принять на борт пассажира, нам нужно скачать "отладочные символы". Эти символы с расширением *.pdb хранятся на сервере Microsoft и позволяют "по отпечатку кода" придавать осмысленные имена функциям, безчисленному кол-ву служебных структур и т.п. При отсутсвии символов, в ответ на большинство команд WinDBG будет обкладывать нас трёхэтажным матом, употребляя при этом приблизительно такой лексикон (аж 13 повторений слова "symbol"):

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Microsoft (R) Windows Debugger Version 6.12.0002.633 х86
Symbol search path is: *** Invalid ***
****************************************************************************
* Symbol loading may be unreliable without a symbol search path.           *
* Use .symfix to have the debugger choose a symbol path.                   *
* After setting your symbol path, use .reload to refresh symbol locations. *
****************************************************************************
Executable search path is: 
*********************************************************************
* Symbols can not be loaded because symbol path is not initialized. *
*                                                                   *
* The Symbol Path can be set by:                                    *
*   using the _NT_SYMBOL_PATH environment variable.                 *
*   using the -y <symbol_path> argument when starting the debugger. *
*   using .sympath and .sympath+                                    *
*********************************************************************
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntoskrnl.exe - 
*******************************************************************************
WARNING: Local kernel debugging requires booting with kernel
debugging support (/debug or bcdedit -debug on) to work optimally.
*******************************************************************************
По умолчанию, отладчик использует в качестве локального хранилища символов свою папку "..\sym" - советую не менять этот путь, и переодически делать из неё бэкап. При первом запуске, в данном хранилище ничего нет - оно будет пополняться динамически с сервера Microsoft (вы должны быть On-line).

Для настройки символов, запустим WinDBG в Kernel-режиме [Ctrl+K] и в появившемся окне перейдём на вкладку 'Local'. Теперь в окне менюшки "File-->Symbol_File_Path" прошьём путь до локального хранилища, и через разделитель(*) укажем дополнительный сервер символов Microsoft. В конечном итоге, адрес должен иметь следующий формат:

SRV*C:\Program Files\Debugging Tools for Windows (x86)\sym*http://msdl.microsoft.com/download/symbols



Если на данном этапе вы находитесь в On-line, то после нажатия ОК отладчик начнёт скачивать в своё/локальное хранилище символы основных файлов ядра - это 'ntoskrnl.exe' и 'ntdll.dll'. Для начала этого набора символов будет достаточно, а остальные будут загружаться по мере необходимости, о которой WinDBG будет сам нас оповещать:

lkd> .reload
Connected to Win-XP 2600 x86 compatible target at (Fri May 10 14:01:43 2019), ptr64 FALSE
Loading Kernel Symbols
........................................ .......................
........................................ ................
Loading User Symbols
............................
Loading unloaded module list
.................

lkd> .sympath
Symbol search path is: SRV*C:\Program Files\Debugging Tools for Windows (x86)\sym*http://msdl.microsoft.com/download/symbols

Expanded Symbol search path is: srv*c:\program files\debugging tools for windows (x86)\sym*http://msdl.microsoft.com/download/symbols
Информацию о всех/доступных на данный момент модулях ядра, можно просмотреть командой lm (Load_Module). Если-же нужно сфокусировать своё внимание на "личном деле" конкретного модуля из списка, то используем многообещающую команду !lmi (Load_Module_Info) с указанием имени пациента. Кроме прочего, в логе будет строка "Symbol Type" где и указано, есть-ли символы в локальном хранилище для этого модуля, или их там нет (PDB not found). После скачивания символов, вы можете уйти в Off-line и продолжать дебажить уже вне сети:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
lkd> !lmi nt 
Loaded Module Info: [nt] 
         Module: ntoskrnl
   Base Address: 804d7000
     Image Name: ntoskrnl.exe
   Machine Type: 332 (i386)
     Time Stamp: 480f223d Wed Apr 23 16:49:17 2008
           Size: 238f00
       CheckSum: 24573a
Characteristics: 10e  perf
Debug Data Dirs: Type  Size     VA  Pointer
             CODEVIEW    25, 72a6c,   72a6c RSDS - GUID: {ACE55375-0D03-4C97-9A0D-BE2E1C6DCC2E}
               Age: 2, Pdb: ntoskrnl.pdb
                CLSID     4, 72a68,   72a68 [Data not mapped]
 
     Image Type: MEMORY   - Image read successfully from loaded memory.
    Symbol Type: PDB      - Symbols loaded successfully from symbol server.       <----------|
                 c:\program files\debugging tools for windows
(x86)\sym\ntoskrnl.pdb\ACE553750D034C979A0DBE2E1C6DCC2E2\ntoskrnl.pdb
    Load Report: public symbols, not source indexed 
                 c:\program files\debugging tools for windows
(x86)\sym\ntoskrnl.pdb\ACE553750D034C979A0DBE2E1C6DCC2E2\ntoskrnl.pdb
Как и любой продукт мелкософта, WinDBG имеет мощную справку в своей/родительской папке. Его маны представлены файлом "debugger.chm", вызвать который можно прямо из ком-строки командой .hh. Перед тем-как рваться в бой, имеет смысл ознакомится с основными командами отладчика, на специально посвящённом ему ресурсе windbg.info
3
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
10.05.2019, 13:12
Ответы с готовыми решениями:

Отладчик WinDBG
Как можно в отладчике WinDBG в окне Disassemble править содержимое, править команды ? Заранее Благодарен!!

Температура 6 ядерного i7 4930K
Здравствуйте. мне кажется данный процессор сильно греется, у меня в играх при авторазгоне с частотой 4300Мгц, температура по ядрам...

Многопоточность и загрузка 8-и ядерного процессора
Добрый день. Есть следующая ситуация: 1. Комп(i7 8 вирт. ядер; 8гб ОЗУ; ВЫНь7 х64; SSD для хранения данных; ХЕ10.2.3) 2. программа для...

7
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
12.05.2019, 15:54  [ТС]
Часть 3. Связываем WinDBG и VirtualBox.

Не удивительно, что для отладки ядра Windows нужны две машины, поскольку большинство ядерных процессов локальной системы уже находятся в памяти, и Win блокирует к ним доступ. Тем-более из прикладного уровня в который мы только-что заселили WinDBG. В локальном режиме отладчик может лишь добывать информацию о системных процессах, но не отлаживать их. В этом деле нет обходных путей, если только ..винда сама этого не захочет!

Изначально, эту "хотелку" прогеры Microsoft точили исключительно под себя, как технические люки из ринга(0) на ринг(3). В процессе отладки своего продукта им приходилось скакать по уровням в обоих направлениях, в результате чего система обзавелась т.н. "отладочным портом". Ничего лучше для этих целей и придумать не возможно.

В наш век использовать пару физических компьютеров уже не обязательно - для этого есть виртуальные машины любой "ориентации". Лично меня, во-всех отношениях устраивает "VirtualBox", примеры на которой я и буду приводить. Однако это не обязывает вас ни к чему, и вы можете использовать привычную для машину типа VM-Ware, QEMU, Virtual-PC и другие. Просто по личному опыту могу сказать, что процесс отладки на связке VBox+WinDBG происходит быстрее, и не грузит процессор по самые помидоры.

Систему, в ядро которой хотим проникнуть, ставим на виртуальную машину и будем называть её "Target" (целевая). WinDBG у нас под капотом реальной машины, к которой будем обращаться как "Host". Значит запускаем VirtualBox и топаем в её настройки. После того-как в разделе "COM-порт" создадим транспортный канал "Pipe", настройки виртуальной машины закончатся, не успев и начаться. Просто задайте их как на скрине, и всё:



После этого, необходимо настроить на отладку и саму (установленную на Target) операционную систему. Запускаем её! ..и в корне диска C: открываем для редактирования файл-конфигурации загрузки "boot.ini" (у меня хрюша, а для обладателей Win7+ конфиг задаётся через оснастку "bcdedit"). Нужно сказать, что именно здесь зарыта та-самая "кнопка", которая заставит винду открыть для нас ворота в свою святая/святых - исполнительное ядро! (модуль Executive в ntoskrnl.exe).

Дело в том, что мастдай может загружаться в нескольких режимах, типа "безопасный" и прочие (клавиша F8 после процедуры POST). Если посмотреть на этот лист, то в списке обнаружим и режим отладки "Debug-mode". В этом режиме, активируется специальная функция ядра, которая будет посылать отладчику сообщения по установленному нами пайпу (канал-хоста, см.рис.выше). При этом отладчик может фильтровать поступающие пакеты, отсеивая тем-самым неинформативные из них.

Режим отладки включается всего одним ключом /debug в файле "boot.ini". После этого ключа нужно указать номер и скорость информационного порта. Для редактирования, мы просто делаем дубликат первой записи, и добавляем отладочные ключи как на рисунке ниже (выделены красным). После этих манипуляций, при загрузке получим окно с выбором режима - система должна сама дополнить нашу запись текстом (с отладчиком):



На этом с виртуальной машиной покончено, и остаётся только настроить отладчик на хосте. Для этого запускаем WinDBG и комбинацией [Ctrl+K] выбираем режим его работы "Kernel". Отладчик отзывается окном, в котором просит задать параметры принимающего сообщения COM-порта. Не трудно догадаться, что здесь нужно ввести те-же данные, что и в настройках виртуальной машины:



После всех телодвижений можно считать, что наши подопытные связяны между собой крепкими узами, разорвать которые в силах только "фильтр пакетов" отладчика.

Установка связи между оппонентами должна происходить по следующей схеме..
Сначала запускаем ось на виртуальной машине и следом сразу отладчик (если сделать наоборот, отладчик не найдёт указанный в его настройках pipe). При этом, в окне отладчика должна красоваться надпись, которая характеризует правильные настройки портов на приёмо/передающих узлах:

Opened \\.\pipe\com_1
Waiting to reconnect......
Как-только мы выберем на таргет-машине загрузку в "Debug-mode", удалённая система сразу начнёт посылать отладочные сообщения хосту, и в его окне сначала появится техническая информация о клиенте, а потом и первый бряк int-3. В некоторых случаях, авто/подключение к виртуальной системе не срабатывает и нужно будет принудительно установить коннект, для чего служит комбинация [Ctrl+Break] в отладчике:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
   Opened \\.\pipe\com_1
   Waiting to reconnect...
Connected to Win-XP 2600 x86 compatible target at (Sun May 12 17:17:27.406 2019), ptr64 FALSE
Kernel Debugger connection established.
 
Symbol search path is: *** Invalid ***
****************************************************************************
* Symbol loading may be unreliable without a symbol search path.           *
* Use .symfix to have the debugger choose a symbol path.                   *
* After setting your symbol path, use .reload to refresh symbol locations. *
****************************************************************************
 
Executable search path is: 
*********************************************************************
* Symbols can not be loaded because symbol path is not initialized. *
*                                                                   *
* The Symbol Path can be set by:                                    *
*   using the _NT_SYMBOL_PATH environment variable.                 *
*   using the -y <symbol_path> argument when starting the debugger. *
*   using .sympath and .sympath+                                    *
*********************************************************************
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntoskrnl.exe - 
 
Windows XP Kernel Version 2600 (Service Pack 3) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp.080413-2111
Machine Name:
Kernel base = 0x804d7000 PsLoadedModuleList = 0x8055b1c0
 
Debug session time: Sun May 12 19:18:23.792 2019
System Uptime: 0 days 0:01:12.594
Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*   You are seeing this message because you pressed either                    *
*       CTRL+C (if you run kd.exe) or,                                        *
*       CTRL+BREAK (if you run WinDBG),                                       *
*   on your debugger machine's keyboard.                                      *
*                                                                             *
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *
*                                                                             *
* If you did not intend to break into the debugger, press the "g" key, then   *
* press the "Enter" key now.  This message might immediately reappear.  If it *
* does, press "g" and "Enter" again.                                          *
*******************************************************************************
 
nt!DbgBreakPointWithStatus+0x4:
804e3592 cc              int     3
Если вы тоже это видите, то вас есть с чем поздравить - вы в ядре системы Win!
Обязательным условием является то, что оси таргет и хост должны быть одинаковы. Теперь, перезагрузив отладочные символы (.reload) можно получить список активных процессов клиента, подключится к любому из них и отлаживать, погружаясь всё глубже в нёдра кода и ядерных структур.
2
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
14.05.2019, 15:01  [ТС]
Часть 4. Системные механизмы

Особенности работы Win в режиме отладки

Чтобы понять принцип работы отладчиков режима ядра, нужно рассмотреть исполняющую систему оси "Executive". Она занимает львиную долю Kernel-пространства и содержит различные сервисы ядерной инфраструктуры. Помимо прочего, в эти сервисы включена и поддержка отладки ядра, реализованная внутри системных библиотек dbgeng.dll и dbghelp.dll.

На первый взгляд может показаться, что эти либы попали в систему при установке WinDBG, но это не так - начиная с ХР они изначально присутствуют в ней (посмотрите в свойствах дату создания/изменения). Если вскрыть в дизассемблере W32Dasm таблицу-экспорта этих длл, можно обнаружить там API, имена которых WinDBG использует непосредственно как свои/консольные команды! В частности, это уже знакомые нам lm/lmi/dh/sym. Следовательно, сам WinDbg - это только оболочка для отладки, с помощью системного движка:

Кликните здесь для просмотра всего текста
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
• Disassembly of File: dbgeng.dll
    Number of Exported Functions = 0002 (decimal)
        Name: DebugConnect
        Name: DebugCreate
 
• Disassembly of File: dbghelp.dll
    Number of Exported Functions = 0098 (decimal)
        Name: dbghelp
        Name: dh
        Name: lm
        Name: lmi
        Name: sym
 
        Name: DbgHelpCreateUserDump
        Name: FindDebugInfoFile
        Name: FindExecutableImage
        Name: ImageNtHeader
        Name: MapDebugInformation
        Name: MiniDumpReadDumpStream
        Name: MiniDumpWriteDump
        Name: StackWalk
        Name: SymGetModuleBase
        Name: SymGetModuleInfo
        Name: SymGetTypeInfo
        Name: SymInitialize
        Name: SymLoadModule
        Name: SymUnloadModule
        Name: UnmapDebugInformation
        Name: WinDbgExtensionDllInit
.......


Но это только вершина айсберга! К судьбе движка небезучастна и сама исполняющая система, зарытая глубоко в файлах ntoskrnl.exe и ntdll.dll. Её разбор на техническом языке займёт много времени, поэтому поставим архитектуру NT на детские рельсы и посмотрим на неё с другой стороны.

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

• В первой части юзера располагаются пользовательские программы, опирающиеся на библиотеки "user/kernel32.dll", а вторую часть занимает системная библиотека "Ntdll.dll".

• В свою очередь, в пространстве кернела первая часть отводится под исполняющую систему "Ntoskrnl.exe", а нижняя - это слой абстракции от реального железа "Hal.dll" (хотя есть ещё и драйверы, которые стоят особняком).

Благодаря Ntdll.dll наши программы могут переходить рубикон из юзера в кернел, сразу-же попадая в поле зрения ядра ntoskrnl. Здесь нужно отметить, что тушка файла ntoskrnl.exe разбита на два самостоятельных модуля - это непосредственно "Микроядро" (или гибридное ядро, что одно и тоже), и исполняющая система ядра "Executive":



Если говорить про либу Ntdll, то она находится в пространстве юзера и содержит подмножество вспомогательных функций ядра таких-как: загрузчики образов программ с диска (префикс Ldr), диспетчер динамической памяти (префикс Mem), функции обмена данными между процессами подсистемы Win32 (префикс Csr), общие библиотеки времени выполнения (префикс Rtl), поддержку юзерской отладки (префикс DbgUi) и отслеживания событий Win (префикс Etw, эвенты). Кроме того, именно в её окопах залегли диспетчер асинхронных вызовов процедур (АРС) и диспетчер исключений пользовательского режима (SEH).


Объектно-ориентированный подход

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

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

С понятием "объект" тесно связан "дескриптор объекта" - система возвращает его нам в ответ, например, открытия файла. Всякий объект имеет одинаковый для всех "заголовок", где прописаны исчерпывающие характеристики данного объекта (его пасспорт). В момент, когда мы открываем файл, система создаёт дубликат своего объекта типа "File", и оформив его в виде дескриптора, вручает нам. Процедура оформления состоит из сброса/установки определённых полей в заголовке-объекта, исходя из наших привилегий в системе.



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

"Квота" представляет собой "откат", взимаемый с процесса за открытие объекта. Например, если диспетчер установил цену для объекта "File" в 2 шекеля (не знаю в каких попугаях она измеряется), а процесс принадлежит к заданию (Job) у которого в портмоне всего 10 шекелей, то суммарно все процессы этого Job'a смогут открыть не более 5-ти файлов. Так для объектов каждого типа устанавливаются ограничения на ресурсы.

Весь пул объектов под названием "Пространство имён" отсортирован по "типам". Это означает, что у каждого типа объектов есть общие (для данного типа) свойства. Информация о типе выведена в отдельную структуру, членами которой являются "имя" и прочие (см.рис). Каждый объект указывает на свой "объект-типа". И наконец поля "методов" хранят указатели на обработчики стандартных операций, например open/close.

Для просмотра таблицы-объектов Руссинович написал утилиту "WinObj". Однако переплюнул его знаменитый хакер Four-F, который внёс огромный вклад в область программирования и разработки драйверов. Его аналог называется "WinObjEx" (Object_Explorer) и вытягивает из системы намного больше информации, чем утилита Руссиновича:



Как видим, в этой синагоге находятся множество объектов типа,
но в контексте темы-отладки нас будут интересовать только два из них - объект PORT, и объект Debug. Мы вернёмся к ним позже (никогда), а пока посмотрим на описание каталогов в пространстве имён:
Code
1
2
3
4
5
6
7
8
9
10
11
??               Место поиска устройств MS-DOS, например С:\
Device           Все обнаруженные устройства ввода-вывода;
Driver           Объекты, связанные с каждым драйвером устройств;
ObjectTypes      Объекты типов (как рисунке выше);
Windows          Объекты для отправки сообщений всем окнам мастдая;
BaseNamedObjs    Объекты создаваемые юзером (семафоры, мьютексы);
ArcName          Имена разделов, обнаруженные загрузчиком;
NLS              Объекты языковой поддержки;
FileSystem       Объекты драйверов файловой системы;
Security         Система безопасности;
KnownDlls        Совместно используемые библиотеки.
2
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
18.05.2019, 18:04  [ТС]
Собираем информацию об объектах приложения

Посмотрим, какую инфу о системных объектах может предоставить нам WinDbg..
Как упоминалось выше, у каждого объекта есть заголовок и тело. Диспетчер объектов управляет заголовками, а телами занимается Executive. В каждом заголовке объекта содержится индекс на структуру "объект-типа" (Object_Type), которая и характеризует тип объекта.

Одной из незаменимых команд является dt (Display_Type), с помощью которой можно просмотреть буквально все/системные структуры. На вооружение нужно взять тот факт, что в WinDbg имена структур должны начинаться с подчёркивания, поскольку в системе они зарегистрированы именно в таком виде. Например, чтобы ознакомиться с членами структуры PEB, нужно ввести dt _peb, предварив её имя подчёркиванием.

Кроме того, в некоторых структурах могут присутствовать члены, которые сами указывают на принадлежащие к данной структуре, вложенные структуры. Для рекурсивного обхода таких структур, команда "dt" имеет ключик "-r" (рекурсия, нет в документации). Справку по этой и другим командам из окна отладчика можно вызвать так: .hh dt, где dt является аргументом.

Перед тем-как дать команду на отображение "dt", мы должны знать имя интересующей нас структуры. Нужно сказать, что общее кол-во структур в системе может в несколько раз превышать числа растительности на голове у некоторых из нас - как найти нужную? Оказывается очень просто! Достаточно указать в каком именно модуле exe/dll мы хотим произвести поиск, и подставить в качестве аргумента, обрамлённую символами(*) маску для поиска. В данном случае модуль будет Ntoskrnl (кличка "nt"), а маска - "_object". Вот пример:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
lkd> .reload                          ;<----- ребут отладочных символов
lkd> dt nt!*_object*
          ntoskrnl!_DEVICE_OBJECT
          ntoskrnl!_OBJECT_ATTRIBUTES
          ntoskrnl!_OBJECT_TYPE
          ntoskrnl!_OBJECT_TYPE_INITIALIZER
          ntoskrnl!_OBJECT_HANDLE_INFORMATION
          ntoskrnl!_FILE_OBJECT
          ntoskrnl!_OBJECT_SYMBOLIC_LINK
          ntoskrnl!_SECTION_OBJECT
          ntoskrnl!_SEGMENT_OBJECT
          ntoskrnl!_SECTION_OBJECT_POINTERS
          ntoskrnl!_DRIVER_OBJECT
          ntoskrnl!_DEVICE_OBJECT_POWER_EXTENSION
          ntoskrnl!_OBJECT_DUMP_CONTROL
          ntoskrnl!_OBJECT_NAME_INFORMATION
          ntoskrnl!_OBJECT_HEADER
          ntoskrnl!_OBJECT_CREATE_INFORMATION
          ntoskrnl!_OBJECT_HEADER_CREATOR_INFO
          ntoskrnl!_OBJECT_HEADER_NAME_INFO
          ntoskrnl!_OBJECT_DIRECTORY
          ntoskrnl!_OBJECT_DIRECTORY_ENTRY
          ntoskrnl!_LPCP_PORT_OBJECT
          ntoskrnl!_ADAPTER_OBJECT
Как-видим, тут есть из чего выбрать. Эксперимента ради, можно поискать связанные с объектами структуры и в других модулях, например Ntdll или Hal. Однако ничего нового вы там не обнаружите, поскольку оба/этих модуля тупо "стреляют сигареты" у Ntoskrnl - они импортируют из этого списка только нужные для себя структуры.

По именам структур можно догадаться об их назначении. К примеру, в наличии имеются уже знакомые нам "_OBJECT_HEADER" (заголовок объекта) и "_OBJECT_TYPE" (выведенный в отдельную структуру, описатель типа объекта). Обратите внимание, что заголовок находится в пространстве кернела (nt!), а тип - отфутболен уже в пространство юзера (ntdll!). Как упоминалось выше, завершающий команду ключ(-r) позволяет сходу просмотреть и вложенные структуры:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
lkd> dt _object_header
nt!_OBJECT_HEADER
   +0x000 PointerCount       : Int4B
   +0x004 HandleCount        : Int4B
   +0x004 NextToFree         : Ptr32 Void
   +0x008 Type               : Ptr32 _OBJECT_TYPE
   +0x00c NameInfoOffset     : UChar
   +0x00d HandleInfoOffset   : UChar
   +0x00e QuotaInfoOffset    : UChar
   +0x00f Flags              : UChar
   +0x010 ObjectCreateInfo   : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged  : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body               : _QUAD
 
lkd> dt _object_type
ntdll!_OBJECT_TYPE
   +0x000 Mutex                    : _ERESOURCE
   +0x038 TypeList                 : _LIST_ENTRY
   +0x040 Name                     : _UNICODE_STRING
   +0x048 DefaultObject            : Ptr32 Void
   +0x04c Index                    : Uint4B
   +0x050 TotalNumberOfObjects     : Uint4B
   +0x054 TotalNumberOfHandles     : Uint4B
   +0x058 HighWaterNumberOfObjects : Uint4B
   +0x05c HighWaterNumberOfHandles : Uint4B
   +0x060 TypeInfo                 : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key                      : Uint4B
   +0x0b0 ObjectLocks              : [4] _ERESOURCE
Команда "Display Type" может форматировать свыше 400 структур ядра (не в обиду лысым будет сказано) благодаря тому, что именно файлы-символов ядра содержат информацию о типах - без них мы ничего не увидим. WinDbg продуман до мелочей, где каждая строчка несёт в себе важную для исследователя информацию. Тот-же префикс nt/ntdll перед именем структуры позволят лучше понять общую архитектуру ОСи уточняя, на каком конкретно уровне происходит то или иное действие.

Хорошо.. А что если нужно узнать, какие из системных объектов использует наше приложение?
Для этого напишем подопытного кролика "Hello Word!", который сбоксит мессагу и будет ждать нажатия буттона "ОК" в своём окне. Наша задача, загрузить кролика в память и подключиться к его процессу через "Attach-to-Process" отладчика (клавиша F6). На этот момент системный загрузчик образов уже полностью проинициализирует все модули нашего приложения, и нам останется только снять с них скальп.

В арсенале WinDbg есть команда !handle, которая допускает сл.аргументы:
!handle ‹индекс_описателя› ‹флаги› ‹ID_процесса›
Из курса начинающих программистов мы помним, что "Handle" (описатель) - это и есть дескриптор, а дескриптор в свою очередь формируется из заголовка объекта. То есть мы на правильном пути.. Индекс в команде определяет элемент в таблице дескрипторов (0 = вывод всех). Индекс первого =4, второго =8, и т.д. Например, введя !bandle 4, вы увидите первый дескриптор в текущем процессе. Кроме того, мы можем фильтровать вывод битовыми флагами:

• 001b (1) = вывести лишь информацию из элемента таблицы;
• 010b (2) = показать не только используемые, но и свободные описатели;
• 100b (4) = сообщить информацию об объекте, на который ссылается описатель;
• 111b (7) = выдать полную информацию!

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0:001> !handle 0 7
Handle 4
  Type          KeyedEvent
  Attributes    0x10
  GrantedAccess 0xf0003:   Delete,ReadControl,WriteDac,WriteOwner,Wait,Wake
  HandleCount   29
  PointerCount  31
  Name          \KernelObjects\CritSecOutOfMemoryEvent
 
Handle 18
  Type          Port
  Attributes    0
  GrantedAccess 0x1f0001:  Delete,ReadControl,WriteDac,WriteOwner,Synch,Connect
  HandleCount   2
  PointerCount  4
  Name          <none>
----------------------
Total 38 Handles
Event           5
Section         6
File            2
Port            2
Directory       3
Mutant          8
WindowStation   2
Semaphore       2
Key             2
Thread          1
Desktop         1
IoCompletion    3
KeyedEvent      1
На уровне ядра команда !handle возвращает немного другую информацию..
Во-первых тут нужно запросить у системы список всех/текущих процессов в памяти, среди которых должен быть и наш "Hello World" (если мы не закрыли его MessageBox() кнопкой ОК). Если таковой присутствует в памяти, то нам потребуется его Cid - Client_ID, который подставляем как третий аргумент в команду !handle:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
    PROCESS 890fc020  SessionId: 0  Cid: 0a08    Peb: 7ffdf000  ParentCid: 0d7c
    DirBase: 30bc8000  ObjectTable: e13caaf0  HandleCount:  38.
    Image: Hello.EXE
 
lkd> !handle 0 7 a08
Searching for Process with Cid == a08
    PROCESS 890fc020  SessionId: 0  Cid: 0a08    Peb: 7ffdf000  ParentCid: 0d7c
    DirBase: 30bc8000  ObjectTable: e13caaf0  HandleCount:  38.
    Image: Hello.EXE
 
Handle table at e34a3000 with 38 entries in use
 
0004: Object: e1009698  GrantedAccess: 000f0003 Entry: e34a3008
      Object: e1009698  Type: (89befe70) KeyedEvent
      ObjectHeader: e1009680 (old version)
      HandleCount: 28  PointerCount: 29
      Directory Object: e1001190  Name: CritSecOutOfMemoryEvent
 
0018: Object: e2bb06b0  GrantedAccess: 001f0001 (Protected) Entry: e34a3030
      Object: e2bb06b0  Type: (89c17758) Port
      ObjectHeader: e2bb0698 (old version)
      HandleCount: 1  PointerCount: 2
Добавлено через 13 минут
..заполненные заголовки объектов и прочие структуры можно выводить на экран следующим способом. Здесь адреса являются указателями - их и ждёт команда DisplayType:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
    PROCESS 89a2a568  SessionId: 0  Cid: 0c24    Peb: 7ffd9000  ParentCid: 0d7c
    DirBase: 624a6000  ObjectTable: e13d5a90  HandleCount:  38.
    Image: Hello.EXE
 
lkd> !object 89a2a568
    Object: 89a2a568  Type: (89bf3040) Process
    ObjectHeader: 89a2a550 (old version)
    HandleCount: 2  PointerCount: 11
 
lkd> dt _Object_Header 89a2a550
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n11
   +0x004 HandleCount      : 0n2
   +0x004 NextToFree       : 0x00000002 Void
   +0x008 Type             : 0x89bf3040 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x20 ' '
   +0x010 ObjectCreateInfo : 0x894b8b68 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x894b8b68 Void
   +0x014 SecurityDescriptor : 0xe2957459 Void
   +0x018 Body             : _QUAD
1
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
22.05.2019, 21:25  [ТС]
Диспетчер системных сервисов и таблица SSDT

В пользовательском пространстве обитает множество системных служб. Некоторые экземпляры запускаются из автозагрузки, некоторые простаивают, а некоторые вообще отключены админом. Чтобы ознакомиться с полным списком и состоянием каждой из них, достаточно запустить одноимённую оснастку через Win+R-->services.msc. Как видно из названия команды, синонимом слова "служба" является английское "servis".

Если проштудировать имена этих служб, то можно обнаружить, что вся/эта шайка представляет из-себя сборище сервисов подсистемы окружения Win32. Кроме перечисленных, в системе имеется ещё вагон и целая тележка, только они не для домохозяек, под которых собственно и заточена Windows всех мастей. Системный диспетчер служб SCM (Service_Control_Management) умышленно скрывает от юзера жизненно/важные свои сервисы, чтобы обезопасить себя от любопытных глаз.

Возьмём к примеру вызов функций Win32-API.. Каждая из этих функций тоже представляет собой определённый сервис, но зачем конечному пользователю знать о его существовании? Для юзера главное чтобы пимпа была красивой, и чтобы на её нажатие что-то происходило (кроме зависания машины). Вывод - система от нас явно что-то скрывает, и мы обязательно должны выяснить, что.

Рассмотрим в подробностях, как происходят вызовы программного интерфейса API..
Для начала, чтобы создать общую картину, взглянем на табличку ниже, где представлена организация критически-важных файлов Win. Это данные, которые я собрал со-своей XP, так-что за остальные оськи не ручаюсь.



Судя по всему, сервисы подсистемы Win32 не сильно обременяют себя работой, лишь изредка прибегая к чьим-либо услугам. Причём сами что-то конкретное делать упорно не хотят, т.к. не имеют в своих тушках ни одной/экспортируемой функции. Это типичные доносчики, которые только и "стучат" ядру о происходящих в пространстве юзера событиях.

Другое дело Ntdll - заслуженный труженик, который не импортирует ни одного тушканчика, зато выдаёт на экспорт аж 1316 функций. Вот если-бы все так пахали! Но у каждой/положительной, есть и отрицательная сторона. Если размазать функции по-всем библиотекам, то получим винегрет и проблему переносимости системы, за что лентяи/программисты из Microsoft так крепко держатся. Для каждой/последующей системы Win7,8,10 пришлось-бы переписывать все системные файлы, а так - только парочку.

Что касается библиотек x32, то дела здесь обстоят лучше. Они выполняют рутину по обслуживанию прихотей юзера - всякие окошки, менюшки, иконки, и прочая лабуда. А вот чего они не смогут сделать, так это создавать/завершать процессы, потоки, работать с файлами, etc.. Другими словами, юзер не может сам создавать системные объекты - для этого есть Executive, к которой рано или поздно нужно будет обратиться за помощью. Вот тут-то и начинается самое/интересное..

Защищённая подсистема Win32 работает в режиме пользователя, вызывая системный сервис NT для выполнения привилегированных действий в режиме ядра. Когда наше приложение вызывает какую-либо API, диспетчер анализирует запрос и выполняет его либо обращаясь к ядру, либо перенаправляя запрос библиотекам окружения Win32 (если это работа с плюшечками/дрюшечками). После завершения процедуры, приложение вручается мандат с результатами.

Мы рассмотрим только механизм обращения программы к ядру, не затрагивая тему локальной обработки в юзер-моде. Для этого напишем всего одну строчку кода, которая закроет объект "Process" текущего приложения по ExitProcess(). Теперь загрузим это чудо в WinDbg по и поставим бряк на эту функцию:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
CommandLine: C:\TEMP\R71MT.EXE
Executable search path is: 
ModLoad: 00400000 00403000   image00400000
ModLoad: 7c900000 7c9b0000   ntdll.dll
ModLoad: 7c800000 7c8f8000   C:\WINDOWS\system32\kernel32.dll
(db0.e3c): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
7c90120e cc              int     3
;---------------------------------
0:000> bp kernel32!ExitProcess         ;<--- точка останова на функцию
0:000> bl                              :<--- проверить ("e" означает Enable)
 0 e 7c81cafa     0001 (0001)  0: kernel32!ExitProcess
 
0:000> g                 ;<--- Go (запустить программу)
Breakpoint 0 hit         ;<--- сработал нулевой бряк!
kernel32!ExitProcess:
7c81cafa 8bff            mov     edi,edi
 
0:000> p                 ;<--- шаг без входа в функцию
7c81cafc 55              push    ebp
7c81cafd 8bec            mov     ebp,esp
7c81caff 6aff            push    0FFFFFFFFh
7c81cb01 68b0f3e877      push    77E8F3B0h
7c81cb06 ff7508          push    dword ptr [ebp+8]    ss:0023:0006ffc0=00000000
7c81cb09 e846ffffff      call    kernel32!_ExitProcess (7c81ca54)
 
0:000> t                 ;<--- шаг со-входом,
;......                           и чуть погодя видим..
kernel32!_ExitProcess+0x35:
7c81ca89 ffd6            call    esi {ntdll!ZwTerminateProcess (7c90de50)}
0:000> t
ntdll!ZwTerminateProcess:                       ;<--- ядерная функция убивающая процесс!!!
7c90de50 b801010000      mov     eax,101h       ;<--- номер функции в ядре = 101h
7c90de55 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c90de5a ff12            call    dword ptr [edx]   ; ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c90e4f0)}
0:000> t
ntdll!KiFastSystemCall:
7c90e4f0 8bd4            mov     edx,esp
7c90e4f2 0f34            sysenter
7c90de5c c20800          ret     8
В этом листинге, всё/что находится до ZwTerminateProcess() это лишь прелюдия. Шоу дельфинов начинается когда номер запроса помещается в EAX и управления принимает инструкция sysenter. Именно эта инструкция позволяет коду пользовательского режима перейти на ядерный уровень - других путей в этом лесу нет!

Однако WinDbg не показывает нам, что происходит за кулисами sysenter, если только не заставить его этого сделать. Открыв толмут Intel vol.4 с описанием MSR-регистров мы обнаруживаем, что регистры с номерами: 174h, 175h, 176h хранят явный указатель, куда передаёт управление sysenter. Запросим их содержимое, после чего можно дизассемблировать код по указателю EIP:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lkd> rdmsr 175                             ;<--- Read MSR
       msr[175] = 00000000`f78a3000        ;<--- IA32_SYSENTER_ESP
lkd> rdmsr 174
       msr[174] = 00000000`00000008        ;<--- IA32_SYSENTER_CS   (8 = селектор ядра)
lkd> rdmsr 176
       msr[176] = 00000000`804de6f0        ;<--- IA32_SYSENTER_EIP  (точка входа)
;----------------------------------
lkd> u 804de6f0                            ;<--- дизассемблировать EIP
nt!KiFastCallEntry:
804de6f0 b923000000      mov     ecx,23h
804de6f5 6a30            push    30h
804de6f7 0fa1            pop     fs
804de6f9 8ed9            mov     ds,cx
804de6fb 8ec1            mov     es,cx
....
Ладно, точку нашли.. Но без теории, с вероятностью близкой к нулю, мы ничего из этой портянки не поймём. Значит нужно с головою уйти в доки (от чего я вас избавил), и познакомиться на этот раз с диспетчером сервисов ядра, начинка которого заложена в функцию KiSystemService(). В прямом подчинении этого диспетчера имеются две таблицы SSDT - System_Service_Descriptor_Table, одна из которых затенённая (shadow) и недоступна никому, кроме самого Ntoskrnl. Вторая - открытая, куда так и норовят пролезть руткины, для перехвата Kernel-API.

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

По своей природе, таблицы SSDT напоминают таблицу прерываний MS-DOS. В них отсортированы по именам все/имеющиеся kernel-API с указанием их точек-входа в функцию. В примере выше с ExitProcess() мы наблюдали, что перед вызовом sysenter в EAX кладётся значение 101h - это и есть порядковый номер ZwTerminateProcess() в таблице SSDT. Формат этих структур представлен ниже (почему-то WinDbg их не отображает):
Code
1
2
3
4
5
6
7
8
9
; ntddk.inc
;----------
struc SYSTEM_SERVICE_TABLE
 {
  .ServiceTable   dd ?        ; указатель на таблицу SSDT
  .CounterTable   dd ?        ; резерв (не используется)
  .ServiceLimit   dd ?        ; всего записей в таблице SSDT
  .ArgumentTable  dd ? }      ; указатель на массив байт SSDP (табл.параметров системной службы), 
                              ; где каждый байт/4 определяет кол-во аргументов функции.
Как видим - это характеристики SSDT. В структуре имеется указатель на начало таблицы, общее число функций в ней, и (наиболее интересный) указатель на дочернюю таблицу SSDP. Эта таблица чем-то напоминает битмап, только вместо битов байты. Когда ядро ищет порядковый номер нужной функции в SSDT, оно по-ходу сканирует и таблицу SSDP, из которой потом берёт байт с тем-же номером, и разделив его на 4 (dword) выделяет в стеке соответствующий фрейм для аргументов.

Кроме того, в ntddk можно найти ещё одну структуру,
которая описывает уже расположения таблиц SSDT в памяти:
Code
1
2
3
4
5
6
7
struc SERVICE_DESCRIPTOR_TABLE 
 {
  .ntoskrnl SYSTEM_SERVICE_TABLE          ;<--- формат описателя открытой таблицы
  .win32k   SYSTEM_SERVICE_TABLE          ;<--- формат описателя таблицы работы с графической
  .unused1  SYSTEM_SERVICE_TABLE          ;<--- свободно (для наших потомков)
  .unused2  SYSTEM_SERVICE_TABLE          ;<---   ^^^
 }
Теперь займёмся практикой..
Команда WinDbg с громким названием x позволяет искать по-маске функции в модулях. Для наших целей маска "KeServiceDescriptorTable" как-раз то, что доктор прописал. В качестве модуля укажем Ntoskrnl под кличкой "nt!" - пробуем..
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
lkd> x nt!*KeServiceDescriptorTable*            ;<--- ищем в модуле по-маске..
82da2980 nt!KeServiceDescriptorTableShadow      ;<--- упс! нашлась Shadow таблица,
82da2940 nt!KeServiceDescriptorTable            ;<---   ..и прицепом - открытая.
;-------
; поскольку в структуре 4 дворда, то указываем длину(L) 4*4=16 или 10h
; команда "dps" называется "Display Words and Symbols"
;-------
lkd> dps nt!KeServiceDescriptorTableShadow L 10
8055a1e0  804e26a8 nt!KiServiceTable            ;<--- смотрим на структуры выше, и делаем выводы.
8055a1e4  00000000                              ;<--- резерв
8055a1e8  0000011c                              ;<--- всего функций в таблице SSDT: 11c (или 284)
8055a1ec  80510088 nt!KiArgumentTable           ;<--- указатель на таблицу аргументов
8055a1f0  bf999b80 win32k!W32pServiceTable      ;<--- начало второй таблицы из Shadow
8055a1f4  00000000
8055a1f8  0000029b                              ;<--- всего функци в win32k.sys: 29b (или 667)
8055a1fc  bf99a890 win32k!W32pArgumentTable     ;<--- её аргументы
8055a200  00000000                              ;<--- для потомков..
8055a204  00000000
8055a208  00000000
8055a20c  00000000
8055a210  00000000
8055a214  00000000
8055a218  00000000
8055a21c  00000000
Точно так-же можно уткнуться в таблицу SSDP с байтами аргументов.
Напомню, чтобы получить кол-во параметров функции, нужно каждый байт разделить на 4.
В данном случае, байт по смещению(0) равен 18h, значит фрейм именно такого размера понадобится первой API из списка SSDT. Разделив 18h на 4 получим число её параметров = 6. Чтобы вывести дамп в байтах, вскормим консоль командой "db":
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
lkd> db nt!KiArgumentTable L 11c
80510088  18 20 2c 2c 40 2c 40 44-0c 08 18 18 08 04 04 0c  . ,,@,@D........
80510098  10 18 08 08 0c 04 08 08-04 04 0c 08 0c 04 04 20  ............... 
805100a8  08 10 0c 14 0c 2c 10 0c-0c 1c 20 10 38 10 14 20  .....,.... .8.. 
805100b8  24 24 1c 14 10 20 10 34-14 08 0c 08 04 04 04 04  $$... .4........
805100c8  0c 08 28 04 1c 18 08 18-0c 18 08 18 0c 08 0c 04  ..(.............
805100d8  10 00 0c 10 28 08 08 10-1c 04 08 0c 04 10 08 00  ....(...........
805100e8  08 04 08 0c 28 08 04 10-04 04 0c 0c 28 04 24 28  ....(.......(.$(
805100f8  30 0c 0c 0c 18 0c 0c 0c-0c 30 10 0c 10 0c 0c 0c  0........0......
80510108  10 10 14 0c 0c 14 0c 18-14 14 08 08 08 08 08 08  ................
80510118  04 2c 1c 24 14 08 14 14-14 14 14 14 14 04 08 14  .,.$............
80510128  14 18 14 14 08 08 24 14-14 14 0c 10 14 10 04 14  ......$.........
80510138  0c 18 18 14 14 0c 18 24-24 18 14 04 08 0c 14 08  .......$$.......
80510148  08 0c 08 10 14 08 04 08-0c 04 08 0c 0c 04 08 08  ................
80510158  0c 0c 24 08 08 08 0c 04-08 04 10 08 04 04 04 14  ..$.............
80510168  14 10 10 10 10 10 10 08-14 18 04 04 10 0c 08 14  ................
80510178  0c 0c 08 08 1c 0c 04 18-14 04 10 04 04 04 08 18  ................
80510188  08 08 08 00 10 10 04 04-08 14 10 08 08 10 14 0c  ................
80510198  04 04 24 24 18 14 00 10-0c 10 10 00              ..$$........
Если во-время не остановиться, по такой-же схеме можно просмотреть и саму таблицу SSDT - святая/святых, где хранится игла Кащея. Если через дров проникнуть туда, то можно подменять точки-входа в нативные API. Таким макаром перехватываются функции ядра:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
lkd> dps nt!KiServiceTable L 11c
804e26a8  8058fdf7 nt!NtAcceptConnectPort            ;<--- функция kernel-API, под кличкой EAX =1
804e26ac  805756e8 nt!NtAccessCheck
804e26b0  80588d6d nt!NtAccessCheckAndAuditAlarm
804e26b4  80591132 nt!NtAccessCheckByType
......
804e2aa4  80649d23 nt!NtSystemDebugControl
804e2aa8  80630235 nt!NtTerminateJobObject
804e2aac  805836b4 nt!NtTerminateProcess             ;<--- наша функция с EAX =101h
804e2ab0  8057b49c nt!NtTerminateThread
.....
804e2af0  80576f5d nt!NtWriteFile
804e2af4  805da465 nt!NtWriteFileGather
804e2af8  8058aa8a nt!NtWriteRequestData
804e2afc  8057f19c nt!NtWriteVirtualMemory
804e2b00  804f0ea6 nt!NtYieldExecution
804e2b04  805cbd8d nt!NtCreateKeyedEvent
804e2b08  80582a00 nt!NtOpenKeyedEvent
804e2b0c  8064a197 nt!NtReleaseKeyedEvent
804e2b10  8064a432 nt!NtWaitForKeyedEvent
804e2b14  8062d4fd nt!NtQueryPortInformationProcess  ;<--- последняя функция с EAX =11сh
Под занавес, предлагаю посмотреть на рисунок, где в визуальной форме подведена черта под вышесказанным. Из значения в EAX, значимыми являются только младшие 13-бит, причём биты[11:0] система использует в качестве индекса в таблице SSDT (всего 4096 возможных записей), а бит[12] выбирает одну из двух таблиц - Shadow или стандартная:



Если вы не в танке, и понимаете о чём я тут "пою серенады", то возможно зададитесь вопросом: -"А как насчёт вызова натива не из юзера, а из самого кенела ..например драйверами. Тут-же идёт прямой вызов без Sysenter?" Это будет заслуживающий аплодисментов стоя вопрос! Ради интриги, оставим его открытым - кому интересно, сам найдёт инфу, например у Руссиновича или ресурсах по борьбе с руткитами.
1
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
22.05.2019, 22:33  [ТС]
Лучший ответ Сообщение было отмечено Mikl___ как решение

Решение

..и ещё. Поскольку ядро Ntoskrnl доверять свою работу никому не может (ниже только плинтус),
то все точки-входа в его функции должны находиться в одной базе 80000000h. Если при анализе адресов мы обнаружим выход за эту границу, то можем с чистой совестью утверждать, что в ядре завелися блохи и от них срочно нужно избавляться. Все аверки проверяют на руткиты именно таким образом:


lkd> dd nt!KiServiceTable L 11c ;<----------- Display Dword Memory
804e26a8 8058fdf7 805756e8 80588d6d 80591132
804e26b8 8058ee57 806380be 8063a247 8063a290
804e26c8 80573c0e 80649087 80637875 8058e475
804e26d8 8062f9bc 8057a77f 80589cfc 8062693f
804e26e8 805dd3c9 80569163 805d9767 805a24d2
804e26f8 804e2cb4 8064909b 805c9b1e 804ecfac
804e2708 8056980f 80567a7d 8058e8e3 8064e97c
804e2718 8058aaec 80590b3f 8064ebe9 80588dbf
804e2728 804e1ff2 8065a094 805a289a 8056fdca
804e2738 8064918c 8056f610 8059138b 805ab1c8
804e2748 8062fe67 f74d70e0 805d9658 8057ab4f
804e2758 80585313 805bbdcf 805975b9 805b1372
804e2768 80581034 806497c3 805652b3 80579605
804e2778 8059f511 8057bd80 8059e5ed 805a8b70
804e2788 805db124 8065b20d 8065b367 80566410
804e2798 80588859 8064909b 805d800b 805952c2
804e27a8 8063a2eb 80592d54 8057cb36 805bef99
804e27b8 80573ff9 8057e40e 80649087 f74f5ca2
804e27c8 80648b13 f74f6030 80625760 805b0b56
804e27d8 8058ad7c 805889d6 8056e43a 805dc590
804e27e8 8059acd4 806271a3 80626cf4 80569a8d
804e27f8 8057a667 805e03f3 8062c1a3 8059fdd1
804e2808 8053b75d 805975dd 8058a558 8057f3b3
........
1
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
28.05.2019, 20:15  [ТС]
Debug API, или что под капотом отладчиков

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

В принципе, всё-что требуется от желающего написать юзермодный отладчик, это расставить под "правильным углом" его форточки и определиться, что именно он хотел-бы видеть в окне своего творения. На данный этап уходит больше времени, чем на процесс оформления полезного кода. Для остального Win имеет мощный, и в тоже время, очень простой "движок отладки". Его начинку составляет группа функций под общим названием Debug-API, оптом предлагающие нам услуги следующего характера:
  • Загpузить или подключиться к клиенту для его отладки (CreateProcess);
  • Собрать полную инфоpмацию о нём (GetStartupInfo);
  • Принимать от клиента уведомления об отладочных событиях (Debug-Event);
  • Дать возможность клиенту сделать шаг (Continue-Debug);
  • Изменять принадлежащую клиенту память (Write-Memory).

Теперь распишем в красках общую картину..


Step 1. Начальный этап отладки

Работа отладчиков основана на модели клиент-сервер, где в роли сервера выступает отладчик. Он выбирает в качестве жертвы другую программу, которую будем называть "клиент". Суть в том, что из своей тушки, отладчик должен запустить процесс клиента присвоив ему флаг DEBUG_PROCESS - на это способна функция CreateProcess(). Связанные с отладкой флаги этой API (из директории "fasm\include\equates\kernel32.inc") представлены ниже:
Assembler
1
2
3
4
; CreateProcess flags
;--------------------
  DEBUG_PROCESS            = 001h
  DEBUG_ONLY_THIS_PROCESS  = 002h
Код клиента может сам порождать процессы, и в большинстве случаях их отладка не входит в наши планы. Для таких случаев предусмотрен флаг DEBUG_ONLY_THIS_PROCESS (только текущий процесс), который нужно прибавить (OR) к основному DEBUG_PROCESS.

Обнаружив комбинацию этих флагов, система сразу-же "заморозит" основной поток клиента как-только он загрузится в память (Soft-Ice). С этого момента, клиент будет полностью под контролем отладчика, а его процесс будет считаться "отлаживаемым". Через системный порт-отладки клиент будет посылать отладчику сообщения, о происходящих в своей тушке событиях "Event".


Step 2. Отладчик ждёт событий клиента

Мы выяснили, что когда запускаем клиента или подключаемся к нему через DebugActiveProcess(), клиент впадает в спячку - это хорошо. Однако наш отладчик-то не спит и продолжает работать! Значит нужно заморозить и его поток, иначе он мухой отработает весь/свой код и просто закроется. Для этого, в движке-отладки имеется функция WaitForDebugEvent(), которая жонглирует объектом синхронизации "Event". Данная функция блокиpует отладчик до тех пор, пока клиент не пошлёт ему сообщение о каком-нибудь отладочном событии. Посмотpим на её прототип:
Code
1
2
3
BOOL WaitForDebugEvent(
     lpDebugEvent            // адpес стpуктуpы DEBUG_EVENT
     dwMilliseconds   );     // сколько ждать события (-1 = вечно, INFINITE).
Тут мы должны указать только время ожидания, а стpуктуpу DEBUG_EVENT заполнит клиент. Он сбрасывает в неё технические детали отладочного события:
Кликните здесь для просмотра всего текста
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct DEBUG_EVENT
  dwDebugEventCode  dd ?          ; код события
  dwProcessId       dd ?          ; Pid,
  dwThreadId        dd ?          ;   ..и Tid клиента.
  u                 DEBUGSTRUCT   ; расширенные сведения о событии.
ends
;------------------
1. DebugEventCode
     CREATE_PROCESS_DEBUG_EVENT - клиент создаёт пpоцесс.
     EXIT_PROCESS_DEBUG_EVENT   - соответственно закрывает его.
     CREATE_THEAD_DEBUG_EVENT   - аналогично для потоков..
     EXIT_THREAD_DEBUG_EVENT    - ^^^
     LOAD_DLL_DEBUG_EVENT       -   ..и внешних модулей.
     UNLOAD_DLL_DEBUG_EVENT     -       ^^^
     EXCEPTION_DEBUG_EVENT      - ВАЖНО! клиент сгенерил исключение (Step/Break/etc..)
     OUTPUT_DEBUG_STRING_EVENT  - клиент хочет послать строку (как-правило драйвер).
     RIP_EVENT                  - системная ошибка!
 
;------------------
; просмотр событий клиента в WinDbg.  
; тут я поставил бряк на ExitProcess, и сразу запустил процесс по "Go".
;------------------
0:000> .eventlog
  0168.0258:  Create process 0:168
  0168.0258:  Load module ntdll.dll at 7c900000
  0168.0258:  Load module C:\WINDOWS\system32\kernel32.dll at 7c800000
  0168.0258:  Break exception - code 80000003 (first chance)
 
  0168.0258:  Hit breakpoint 0               ;<----- сработал мой бряк
 Last event:  Exit process 0:168, code 0

Теперь у нас есть пауза, чтобы обработать событие клиента,
и отобразить необходимую информацию в форточках своего отладчика.


Step 3. Собираем информацию об отладочном событии

Как-правило событием является Step с исключением 0x80000004 (int-1), но чтобы узнать наверняка, нужно извлечь код-события из структуры DEBUG_EVENT. Если вы используете предпочитаемый мною FASM, то по-умолчанию в его штанах нет инклуд со-структурами отладки (у masm'a они зашиты в windows.inc). Поэтому я создал свой "debug.inc" для фасма, который прицепил в скрепке. Внутри него вы найдёте структуру "DEBUGSTRUCT", куда клиент сбрасывает расширенную информацию о своём событии.

Отладчик, который не отображает регистры отлаживаемой программы, никому не нужен - именно в этом его предназначение. Встроим такую возможностью, прибегнув к функции GetThreadContext(). У неё всего два параметра, один из которых отправляем, а во-второй получаем ответ. Хендл возвращает CreateProcess(), когда мы только открываем клиента, а вот контекст - требует пояснений:
Code
1
2
3
BOOL GetThreadContext(
    HANDLE    hThread,            ; хендл потока, с которого собираемся читать
    LPCONTEXT lpContext  );       ; адpес нашей стpуктуpы CONTEXT, котоpую заполнит система
Структуру "CONTEXT" (см. debug.inc) система создаёт для каждого потока, когда переключается на другой.. в целях поддержки много/задачности. Наш клиент тоже попадает под эту раздачу. Первым членом структуры представлен "ContextFlags" - он уточняет, значения каких именно регистров мы хотим получить от клиента. Обычно его устанавливают в *Full* (все регистры), хотя имеется возможность отфильтровать выхлоп - например только РОН, или только FPU. Все флаги перечислены в скрепке.


Step 4. Возврат управления клиенту

Один цикл работы отладчика заканчивается тем, что нужно пробудить спящий поток клиента, пнув его функцией ContinueDebugEvent(). Эта функция заставит клиента сделать один стэп, после которого клиент опять отправится в царство Морфея, и весь цикл-отладки вернётся на круги свои:
Code
1
2
3
4
5
6
7
8
9
10
BOOL ContinueDebugEvent (
   dwProcessId,            ;// Pid спящего клиента (лежит в структуре "DEBUG_EVENT"),
   dwThreadId,             ;//   ..соответственно его Tid.
   dwContinueStatus );     ;// указывает, как продолжить клиента.
 
;------
; Возможные значения статуса:
    DBG_CONTINUE                ; тупо продолжить;
    DBG_EXCEPTION_HANDLED       ; исключение обработано;
    DBG_EXCEPTION_NOT_HANDLED   ;  ..или не обработано.
Здесь нужно обратить внимание на одну/важную деталь..
Когда обычная программа генерит Exception, его перехватывает системный "Диспетчер исключений". У него свои планы на обработку возникшей ситуации, от которых ничего/хорошего ждать не приходится - в большинстве случаях он прибивает процесс и всё. Однако бит гуманности у диспетчера всё-же присутствует, и он сначала пробует передать бразды в юзерский SEH-обработчик, и если такового не обнаруживает, тогда пиши-пропало.

Аналогичную ситуацию мы наблюдаем и здесь..
Если в ответ на событие клиента "EXCEPTION_DEBUG_EVENT" функция ContinueDebug() возвратит клиенту "NOT_HANDLED" (исключение не обработано), то притаившийся в кустах диспетчер посчитает это как знак свыше, и сразу-же примется за обслуживание сам. Поэтому со-статусом "NOT_HANDLED" нужно быть аккуратней, ато клиент может зависнуть на одном исключении, и будет генерить его снова-и-снова. Нужно всегда проверять ExceptionCode в структуре "DEBUG_EVENT" и исходя из него возвращать клиенту тот или иной свой/статус.

Среди исключений есть "EXCEPTION_BREAKPOINT" с кодом 80000003h. Это первое, что получит отладчик после запуска клиента на исполнение. В ответ на данное событие, отладчик в первый раз обязательно должен вернуть статус "DBG_CONTINUE", иначе система откажется запускать пpоцесс клиента, обращая в прах все/наши усилия. Следуюший "EXCEPTION_BREAKPOINT" можно уже обрабатывать на своё усмотрение, возвращая HANDLED или NOT_HANDLED соответственно.


Пользовательский отладчик на практике

Сухая теория до добра не доводит, но и без неё никак. Попробуем написать демонстрационный отладчик, чтобы так-сказать пощупать это дело руками. Я не буду изворачиваться тут на изнанку, а просто открою клиента, соберу о нём информацию, и тупо сбоксю её в мессагу. Выбирать клиента буду через системное окно выбора-файла функцией GetOpenFileName(), а для сбора информации о нём воспользуюсь GetThreadContext():
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
format   PE gui
include  'win32ax.inc'
include  'equates\debug.inc'              ;<--- мой инклуд сбрасываем сюда
;-------
.data
flt     db  '*.exe',0,'*.exe',0,0         ;<--- тип файлов для "GetOpenFileName()"
capt    db  'Win32 Debug',0
mes0    db  'Отладка приложения завершена!',0
 
dbgMes  db  'EAX...:  %08X',13,10
        db  'EBX...:  %08X',13,10
        db  'CS....:  %08X',13,10
        db  'FS....:  %08X',13,10
        db  'EFLAGS:  %08X',13,10
        db  'ESP...:  %08X',13,10
        db  'DR0...:  %08X',0
 
text    db  'Состояние регистров клиента',13,10
        db  '---------------------------',13,10
buff    db  512 dup(?)
 
align   4
ofn     OPENFILENAME
sInfo   STARTUPINFO
pInfo   PROCESS_INFORMATION
dbgEv   DEBUG_EVENT
cntxt   CONTEXT
;-------
.code
start:  
; Заполняю структуру "OPENFILENAME" и открываю клиента
;-------
        mov   [ofn.lStructSize],76
        mov   [ofn.lpstrFilter],flt
        mov   [ofn.lpstrFile],buff
        mov   [ofn.nMaxFile],512
        mov   [ofn.Flags], OFN_EXPLORER
 
        invoke  GetOpenFileName, ofn
        invoke  GetStartupInfo, sInfo
 
        invoke  CreateProcess, buff,0,0,0,0, \
                               DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS, \
                               0,0, sInfo, pInfo
; Начало отладки клиента...
;-------
@debugStart:
        invoke  WaitForDebugEvent, dbgEv, -1
        cmp     [dbgEv.dwDebugEventCode], EXIT_PROCESS_DEBUG_EVENT
        jnz     @f
        invoke  MessageBox, 0,mes0,capt,0        ; если встретили событие ExitProcess
        jmp     @exit
 
; Иначе проверяем на первое исключение "EXCEPTION_BREAKPOINT"
@@:     cmp     [dbgEv.dwDebugEventCode], EXCEPTION_DEBUG_EVENT
        jnz     @f
        cmp     [dbgEv.u.Exception.pExceptionRecord.ExceptionCode], EXCEPTION_BREAKPOINT
        jnz     @f
 
; Если оно, возвращаем "DBG_CONTINUE", чтобы продолжить отладку
        invoke  ContinueDebugEvent, [dbgEv.dwProcessId], [dbgEv.dwThreadId], DBG_CONTINUE
        jmp     @debugStart
 
; Иначе - читаем контекст клиента
@@:     invoke  GetThreadContext, [pInfo.hThread],cntxt
        invoke  wsprintf, buff, dbgMes, \
                          [cntxt.regEax], \
                          [cntxt.regEbx], \
                          [cntxt.regCs], \
                          [cntxt.regFs], \
                          [cntxt.regFlag], \
                          [cntxt.regEsp], \
                          [cntxt.iDr7]
 
; Боксим регистры клиента в окно
        invoke  MessageBox,0, text,capt,0
 
; Разбудим клиента, чтобы продолжить дальше..
        invoke  ContinueDebugEvent, [dbgEv.dwProcessId], [dbgEv.dwThreadId], DBG_EXCEPTION_HANDLED
        jmp     @debugStart
 
; Клиент породил ExitProcess, закрываем его объекты Process/Thread
@exit:  invoke  CloseHandle,[pInfo.hProcess]
        invoke  ExitProcess,0                  ;<---- не забудим про себя.
.end start
Ясно, что это пародия на отладчик, но он работает по общей схеме системного движка-отладки пользовательского режима. Мы не собираемся конкурировать с гигантами типа Оли и x64Dbg, а только изучаем их принципы. Как и следовало ожидать, заправляет юзерским движком - ядерный, у которого свои функции и свои структуры.
Вложения
Тип файла: zip DEBUG.zip (1.5 Кб, 28 просмотров)
2
Эксперт Hardware
Эксперт Hardware
 Аватар для R71MT
6213 / 2447 / 403
Регистрация: 29.07.2014
Сообщений: 3,178
Записей в блоге: 4
01.06.2019, 10:38  [ТС]
Kernel Debug-API (или логово тираноидов)

Механизмы отладки радикально изменились в Win-XP. В то время, большинство функций из Ntoskrnl перекочевали в Ntdll, в результате чего пришлось переписать большую часть ядра. Связано это с тем, что начиная именно с ХР была напрочь искорена подсистема окружения OS/2, а в Win8 отказались и от POSIX. На данный момент, полным монополистом является только подсистема Win32.

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

• Первый уровень расположен в исполняющей системе Ntoskrnl. Его Native-API имеют префикс Dbgk (фреймворк, среда отладки). Данный модуль предоставляет нам внутренние функции ядра для прослушивания событий отладчика (объект Event), управления объектом отладки "DebugPort", и упаковки информации для отправки её юзермодному отладчику:

Список функций Dbgk
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
lkd> x /n nt!*dbgk*                     ;<------ ищем функции в модуле по маске..
     8065aef2  nt!DbgkClearProcessDebugObject 
     80582a91  nt!DbgkCopyProcessDebugPort 
     8057c168  nt!DbgkCreateThread 
     80557240  nt!DbgkDebugObjectType 
     8065b6eb  nt!DbgkExitProcess 
     8065b66c  nt!DbgkExitThread 
     805891c0  nt!DbgkForwardException 
     806b32b1  nt!DbgkInitialize 
     8057e9cc  nt!DbgkMapViewOfSection 
     8065a015  nt!DbgkOpenProcessDebugPort 
     8057ea37  nt!DbgkUnMapViewOfSection 
     8065ae0d  nt!DbgkpCloseObject 
     8065a8f9  nt!DbgkpConvertKernelToUserStateChange 
     8056e034  nt!DbgkpDeleteObject 
     8065a1cb  nt!DbgkpFreeDebugEvent 
     80659f6d  nt!DbgkpMarkProcessPeb 
     8055723c  nt!DbgkpMaxModuleMsgs 
     8065a81b  nt!DbgkpOpenHandles 
     8065a5ff  nt!DbgkpPostFakeModuleMessages 
     8065a7a0  nt!DbgkpPostFakeProcessCreateMessages 
     8065a3df  nt!DbgkpPostFakeThreadMessages 
     80558240  nt!DbgkpProcessDebugPortMutex 
     8065a21f  nt!DbgkpQueueMessage 
     8065b5e3  nt!DbgkpResumeProcess 
     8065b5f2  nt!DbgkpSectionToFileHandle 
     8065b4c0  nt!DbgkpSendApiMessage 
     8065b52f  nt!DbgkpSendApiMessageLpc 
     8065afd7  nt!DbgkpSetProcessDebugObject 
     8065b5bc  nt!DbgkpSuspendProcess 
     8065adb6  nt!DbgkpWakeTarget


• Второй модуль расположен в Ntdll.dll в составе API с префиксом DbgUi. Эти API оборачивают в свой кокон основные функции выше, и позволяют приложениям Win32 использовать отладочные средства ядерного уровня:

Функции DbgUi, Dbg
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
lkd> x /n ntdll!*dbgui*
     7c94fef9  ntdll!DbgUiConnectToDbg 
     7c94ffa4  ntdll!DbgUiContinue 
     7c9500cc  ntdll!DbgUiConvertStateChangeStructure 
     7c95008a  ntdll!DbgUiDebugActiveProcess 
     7c94ff4e  ntdll!DbgUiGetThreadDebugObject 
     7c950049  ntdll!DbgUiIssueRemoteBreakin 
     7c94ffeb  ntdll!DbgUiRemoteBreakin 
     7c94ff60  ntdll!DbgUiSetThreadDebugObject 
     7c94ffc9  ntdll!DbgUiStopDebugging 
     7c94ff7d  ntdll!DbgUiWaitStateChange 
 
lkd> x /n ntdll!*dbg*
     7c90120e  ntdll!DbgBreakPoint 
     7c901216  ntdll!DbgBreakPointWithStatus 
     7c92fb3f  ntdll!DbgPrint 
     7c91ead5  ntdll!DbgPrintEx 
     7c95808a  ntdll!DbgPrintReturnControlC 
     7c958179  ntdll!DbgPrompt 
     7c9581bf  ntdll!DbgQueryDebugFilterState 
     7c9581cf  ntdll!DbgSetDebugFilterState 
     7c901212  ntdll!DbgUserBreakPoint


• Третий модуль принадлежит уже библиотекам подсистемы Win32.
Это функции из Kernel32.dll, которые обслуживают юзера - они без префикса:

Список пользовательских функций
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
lkd> x /n kernel32!*debug*
     7c85aa22  kernel32!CheckRemoteDebuggerPresent 
     7c85b46d  kernel32!ContinueDebugEvent 
     7c85b02b  kernel32!DebugActiveProcess 
     7c85b4b1  kernel32!DebugActiveProcessStop 
     7c85aa76  kernel32!DebugBreak 
     7c85b07e  kernel32!DebugBreakProcess 
     7c85b0a5  kernel32!DebugSetProcessKillOnExit 
     7c8260aa  kernel32!DnsApiSetDebugGlobals 
     7c813123  kernel32!IsDebuggerPresent 
     7c8808b5  kernel32!NtSetInformationDebugObject 
     7c85ac7c  kernel32!OutputDebugStringA 
     7c86d7c4  kernel32!PatchDebug <_IMAGE_NT_HEADERS> 
     7c85b388  kernel32!WaitForDebugEvent


В этих списках, перед именем указана точка-входа в функцию. Воспользовавшись этим адресом, мы можем дизассемблировать функцию командой(u), или собрать о ней информацию командой .fnent - function environment. Обратите внимание, что все функции с префиксом "Dbgk" находятся в пространстве ядра (адрес выше 0х80000000), в то время-как остальные болтаются в пространстве юзера (нижняя половина):
Code
1
2
3
4
5
6
7
8
9
10
11
12
lkd> .fnent 7c85b388
     Debugger function entry 024959a0 for: 
          (7c85b388) kernel32!WaitForDebugEvent | (7c85b46d) kernel32!ContinueDebugEvent
     Exact matches:
          kernel32!WaitForDebugEvent
 
     OffStart:  0005b388               ;<--- смещение функции от 0х7с800000
     ProcSize:  0xE0                   ;<--- размер функции в опкодах
     Prologue:  0x9                    ;<--- размер пролога на выходе из fn
     Params:    0n02 (0x08 bytes)      ;<--- кол-во аргументов
     Locals:    0n26 (0x68 bytes)      ;<--- размер локальных данных
     Non-FPO
Тут видим редко упоминающийся в литературе термин "FPO". Дело в том, что в зависимости от вида оптимизации, в некоторых функциях в качестве указателя кадра-стека может применяться не EBP, а другой регистр. Такие процедуры поддерживают то, что называется "Данными о кадре стека с отсутствующим указателем", или Frame Pointer Omission, FPO (см.описание в хидере winnt.h). Если при отладке кода вы видите ссылки по положительным смещениям от регистра ESP знайте, что вы находитесь в функции с данными FPO.

NT поддерживает отладку со времён своего "подгузничества", а в последующих выпусках добавляются всё/больше доп.функций. В частности, начиная с XP добавлены ещё 4 полезных API-интерфейса:
  1. DebugActiveProcessStop() - остановить отладку, без остановки самого процесса.
  2. DebugSetProcessKillOnExit() - продолжить выполнение процесса даже после его отсоединения.
  3. DebugBreakProcess() - выполнять удаленный Break без необходимости вручную создавать удаленный поток.
  4. CheckRemoteDebuggerPresent() - проверить подключенный отладчик в другом процессе, без необходимости удаленного чтения PEB (аналог IsDebuggerPresent для удалённых процессов).

Система ХР содержит и несколько изменений базовой реализации. В ней отказались от устаревшего метода обмена сообщениями через вызов локальных процедур LPC, в пользу порта отладки "DebugPort". Работой LPC заправляет сервис "csrss.exe", но поскольку теперь он не имеет отношения к отладке, то появилась возможность дебажить и сам "csrss.exe" (раньше такой возможности не было). Структуры и описания ядерных функций можно найти в скрепке..
Вложения
Тип файла: pdf dbg.pdf (114.4 Кб, 63 просмотров)
2
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
01.06.2019, 10:38
Помогаю со студенческими работами здесь

Запуск 22-х ядерного процессора на одном ядре
Доброго времени суток! Не подскажите мне программу для двух ядерного проца что бы запустить его на одном ядре Заранее спасибо

Замена 2 ядерного 2020m на 4x core i7-3610qm
Здравствуйте! Хочу заменить свой intel pentium 2020m g2 сокет, чипсет hm77, ivy на i7-3610qm, тот же ivy, g2, hm77, уже 4 ядра, но...

Неполная загрузка 4-ядерного процессора, Canopus Procoder3.
Видеокодировщик Canopus Procoder3 не полностью загружает 4-ядерный процессор, а примерно на 30% суммарную мощность, в чем причина, как...

Энергия πи-мезонов (кванты ядерного поля)
Уважаемые физики, помогите пожалуйста с формулой определения энергии \pi-мезонов! Не могу оперативно отыскать на просторах сети. ...

Изучение отладчика WinDbg 86/64
Доброго дня уважаемые гуру реверсинга и программирования, ищу специалистов для изучения отладчика WinDbg, реверсинга и других аспектов...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Опции темы

Новые блоги и статьи
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает монорепозиторий в котором находятся все исходники. При создании нового решения, мы просто добавляем нужные проекты и имеем. . .
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение: В этой книге («Подход, основанный на вариантах использования») Ивар утверждает, что архитектура программного обеспечения — это структуры,. . .
Управление камерой с помощью скрипта OrbitControls.js на Three.js: Вращение, зум и панорамирование
8Observer8 05.03.2026
Содержание блога Финальная демка в браузере работает на Desktop и мобильных браузерах. Итоговый код: orbit-controls-threejs-js. zip. Сканируйте QR-код на мобильном. Вращайте камеру одним пальцем,. . .
SDL3 для Web (WebAssembly): Синхронизация спрайтов SDL3 и тел Box2D
8Observer8 04.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-sync-physics-sprites-sdl3-c. zip На первой гифке отладочные линии отключены, а на второй включены:. . .
SDL3 для Web (WebAssembly): Идентификация объектов на Box2D v3 - использование userData и событий коллизий
8Observer8 02.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-collision-events-sdl3-c. zip Сканируйте QR-код на мобильном и вы увидите, что появится джойстик для управления главным героем. . . .
Реалии
Hrethgir 01.03.2026
Нет, я не закончил до сих пор симулятор. Эта задача сложнее. Не получилось уйти в плавсостав, но оно и к лучшему, возможно. Точнее получалось - но сварщиком в палубную команду, а это значит, в моём. . .
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru