Вызов системных функций через прерывание 2E
В одной из статей, опубликованной на этом сайте, рассматривался способ нахождения номеров функций в SDT (см. Номера системных функций в SDT Windows). Например, при вызове NtCreateFile из ntdll.dll в Windows 2000 будет выполняться следующий код:
public ZwCreateFile ZwCreateFile proc near mov eax, 20h ; номер в SDT lea edx, [esp+dword ptr 4] ; адрес первого аргумента int 2Eh ; шлюз retn 2Ch ZwCreateFile endp
Перед вызовом ZwCreateFile все аргументы были переданы в стек командой push, а затем следовал call, который привел к появлению в стеке еще одной переменной - адреса возврата. Именно поэтому в качестве адреса первого аргумента в edx записывается [esp+dword ptr 4], а не просто [esp].
Итак, чтобы вызвать функцию напрямую без использования ntdll.dll следует выполнить следующую последовательность действий:
- Передать все аргументы функции в стек
- Записать в eax номер функции в SDT
- Записать в edx адрес первого аргумента
- Вызывать прерывание 2Eh
- Т.к. все Native API функции используют соглашение __stdcall, то необходимо очистить стек от переданных аргументов перед вызывом прерывания
Для примера вызовем ZwOpenKey, имеющей следующий прототип:
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenKey(
OUT PHANDLE KeyHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
Открытый ключ необходимо закрыть используя ZwClose следующего прототипа:
NTSYSAPI
NTSTATUS
NTAPI
ZwClose(
IN HANDLE Handle
);
Рассмотрите код функции, которая открывает ключ HKEY_LOCAL_MACHINE\Software и закрывает его. Заметьте, что имя ключа, которое необходимо передать функции записывается в пространстве имен ядра, т.е. "\Registry\MACHINE\Software":
VOID
SdtSimpleTestNtOpenKeyNtCloseKeyWindowsXP(
)
{
UNICODE_STRING KeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
ACCESS_MASK DesiredAccess = GENERIC_READ;
HANDLE KeyHandle;
//
// Будем открывать ключ HKEY_LOCAL_MACHINE\SOFTWARE.
// RtlInitUnicodeString - это макрос препроцессора
//
RtlInitUnicodeString( &KeyName, L"\\REGISTRY\\MACHINE\\SOFTWARE" );
//
// Заполним OBJECT_ATTRIBUTES.
// InitializeObjectAttributes - это макрос препроцессора
//
InitializeObjectAttributes( &ObjectAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
//
// Прототип вызываемой функции такой:
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// ZwOpenKey(
// OUT PHANDLE KeyHandle,
// IN ACCESS_MASK DesiredAccess,
// IN POBJECT_ATTRIBUTES ObjectAttributes
// );
__asm{
lea eax, ObjectAttributes ; Адрес ObjectAttributes
push eax ; Передача в стек
mov eax, DesiredAccess ; Доступ (втрой аргумент)
push eax ; Передача в стек
lea eax, KeyHandle ; Адрес KeyHandle (в функцию уже пришёл адресом)
push eax ; Передача в стек
; Далее код взят из ntdll.dll Windows 2000
mov eax, 119d ; Номер функции в SDT (должен лежать в eax) (в XP это 119)
lea edx, [esp] ; В Windows 2000 здесь стояло [esp + DWORD PTR 4]
; Это сохранение адреса первого аргумента (должен лежать в edx)
int 2Eh ; Вызов прерывание - обрабатывать его будет ядро.
; Это шлюз к ядру
mov Status, eax ; По завершению обработки в eax лежит возвращенное
; функцией значение (NTSTATUS)
; Далее, поскольку функция __stdcall (сама не чистит стек после выполнения), следует убрать
; из стека "запушенные" туда данные. Запушивали 3 аргумента => pop сделать 3 раза
pop eax
pop eax
pop eax
; Освободить стек можно по-другому, например add esp, 12d
}
//
// Теперь в Status лежит возвращенное функцией значение
//
if ( Status != 0 )
return;
//
// Теперь следует закрыть описатель. Вызовем ZwClose. Прототип у нее такой:
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// ZwClose(
// IN HANDLE Handle
// );
__asm{
mov eax, KeyHandle ; Значение параметра
push eax ; Передача его в стек
mov eax, 25d ; Далее в eax записывается номер функции в SDT
lea edx, [esp] ; В edx - адрес аргумента
int 2Eh ; Вызов шлюза
mov Status, eax ; Сохранение возвращенного результата
pop eax ; Освобождение стека
}
}
Эта функция успешно открывает и закрывает ключ реестра при выполнении в Windows XP не вызывая ни одной API функции и не используя ни одной библиотеки. Почему только в Windows XP? Потому что перед вызовом прерывания int 2Eh в eax записывался номер функции SDT именно для этой ОС. Для запуска функции на другой ОС, следует изменить номера (для этого используйте таблицу, полученную программой sdt.exe, таблица также опубликована в одной из статей).
Существенной проблемой является определение версии Windows без использования API. Но если посмотреть внимательно на таблицу номеров SDT, то можно заметить, что от версии к версии ОС содержит всё больше функций. Например, функция с номером 390 есть только в Windows Vista и ее нет в предыдущих версиях, 295 - в Windows 2003, 282 - в Windows XP, 247 - в Windows 2000. Очевидно, что следующая версия Windows будет иметь ещё больше функций и метод будет работать и дальше.
Теперь необходимо определить наличие функции с определенным номером в системе. Если функция отсутствует, то после вызова int 2Eh в eax будет значение 0xc000001c (STATUS_INVALID_SYSTEM_SERVICE). Но если функция присутствует, то она будет выполняться и может сгенерировать исключение, которое сложно обработать без использования API. Чтобы функция не выполнялась, запишем в edx в качестве адреса первого параметра функции NULL. При этом после выполнения прерывания int 2Eh исключения не возникнет, но eax будет содержать 0xC0000005 (STATUS_ACCESS_VIOLATION), а указатель команды (регистр EIP) изменится не на 4, а на 8 (небольшая особенность при возникновении исключительной ситуации).
Проверить наличие функции с указанным номером в системе можно с помощью следующей функции:
BOOL
SdtTestServicePresent(
IN ULONG Number
)
{
NTSTATUS Status = 0;
__asm{
mov eax, Number ; Далее в eax записывается номер функции в SDT
mov edx, 0
int 2Eh ; Вызов шлюза
mov Status, eax ; Сохранение возвращенного результата. При AV
; EIP изменяется на 8 а не 4 байта, поэтому эта
; команда выполнена не будет
}
// STATUS_INVALID_SYSTEM_SERVICE = 0xc000001c
if ( Status == 0xc000001c )
return FALSE;
return TRUE;
}
Тогда, определить наличие ОС можно с помощью такой функции:
SDT_SYSTEM
SdtTestGetOsVersion()
{
// Последние номера в SDT:
// 2000: 247
// XP: 282
// 2003: 295
// Vista: 390
if ( SdtTestServicePresent( 390 ) )
return SystemWindowsVista;
if ( SdtTestServicePresent( 295 ) )
return SystemWindows2003;
if ( SdtTestServicePresent( 282 ) )
return SystemWindowsXP;
if ( SdtTestServicePresent( 247 ) )
return SystemWindows2000;
return SystemWindowsUnknown;
}
Далее эту технику можно применять при разработке ПО и доставить огромное наслаждение хакерам, отлаживающим вашу программу :). Чтобы было удобнее вызывать функции из ntdll.dll, объявим следующие типы данных, функции, макросы:
typedef struct
{
ULONG Windows2000;
ULONG WindowsXP;
ULONG Windows2003;
ULONG WindowsVista;
ULONG Reserved;
} SDT_NUMBER;
ULONG
SdtNumber(
SDT_SYSTEM System,
SDT_NUMBER *Numbers
)
{
return ((ULONG*) Numbers)[System];
}
#define PROLOG_CODE( Win2000, WinXP, Win2003, WinVista ) \
SDT_NUMBER Numbers = { Win2000, WinXP, Win2003, WinVista, -1 }; \
ULONG Number = SdtNumber( System, &Numbers );
#define EPILOG_CODE( PopBytes ) \
__asm mov eax, Number \
__asm lea edx, [esp] \
__asm int 2Eh \
__asm mov Status, eax \
__asm add esp, PopBytes
#define PUSH_POINTER( Pointer ) \
__asm lea eax, Pointer \
__asm push eax
#define PUSH_VALUE( Value ) \
__asm mov eax, Value \
__asm push eax
Вызовы функций можно запрограммировать используя макросы следующим образом:
#define ZwOpenKeyCall( System, Status, KeyHandle, DesiredAccess, ObjectAttibutes ) { \
PROLOG_CODE( 103, 119, 125, 189 ) \
PUSH_POINTER(ObjectAttributes) \
PUSH_VALUE( DesiredAccess ) \
PUSH_POINTER( KeyHandle ) \
EPILOG_CODE( 12 ) }
#define ZwCloseHandleCall( System, Status, Handle ) { \
PROLOG_CODE( 24,25,27,48 ) \
PUSH_VALUE( Handle ) \
EPILOG_CODE( 4 ) }
/*NTSYSAPI
NTSTATUS
NTAPI
ZwEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);*/
#define ZwEnumerateKeyCall( System, Status, \
KeyHandle, Index, KeyInformationClass, KeyInformation, \
Length, ResultLength ) { \
PROLOG_CODE( 60,71,75,133) \
PUSH_POINTER( ResultLength ) \
PUSH_VALUE( Length ) \
PUSH_POINTER( KeyInformation ) \
PUSH_VALUE( KeyInformationClass ) \
PUSH_VALUE( Index ) \
PUSH_VALUE( KeyHandle ) \
EPILOG_CODE( 24 ) }
Впринципе, макросов должно хватить для объявления любой Native API функции таким вот образом (готовым к вызову в коде других функций).
Продемонстрируем пример использования функций. Например, перечислим подключи в ключе реестра HKEY_LOCAL_MACHINE\Software:
VOID
SdtTestEnumerateSoftwareSubkeys(
)
{
SDT_SYSTEM System = SdtTestGetOsVersion();
NTSTATUS Status;
HANDLE hKey;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName;
LPWSTR szKey = L"\\REGISTRY\\MACHINE\\SOFTWARE";
RtlInitUnicodeString( &KeyName, szKey );
InitializeObjectAttributes( &ObjectAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
_tprintf( _T("Key: %S\n"), szKey );
ZwOpenKeyCall( System, Status, hKey, GENERIC_READ, ObjectAttributes );
if ( Status == 0 )
{
UCHAR Buffer[1024];
PKEY_BASIC_INFORMATION pKeyInfo = (PKEY_BASIC_INFORMATION) Buffer;
ULONG i = 0;
while ( Status == 0 )
{
ULONG uSize = sizeof( Buffer );
ULONG uRetSize = 0;
ZeroMemory( Buffer, sizeof(Buffer) );
ZwEnumerateKeyCall( System, Status, hKey, i, KeyBasicInformation, Buffer, uSize, uRetSize );
if ( Status == 0 )
{
_tprintf( _T("Subkey[%i]: %S\n"), i, pKeyInfo->Name );
}
i++;
}
ZwCloseHandleCall( System, Status, hKey );
}
else
{
_tprintf( _T("-Can't open key. Status = 0x%.8X\n"), Status );
}
}
Вообще говоря, функциональности Native API функций должно хватить для всего, что связано с системой, т.к. все документированные API используют Native API.
При использовании этой техники при разработке ПО незначительно снижается скорость разработки, но плюсы техники очевидны: трудность отладки бинарного кода хакером, обход перехвата функций в Usermode.
Теоретически, используя такую технику, можно создавать программы, не содержащие импортов вообще, не вызывающие GetProcAddress для получения адресов функций. Такие программы скрыты для перехвата и их практически невозможно отладить.
Исходники, код, приведенный в этой статье: sdttest.c
Автор: Fur
Дата: 07 августа 2007
Избранное
Остальное
По вопросам сотрудничества и другим вопросам по работе сайта пишите на cleogroup[собака]yandex.ru