Главная таблица, используемая по умолчанию, KeServiceDescriptorTable, определяет основные системные службы исполняющей системы, реализованные в файле Ntosrknl.exe. Другая таблица, KeServiceDescriptorTableShadow, включает Windows-службы USER и GDI, реализованные в той части подсистемы Windows, которая работает в режиме ядра, Win32k.sys.
На 32-разрядной версии Windows и на версии IA64, где поток Windows сразу же вызывает Windows-службу USER или службу GDI, адрес таблицы системных служб потока изменяется, чтобы указать на таблицу, которая включает Windows-службы USER и GDI.
Функция KeAddSystemServiceTable позволяет Win32k.sys добавлять таблицу системных служб.
Инструкции диспетчера системных служб для служб исполняющей системы Windows находятся в системной библиотеке Ntdll.dll. Для реализации своих документированных функций DLL-библиотеки подсистем вызывают функции в Ntdll.
Исключение составляют функции Windows USER и GDI, для которых инструкции диспетчера системных служб реализованы в User32.dll и Gdi32.dll — Ntdll.dll в данном случае не привлекается. Эти два обстоятельства показаны на рисунке.
Как показано на рисунке, Windows-функция WriteFile в Kernel32.dll импортирует и вызывает функцию WriteFileв API-MS-Win-Core-File-L1-1-0.dll, в одной из DLL-библиотек перенаправления MinWin, которая, в свою очередь, вызывает функцию WriteFile в KernelBase.dll, где находится ее фактическая реализация.
После проверки некоторых параметров, характерных для подсистемы, она затем вызывает функцию NtWriteFile в Ntdll.dll, которая, в свою очередь, выполняет соответствующую инструкцию, чтобы вызвать системное прерывание системной службы, передавая номер системной службы, представляющей NtWriteFile.
Затем диспетчер системных служб (функция KiSystemService в Ntoskrnl.exe) вызывает настоящую функцию NtWriteFile для обработки запроса ввода-вывода. Для функций Windows USER и GDI диспетчер системных служб вызывает функции в загружаемой части подсистемы Windows режима ядра, Win32k.sys.
Содержание:
Отображение номеров системных служб на функции и аргументы.
Такой же поиск можно продублировать с помощью ядра, работая с идентификатором системного вызова для определения, какая именно функция отвечает за его обработку и сколько аргументов она принимает
- Обе таблицы, KeServiceDescriptorTableи KeServiceDescriptorTableShadow, направляют на один и тот же массив указателей (или смещений на 64-разрядной системе) для системных вызовов ядра, который называется KiServiceTable, и на один и тот же массив байтов стека, который называется KiArgumentTable. На 32-разрядной системе для получения дампа данных наряду с символьной информацией можно воспользоваться командой отладчика ядра dds.
Отладчик пытается сопоставить каждый указатель с символом. Частичный вывод имеет следующий вид:
lkd> dds KiServiceTable
820807d0 821be2e5 nt!NtAcceptConnectPort
820807d4 820659a6 nt!NtAccessCheck
820807d8 8224a953 nt!NtAccessCheckAndAuditAlarm
820807dc 820659dd nt!NtAccessCheckByType
820807e0 8224a992 nt!NtAccessCheckByTypeAndAuditAlarm
820807e4 82065a18 nt!NtAccessCheckByTypeResultList
820807e8 8224a9db nt!NtAccessCheckByTypeResultListAndAuditAlarm
820807ec 8224aa24 nt!NtAccessCheckByTypeResultListAndAuditAlarmByHandle
820807f0 822892af nt!NtAddAtom
- Как уже ранее говорилось, 64-разрядная Windows создает таблицу системных вызовов по-другому и использует относительные указатели (или смещения) к системным вызовам, а не абсолютные адреса, используемые в 32-разрядной Windows. Базой указателя служит сама таблица KiServiceTable, поэтому выводить дамп данных в его необработанном формате придется с помощью команды dq. Пример вывода из 64-разрядной системы имеет следующий вид:
lkd> dq nt!KiServiceTable
fffff800'01a73b00 02f6f000'04106900 031a0105'fff72d00
- Вместо вывода дампа всей таблицы вы также можете найти конкретный номер. На 32-разрядной Windows, благодаря тому, что номер каждого системного вызова является индексом в таблице, и тому, что каждый элемент занимает 4 байта, можно воспользоваться следующим вычислением: Обработчик = KiServiceTable + Номер * 4. Давайте возьмем номер 0x102, полученный во время описания кода функции-заглушки
NtReadFile в Ntdll.dll.
lkd> ln poi(KiServiceTable + 102 * 4)
(82193023) nt!NtReadFile
На 64-разрядной Windows каждое смещение может быть сопоставлено с каждой функцией с помощью команды ln, путем смещения вправо на 4 разряда (используемых согласно ранее данному описанию) и добавления оставшегося значения к базе самой таблицы KiServiceTable, как показано в следующем примере:
lkd> ln @@c++(((int*)@@(nt!KiServiceTable))[3] >> 4) + nt!KiServiceTable
(fffff800'01d9cb10) nt!NtReadFile | (fffff800'01d9d24c) nt!NtOpenFile
Exact matches:
nt!NtReadFile = <no type information>
- Поскольку драйверы, включая руткиты режима ядра, способны на 32-разрядных версиях Windows делать вставки в эту таблицу, что операционной системой не поддерживается, команду dds можно использовать для получения дампа всей таблицы и для поиска любых значений за пределами диапазона допустимых адресов ядра (dds также даст это понять, не давая возможности просматривать символ для функции). На 64-разрядной Windows таблицы системных служб отслеживаются системой защиты от правки ядра — Kernel Patch Protection, которая при обнаружении изменений вводит систему в аварийное состояние.
Просмотр проявления активности системной службы.
Проявление активности системной службы можно отследить, наблюдая за счетчиком производительности, показывающим количество системных вызовов в секунду — "Системных вызовов/с" (System Calls/Sec) в объекте "Система" (System).
Запустите "Системный монитор" (Performance Monitor), щелкните на пункте "Системный монитор" (Performance Monitor) в разделе "Средства наблюдения" (Monitoring Tools) и щелкните на кнопке "Добавить" (Add), чтобы добавить счетчик к графику. Выберите объект "Система" (System), выберите счетчик "Системных вызовов/с" (System Calls/Sec), а затем щелкните на кнопке "Добавить" (Add) для добавления счетчика к графику.