Уроки Внутренние компоненты инструкции Sysenter


offsec

Уважаемый пользователь
Форумчанин
Регистрация
12.11.2023
Сообщения
19
Репутация
17
Это, одна из ссылок из моего перевода "Фантастические руткиты". Рекомендую прочитать, все равно интересно ведь)

Введение

В предыдущей статье мы увидели, что независимо от того, используем ли мы прерывание int 0x2e или инструкцию sysenter, в ядре используется один и тот же метод. Мы также определили, что в обоих случаях вызывается KiSystemService. В этой статье мы рассмотрим, как это происходит на самом деле.

При использовании прерывания 0x2e или инструкции sysenter номер системного сервиса используется для определения того, какой системный вызов будет вызван. Мы уже видели, что номер системной службы передается в регистр EAX, который на машинах IA-32 является 32-разрядным регистром. Однако не сразу понятно, как это значение используется в дальнейшем.


Первое, что приходит в голову, это то, что это просто индекс в некоторой таблице, которая содержит указатели на системные процедуры, которые будут вызываться. Это довольно близко к тому, как значение используется на самом деле, но следует отметить, что 32 бита не используются в качестве индекса, поскольку в этом случае нам пришлось бы иметь таблицу указателей размером 4 ГБ или многоуровневую таблицу, что непрактично и не нужно.


Сервисный номер системы состоит из следующих частей:

  • bits 0-11: номер системной службы, которую необходимо вызвать
  • bits 12-13: используемая таблица дескрипторов сервисов
  • bits 14-31: не используется
Видно, что только младшие 12 бит используются в качестве индекса в таблице, размер которой составляет 4096 байт. Но есть еще 2 бита (с 12-13), которые используются для выбора соответствующей таблицы дескрипторов сервисов, т.е. мы можем иметь не более 4 таблиц дескрипторов сервисов.


В Windows таблица SSDT (System Service Dispatch Table) представляет собой таблицу, указывающую на функции ядра, которые обрабатываются в ntoskrnl.exe. ntoskrnl.exe отвечает за различные задачи, такие как аппаратная виртуализация, управление процессами и памятью, управление кэшем, планирование процессов и т.д. [5] В системах Windows используются только две таблицы, которые называются KeServiceDescriptorTable и KeServiceDescriptorTableShadow.

На рисунке ниже мы видим адрес и первый элемент обеих таблиц в памяти.

050913_1933_thesysenter1.png


Обе эти таблицы содержат структуры SST (System Service Tables), которые состоят из следующих элементов (обобщенно из [4]):
  • ServiceTable: указатель на массив адресов SSDT, указывающих на функции ядра
  • CounterTable: не используется
  • ServiceLimit: количество элементов в массиве SSDT
  • ArgumentTable: указатель на массив аргументов SSPT (System Service Parameter Table)
На рисунке ниже показаны структуры SST в обеих таблицах:

050913_1933_thesysenter2.png


На рисунке выше мы видим первый SST таблицы KeServiceDescriptorTable, длина которого составляет 16 байт. Это SST, указывающий на SSDT, содержащий основные функции Windows. Значения SST следующие:

  • ServiceTable: 80501b8c (указатель на KiServiceTable)
  • CounterTable: не используется
  • ServiceLimit: 0x11c (hex) = 286 (dec)
  • ArgumentTable: 80502000 (указатель на KiArgumentTable)
KeServiceDescriptorTableShadow содержит два SST, которые занимают первые 32 байта. Видно, что первый SST совпадает с тем, который присутствует в таблице KeServiceDescriptorTable, а второй используется для указания на функции в драйвере ядра win32k.sys, который отвечает за графический интерфейс Windows.

Представим поля данной SST:
  • ServiceTable: bf99e900 (указатель на W32pServiceTable)
  • CounterTable: не используется
  • ServiceLimit: 0x29b (hex) = 667 (dec)
  • ArgumentTable: bf99f610 (указатель на W32pArgumentTable)
Подведем итог тому, что мы только что сделали. К таблице KeServiceDescriptorTable обращаются, если 12-13 биты системного сервисного номера имеют значение 0x00, а к KeServiceDescriptorTableShadow обращаются, если 12-13 биты имеют значение 0x01. Два других значения 0x10 и 0x11 в настоящее время не используются. Это означает, что значение в регистре EAX, являющееся системным сервисным номером, может содержать следующие значения (представляя 16-разрядные значения):
  • 0000xxxx xxxxxxxx: используемая KeServiceDescriptorTable, где х может быть 0 или 1, из чего следует, что первая таблица используется, если номера системных сервисов находятся в диапазоне 0x0 - 0xFF.
  • 0001yyyy yyyyyyyy: используемая KeServiceDescriptorTableShadow, где y может быть 0 или 1, из чего следует, что вторая таблица используется, если системные сервисные номера находятся в диапазоне 0x1000 - 0x1FF.
