Вызов системных функций через прерывание 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