hex.pp.ua

Вызов системных функций в Windows

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

  1. Передать все аргументы функции в стек
  2. Записать в eax номер функции в SDT
  3. Записать в edx адрес первого аргумента
  4. Вызывать прерывание 2Eh
  5. Т.к. все 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

система комментирования CACKLE

Автор: Fur
Дата: 07 августа 2007


Разделы сайта
Главная
Блог
Native API
NTFS и ReFS
Микроконтроллеры
Справочник NTDLL
Коды NTSTATUS
Разное

Избранное
NTFS Stream Explorer
Native Shell
Тенгвар

Остальное
nvpnhcknn (архив)
English pages
Контакты

Ленты atom
Лента Atom сайта Лента Atom блога



При копировании материалов хорошим тоном будет указание авторства и ссылка на сайт.