Это означает, что системные служебные номера в регистре EAX могут находиться только в диапазоне 0x0000 - 0x1FFFF, а все остальные значения недопустимы.

Для дампа функций ядра Windows из таблицы KiServiceTable можно воспользоваться командой Windbg "dps KiServiceTable" следующим образом (обратите внимание, что представлена только первая часть функций):

050913_1933_thesysenter3.png


Сделаем также дамп первой части функций, содержащихся в таблице W32pServiceTable. Это видно ниже, где для отображения функций мы использовали команду "dps W32pServiceTable":

050913_1933_thesysenter4.png


Заметили ли вы, что таблица KiServiceTable содержит основные функции Windows, а таблица W32pServiceTable - графические функции, о которых мы уже говорили? Приведенные выше результаты подтверждают это.

Представление примера

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

C++:
#include "stdafx.h"

#include <stdio.h>

#include <windows.h>

#include <Winternl.h>
int _tmain(int argc, _TCHAR* argv[]) {
__asm { int 3 }

typedef long NTSTATUS;

#define STATUS_SUCCESS ((NTSTATUS)0L)

HANDLE hProcess = GetCurrentProcess();
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
BOOLEAN DebuggerEnabled;

BOOLEAN DebuggerNotPresent;

} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION;
enum SYSTEM_INFORMATION_CLASS { SystemKernelDebuggerInformation = 35 };
typedef NTSTATUS (__stdcall *ZW_QUERY_SYSTEM_INFORMATION)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);

ZW_QUERY_SYSTEM_INFORMATION ZwQuerySystemInformation;

SYSTEM_KERNEL_DEBUGGER_INFORMATION Info;
/* load the ntdll.dll */
HMODULE hModule = LoadLibrary(_T("ntdll.dll"));

ZwQuerySystemInformation = (ZW_QUERY_SYSTEM_INFORMATION)GetProcAddress(hModule, "ZwQuerySystemInformation");

if(ZwQuerySystemInformation == NULL) {

printf("Error: could not find the function ZwQuerySystemInformation in library ntdll.dll.");

exit(-1);

}

printf("ZwQuerySystemInformation is located at 0x%08x in ntdll.dll.n", (unsigned int)ZwQuerySystemInformation);
if (STATUS_SUCCESS == ZwQuerySystemInformation(SystemKernelDebuggerInformation, &amp;Info, sizeof(Info), NULL)) {
if (Info.DebuggerEnabled &amp;&amp; !Info.DebuggerNotPresent) {

printf("System debugger is present.");

}

else {

printf("System debugger is not present.");

}

}
/* wait */
getchar();

return 0;
}

Запустив программу под отладчиком ядра Windbg, мы получим следующий вывод программы:

050913_1933_thesysenter5.png


Видно, что программа правильно определила наличие системного отладчика. На рисунке ниже представлена дизассемблированная инструкция функции ZwQuerySystemInformation:

050913_1933_thesysenter6.png


Видно, что реализация функции ZwQuerySystemInformation в библиотеке ntdll.dll - это всего лишь процедура, вызывающая ядро и не предоставляющая собственно сервис. В приведенном выше коде мы записываем в регистр EAX шестнадцатеричное число 0xAD - номер системного сервиса, о котором мы говорили в статье. Поскольку число 0xAD находится в диапазоне 0x000-0xFF, мы фактически используем таблицу KeServiceDescriptorTable для получения всей информации, необходимой нам для вызова функции ядра.

Сначала рассмотрим адрес, загружаемый в регистр EDX:

050913_1933_thesysenter7.png


В приведенном выше коде мы читаем адрес 0x7c90e510 в памяти регистра EDX и вызываем его. Адрес 0x7c90e510 является адресом функции KiFastSystemCall, что видно из приведенного ниже вывода:

050913_1933_thesysenter8.png


KiFastSystemCall выполняет инструкцию sysenter, которая должна вызвать соответствующую системную функцию в ядре. Мы уже знаем, что при выполнении инструкции sysenter вызывается функция KiFastCallEntry, поэтому нам необходимо установить точку останова на этой функции следующим образом:

050913_1933_thesysenter9.png


После этого мы можем запустить программу командой g, и функция будет выглядеть так, как показано ниже:

050913_1933_thesysenter10.png


Давайте дизассемблируем всю функцию KiFastCallEntry, чтобы понять, что она делает. Дизассемблированные инструкции приведены ниже:

Код:
kd> u KiFastCallEntry l100

nt!KiFastCallEntry:

8053d600 b923000000 mov ecx,23h

8053d605 6a30 push 30h

8053d607 0fa1 pop fs

8053d609 8ed9 mov ds,cx

8053d60b 8ec1 mov es,cx

8053d60d 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h]

8053d613 8b6104 mov esp,dword ptr [ecx+4]

8053d616 6a23 push 23h

8053d618 52 push edx

8053d619 9c pushfd

8053d61a 6a02 push 2

8053d61c 83c208 add edx,8

8053d61f 9d popfd

8053d620 804c240102 or byte ptr [esp+1],2

8053d625 6a1b push 1Bh

8053d627 ff350403dfff push dword ptr ds:[0FFDF0304h]

8053d62d 6a00 push 0

8053d62f 55 push ebp

8053d630 53 push ebx

8053d631 56 push esi

8053d632 57 push edi

8053d633 8b1d1cf0dfff mov ebx,dword ptr ds:[0FFDFF01Ch]

8053d639 6a3b push 3Bh

8053d63b 8bb324010000 mov esi,dword ptr [ebx+124h]

8053d641 ff33 push dword ptr [ebx]

8053d643 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh

8053d649 8b6e18 mov ebp,dword ptr [esi+18h]

8053d64c 6a01 push 1

8053d64e 83ec48 sub esp,48h

8053d651 81ed9c020000 sub ebp,29Ch

8053d657 c6864001000001 mov byte ptr [esi+140h],1

8053d65e 3bec cmp ebp,esp

8053d660 759a jne nt!KiFastCallEntry2+0x47 (8053d5fc)

8053d662 83652c00 and dword ptr [ebp+2Ch],0

8053d666 462cff test byte ptr [esi+2Ch],0FFh

8053d66a 89ae34010000 mov dword ptr [esi+134h],ebp

8053d670 0f854afeffff jne nt!Dr_FastCallDrSave (8053d4c0)

8053d676 8b5d60 mov ebx,dword ptr [ebp+60h]

8053d679 8b7d68 mov edi,dword ptr [ebp+68h]

8053d67c 89550c mov dword ptr [ebp+0Ch],edx

8053d67f c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h

8053d686 895d00 mov dword ptr [ebp],ebx

8053d689 897d04 mov dword ptr [ebp+4],edi

8053d68c fb sti

8053d68d 8bf8 mov edi,eax

8053d68f c1ef08 shr edi,8

8053d692 83e730 and edi,30h

8053d695 8bcf mov ecx,edi

8053d697 03bee0000000 add edi,dword ptr [esi+0E0h]

8053d69d 8bd8 mov ebx,eax

8053d69f 25ff0f0000 and eax,0FFFh

8053d6a4 3b4708 cmp eax,dword ptr [edi+8]

8053d6a7 0f8345fdffff jae nt!KiBBTUnexpectedRange (8053d3f2)

8053d6ad 83f910 cmp ecx,10h

8053d6b0 751a jne nt!KiFastCallEntry+0xcc (8053d6cc)

8053d6b2 8b0d18f0dfff mov ecx,dword ptr ds:[0FFDFF018h]

8053d6b8 33db xor ebx,ebx

8053d6ba 0b99700f0000 or ebx,dword ptr [ecx+0F70h]

8053d6c0 740a je nt!KiFastCallEntry+0xcc (8053d6cc)

8053d6c2 52 push edx

8053d6c3 50 push eax

8053d6c4 ff15e4305580 call dword ptr [nt!KeGdiFlushUserBatch (805530e4)]

8053d6ca 58 pop eax

8053d6cb 5a pop edx

8053d6cc ff0538f6dfff inc dword ptr ds:[0FFDFF638h]

8053d6d2 8bf2 mov esi,edx

8053d6d4 8b5f0c mov ebx,dword ptr [edi+0Ch]

8053d6d7 33c9 xor ecx,ecx

8053d6d9 8a0c18 mov cl,byte ptr [eax+ebx]

8053d6dc 8b3f mov edi,dword ptr [edi]

8053d6de 8b1c87 mov ebx,dword ptr [edi+eax*4]

8053d6e1 2be1 sub esp,ecx

8053d6e3 c1e902 shr ecx,2

8053d6e6 8bfc mov edi,esp

8053d6e8 3b35d48a5580 cmp esi,dword ptr [nt!MmUserProbeAddress (80558ad4)]

8053d6ee 0f83a8010000 jae nt!KiSystemCallExit2+0x9f (8053d89c)

8053d6f4 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]

8053d6f6 ffd3 call ebx

8053d6f8 8be5 mov esp,ebp

8053d6fa 8b0d24f1dfff mov ecx,dword ptr ds:[0FFDFF124h]

8053d700 8b553c mov edx,dword ptr [ebp+3Ch]

8053d703 899134010000 mov dword ptr [ecx+134h],edx

Рассмотрим первую инструкцию в функции KiFastCallEntry, в которой используется значение регистра EAX, то есть для выполнения какого-либо действия используется системный служебный номер. Эта инструкция показана ниже:

Код:
8053d68d 8bf8 mov edi,eax

8053d68f c1ef08 shr edi,8

8053d692 83e730 and edi,30h

8053d695 8bcf mov ecx,edi

Поначалу эти инструкции могут показаться странными, но вскоре они начинают обретать смысл. Все операции можно увидеть на рисунке ниже. Начнем с того, что весь служебный номер системы хранится в регистре EAX, а затем переносится в регистр EDI. Это видно на первой части рисунка, где младшие 12 бит - это собственно системный сервисный номер, а средние два бита определяют используемую таблицу SSDT, в то время как старшие 18 бит не используются.

050913_1933_thesysenter11.png


Инструкция shr сдвигает все биты регистра EDI вправо на 8. Это видно на средней части рисунка, где младшие 4 бита используются для представления теперь уже поврежденного служебного номера системы, а средние два бита по-прежнему являются номером SSDT. Верхние 26 бит не используются. Инструкция next and обнуляет младшие четыре бита, оставляя неизменными только два средних бита. В конце приведенного выше кода номер SSDT сохраняется в 4-5-м битах регистра ECX.

Поскольку в регистр EAX мы передаем номер системного сервиса 0xAD, то действительно следует установить условную точку останова в WinDbg, так как в противном случае мы не сможем управлять выполнением так, как нам хочется. Это связано с тем, что функция KiFastCallEntry вызывается самим ядром так много раз, что нет смысла вручную проверять, содержит ли регистр EAX нужный номер системной службы. Нужно установить условную точку останова по адресу 0x8053d68d и проверить, равно ли значение в регистре EAX значению 0xAD (номер нашего системного сервиса).

Мы можем установить условную точку останова следующим образом:

Код:
kd> bp 8053d68d "j @eax = 0x000000ad '';'gc'"

kd> bl

0 e 8053d68d 0001 (0001) nt!KiFastCallEntry+0x8d "j @eax = 0x000000ad '';'gc'"

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

Рассмотрим пример в точке, где он попадает в точку останова, то есть значение в регистре EAX должно быть установлено в 0xAD:

Код:
kd> g

nt!KiFastCallEntry+0x8d:

8053d6dd 8bf8 mov edi,eax
kd> r eax, ecx, edi
eax=000000ad ecx=80042000 edi=7c90e514

kd> p

nt!KiFastCallEntry+0x8f:

8053d6df c1ef08 shr edi,8
kd> r eax, ecx, edi
eax=000000ad ecx=80042000 edi=000000ad

kd> p

nt!KiFastCallEntry+0x92:

8053d6e2 83e730 and edi,30h
kd> r eax, ecx, edi
eax=000000ad ecx=80042000 edi=00000000

kd> p

nt!KiFastCallEntry+0x95:

8053d6e5 8bcf mov ecx,edi

Первое число в регистре EAX - 0xad (10101101), которое мы сдвигаем вправо на 8 бит. Таким образом, получается число 0x0, которое затем AND-ed с числом 0x30. В результате преобразований получается число 0x000000.

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

Заключение

В этой статье мы рассмотрели внутреннее устройство того, что происходит при вызове инструкции sysenter. Мы могли бы углубиться в эту тему, но я не хотел делать статью слишком длинной. Самое главное, что следует запомнить, - это то, как вычисляется номер системного сервиса и как этот сервис вызывается.

Ссылки:

[1] Переход в пространство: .

[2] Ручная проверка таблицы диспетчеризации служб (SSDT) для обнаружения хуков: .

[3] Охота на руткиты с помощью Windbg: www.reconstructer.org/…/Hunting%20rootkits%20with%20Windbg.pdf.

[4] Анализ руткита BlackEnergy версии 2 – SecuraBit: www.securabit.com/wp…/Rootkit-Analysis-Hiding-SSDT-Hooks1.pdf.

[5] ntoskrnl.exe: .

[6] SYSENTER: .

[7] Справочник по набору инструкций x86: .

Статья про руткиты(Рекомендую прочитать): Фантастические руткиты: И где их найти

Оригинал: https://resources.infosecinstitute[.]com/topics/reverse-engineering/the-sysenter-instruction-internals/
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 085
Репутация
8 208
Полезная кстати тема, для тех-кто хочет сделать антихуки или обход некоторых проактивных защит.)

Вот например чтобы такие штуки делать, важно знать как получать номер системного вызова:Уроки - Разработка вирусов-32.Открываем врата ада

Собственно на этом всё и строится.)

Единственное некоторые системы защиты могут определять наличие инструкций syscall или sysenter в коде и делать на это детекты.
Можно это обойти делая не прямые вызовы, а переходы на код системной библиотеки, где эти системные вызовы легально выполняются.

Подробнее можно почитать в теме выше, если интересно.)
 
Верх Низ