Как вы можете использовать стеки вызовов

Как вы можете использовать стеки вызовов

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

Что такое стек вызовов?

Когда функция A, выполняющая поток, вызывает функцию B, ЦП автоматически сохраняет адрес текущей инструкции (внутри A) в область памяти, специфичную для потока, называемую стеком. Этот сохраненный указатель известен как адрес возврата — именно здесь выполнение возобновится после того, как B завершит свою работу. Если B вызовет третью функцию C, то адрес возврата внутри B также будет сохранен в стек. Эти адреса возврата можно получить с помощью процесса, известного как обход стека, который восстанавливает последовательность вызовов функций, которые привели к текущему состоянию потока. При обходе стека адреса возврата перечисляются в обратном хронологическом порядке, поэтому самая последняя функция всегда находится вверху.

Например, в Windows, когда мы дважды щелкаем по файлу notepad.exe, вызывается следующая серия функций:

  • Зеленый раздел относится к инициализации базового потока, выполняемой операционной системой, и обычно идентичен для всех операций (файл, реестр, процесс, библиотека и т. д.).
  • Красная часть — это код пользователя; он часто состоит из нескольких модулей и предоставляет приблизительные сведения о том, как была достигнута операция создания процесса.
  • Синий раздел — это уровень Win32 и Native API; это зависит от операции, включая последние 2–3 промежуточных модуля Windows перед пересылкой деталей операции для эффективного выполнения в режиме ядра.

На следующем снимке экрана показан стек вызовов для этой цепочки выполнения:

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

  • В синей части перечислены последние промежуточные API-интерфейсы Windows в пользовательском режиме перед пересылкой операции создания файла драйверам режима ядра для эффективного выполнения.
  • Красный раздел включает функции из user32.dll и notepad.exe, которые указывают на то, что эта файловая операция, вероятно, была инициирована через графический интерфейс.
  • Красный раздел включает функции из user32.dll и notepad.exe, которые указывают на то, что эта файловая операция, вероятно, была инициирована через графический интерфейс.
  • Зеленая часть представляет начальную инициализацию потока.

Объясняемость событий

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

Например, при поиске подозрительных запусков процессов, запущенных WmiPrvSe.exe через WMI, вы обнаруживаете этот экземпляр notepad.exe:

Просматривая стандартные поля журнала событий, можно предположить, что он был запущен с использованием класса Win32_Process с использованием синтаксиса вызова процесса wmic.exe create notepad.exe. Однако в деталях события описывается ряд модулей и функций:

В синем разделе показаны стандартные промежуточные API CreateProcess Windows, а в красном разделе выделена более подробная информация: мы видим, что перед первым вызовом CreateProcessW используется библиотека wbemcons.dll, а при проверке ее свойств мы видим, что она связана с событием потребителей WMI. Мы можем сделать вывод, что этот экземпляр notepad.exe, скорее всего, связан с подпиской на события WMI. Это потребует конкретных действий по реагированию на инциденты, чтобы смягчить действие механизма сохранения WMI.

Еще один отличный пример — запланированные задачи Windows. При выполнении они создаются как дочерние элементы службы расписания, которая запускается в ведущем процессе svchost.exe. На современных компьютерах с Windows 11 может быть запущено 50 или более процессов svchost.exe. К счастью, служба Schedule имеет специальный аргумент процесса -s Schedule, который отличает ее:

В более старых версиях Windows служба запланированных задач является членом группы сетевых служб и выполняется как компонент общего экземпляра svchost.exe netsvcs. Не все дочерние элементы этого процесса обязательно являются запланированными задачами в этих старых версиях:

Просматривая стек вызовов в обеих версиях, мы видим, что модуль, соседний с вызовом CreateProcess, — это один и тот же ubpm.dll (DLL Unified Background Process Manager), выполняющий экспортированную функцию ubpm.dll!UbpmOpenTriggerConsumer:

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

 

Еще один интересный пример возникает, когда пользователь дважды щелкает файл сценария из ZIP-архива, который был открыт с помощью проводника Windows. Глядя на дерево процессов, вы увидите, что explorer.exe является родительским, а дочерний процесс — интерпретатором сценариев, например wscript.exe или cmd.exe.

Это дерево процессов можно спутать с двойным щелчком пользователем файла сценария в любом месте файловой системы, что не очень подозрительно. Но если мы проверим стек вызовов, мы увидим, что родительский стек указывает на zipfld.dll (расширение оболочки Zipped Folders):

Примеры обнаружения

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

Процесс 

Создание подозрительного процесса посредством отражения

Dirty Vanity — это новейший метод внедрения кода, который злоупотребляет разветвлением процесса для выполнения шелл-кода внутри копии существующего процесса. Когда процесс разветвляется, ОС создает копию существующего процесса, включая его адресное пространство и любые наследуемые дескрипторы в нем.

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

Мы видим вызов RtlCreateProcessReflection и RtlCloneUserProcess для разветвления процесса. Теперь мы знаем, что это раздвоенный процесс, и следующий вопрос: «Распространено ли это в нормальных условиях?» Хотя с диагностической точки зрения такое поведение кажется распространенным и единичным, оно не является явным сигналом чего-то вредоносного. Далее проверяем, выполняют ли разветвленные процессы какие-либо сетевые подключения, загружают библиотеки DLL или порождают дочерние процессы, которые оказались менее распространенными и созданы для хорошего обнаружения:

descendant of 
   [process where event.action == "start" and 
   _arraysearch(process.parent.thread.Ext.call_stack, $entry, 
   $entry.symbol_info: 
    ("*ntdll.dll!RtlCreateProcessReflection*", 
    "*ntdll.dll!RtlCloneUserProcess*"))] and

not (process.executable : 
      ("?:\\WINDOWS\\SysWOW64\\WerFault.exe", 
      "?:\\WINDOWS\\system32\\WerFault.exe") and
     process.parent.thread.Ext.call_stack_summary : 
      "*faultrep.dll|wersvc.dl*")
// EQL detecting a forked process loading a network DLL 
//  or performs a network connection - very suspicious

sequence by process.entity_id with maxspan=1m
 [process where event.action == "start" and
  _arraysearch(process.parent.thread.Ext.call_stack, 
  $entry, $entry.symbol_info: 
    ("*ntdll.dll!RtlCreateProcessReflection*", 
    "*ntdll.dll!RtlCloneUserProcess*"))]
 [any where
  (
   event.category : ("network", "dns") or 
   (event.category == "library" and 
    dll.name : ("ws2_32.dll", "winhttp.dll", "wininet.dll"))
  )]
 

Вот пример разветвления explore.exe и выполнения шеллкода, который порождает cmd.exe из разветвленного экземпляра explorer.exe:

Прямой системный вызов через байты сборки

Второй и последний пример событий процесса — создание процесса посредством прямого системного вызова. При этом напрямую используется инструкция системного вызова вместо вызова API NtCreateProcess. Злоумышленники могут использовать этот метод, чтобы избежать продуктов безопасности, которые полагаются на перехват API пользовательского режима (чем Elastic Defend не является):

process where event.action : "start" and 

// EQL detecting a call stack not ending with ntdll.dll 
not process.parent.thread.Ext.call_stack_summary : "ntdll.dll*" and 

/* last call in the call stack contains bytes that execute a syscall
 manually using assembly <mov r10,rcx, mov eax,ssn, syscall> */

_arraysearch(process.parent.thread.Ext.call_stack, $entry,
 ($entry.callsite_leading_bytes : ("*4c8bd1b8??????000f05", 
 "*4989cab8??????000f05", "*4c8bd10f05", "*4989ca0f05")))

Этот пример соответствует случаю, когда последняя область памяти в стеке вызовов не резервирована и содержит ассемблерные байты, которые заканчиваются инструкцией системного вызова (0F05):

Файл

Подозрительный встроенный объект Microsoft Office

Следующая логика правила определяет подозрительные расширения файлов, записанные процессом Microsoft Office из встроенного потока OLE, которые часто используются вредоносными документами для удаления полезных данных для первоначального доступа.

// EQL detecting file creation event with call stack indicating 
// OleSaveToStream call to save or load the embedded OLE object

file where event.action != "deletion" and 

process.name : ("winword.exe", "excel.exe", "powerpnt.exe") and

_arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info:
 ("*!OleSaveToStream*", "*!OleLoad*")) and
(
 file.extension : ("exe", "dll", "js", "vbs", "vbe", "jse", "url", 
 "chm", "bat", "mht", "hta", "htm", "search-ms") or

 /* PE & HelpFile */
 file.Ext.header_bytes : ("4d5a*", "49545346*")

Пример совпадений:

Переименование подозрительного файла из незарезервированной памяти

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

file where event.action : "rename" and 
  
process.code_signature.status : "trusted" and file.extension != null and 

file.Ext.original.name : ("*.jpg", "*.bmp", "*.png", "*.pdf", "*.doc", 
"*.docx", "*.xls", "*.xlsx", "*.ppt", "*.pptx") and

not file.extension : ("tmp", "~tmp", "diff", "gz", "download", "bak", 
"bck", "lnk", "part", "save", "url", "jpg",  "bmp", "png", "pdf", "doc", 
"docx", "xls", "xlsx", "ppt", "pptx") and 

process.thread.Ext.call_stack_summary :
("ntdll.dll|kernelbase.dll|Unbacked",
 "ntdll.dll|kernelbase.dll|kernel32.dll|Unbacked", 
 "ntdll.dll|kernelbase.dll|Unknown|kernel32.dll|ntdll.dll", 
 "ntdll.dll|kernelbase.dll|Unknown|kernel32.dll|ntdll.dll", 
 "ntdll.dll|kernelbase.dll|kernel32.dll|Unknown|kernel32.dll|ntdll.dll", 
 "ntdll.dll|kernelbase.dll|kernel32.dll|mscorlib.ni.dll|Unbacked", 
 "ntdll.dll|wow64.dll|wow64cpu.dll|wow64.dll|ntdll.dll|kernelbase.dll|
 Unbacked", "ntdll.dll|wow64.dll|wow64cpu.dll|wow64.dll|ntdll.dll|
 kernelbase.dll|Unbacked|kernel32.dll|ntdll.dll", 
 "ntdll.dll|Unbacked", "Unbacked", "Unknown")

Вот несколько примеров совпадений, в которых explorer.exe (Windows Explorer) внедряется программой-вымогателем KNIGHT/CYCLOPS:

Исполняемый файл, удаленный неподписанной служебной DLL

Определенные типы вредоносных программ сохраняют свое присутствие, маскируясь под служебные библиотеки Windows. Чтобы служебная DLL распознавалась и управлялась диспетчером управления службами, она должна экспортировать функцию с именем ServiceMain. Приведенный ниже запрос KQL помогает идентифицировать случаи, когда создается исполняемый файл, а стек вызовов включает функцию ServiceMain.

event.category : file and file.Ext.header_bytes :4d5a* and process.name : svchost.exe and process.thread.Ext.call_stack.symbol_info :*!ServiceMain*

Библиотека

Загружен неподписанный драйвер монитора печати

Следующий запрос EQL идентифицирует загрузку неподписанной библиотеки службой очереди печати, где стек вызовов указывает, что загрузка поступает из SplAddMonitor. Злоумышленники могут использовать мониторы портов для запуска предоставленной злоумышленником DLL во время загрузки системы для обеспечения устойчивости или повышения привилегий.

library where
process.executable : ("?:\\Windows\\System32\\spoolsv.exe", 
"?:\\Windows\\SysWOW64\\spoolsv.exe") and not dll.code_signature.status : 
"trusted" and _arraysearch(process.thread.Ext.call_stack, $entry, 
$entry.symbol_info: "*localspl.dll!SplAddMonitor*")

Пример совпадения:

Потенциальная загрузка библиотеки через гаджеты ROP

Это правило EQL определяет загрузку библиотеки из необычных смещений win32u или ntdll. Это может указывать на попытку обойти мониторинг API с помощью гаджетов сборки возвратно-ориентированного программирования (ROP) для выполнения инструкции системного вызова из доверенного модуля.

library where
// adversaries try to use ROP gadgets from ntdll.dll or win32u.dll 
// to construct a normal-looking call stack

process.thread.Ext.call_stack_summary : ("ntdll.dll|*", "win32u.dll|*") and 

// excluding normal Library Load APIs - LdrLoadDll and NtMapViewOfSection
not _arraysearch(process.thread.Ext.call_stack, $entry, 
 $entry.symbol_info: ("*ntdll.dll!Ldr*", 
 "*KernelBase.dll!LoadLibrary*", "*ntdll.dll!*MapViewOfSection*"))

Этот пример соответствует ситуации, когда AtomLdr загружает DLL с помощью гаджетов ROP из win32u.dll вместо использования API-интерфейсов библиотеки загрузки ntdll (LdrLoadDll и NtMapViewOfSection).

Уклонение через перезапись LdrpKernel32

[LdrpKernel32(https://github.com/rbmm/LdrpKernel32DllName) — это интересный метод, позволяющий перехватить раннее выполнение процесса на этапе начальной загрузки путем перезаписи имени начальной библиотеки DLL, на которое ссылается память ntdll.dll, — вынуждая процесс загрузить вредоносную DLL.

library where 
 
// BaseThreadInitThunk must be exported by the rogue bootstrap DLL
 _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info :
  "*!BaseThreadInitThunk*") and

// excluding kernel32 that exports normally exports BasethreadInitThunk
not _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info
 ("?:\\Windows\\System32\\kernel32.dll!BaseThreadInitThunk*", 
 "?:\\Windows\\SysWOW64\\kernel32.dll!BaseThreadInitThunk*", 
 "?:\\Windows\\WinSxS\\*\\kernel32.dll!BaseThreadInitThunk*", 
 "?:\\Windows\\WinSxS\\Temp\\PendingDeletes\\*!BaseThreadInitThunk*", 
 "\\Device\\*\\Windows\\*\\kernel32.dll!BaseThreadInitThunk*"))

Пример совпадения:

Подозрительное изменение удаленного реестра

Как и в примере с запланированным заданием, служба удаленного реестра размещается в svchost.exe. Мы можем использовать стек вызовов для обнаружения изменений в реестре, отслеживая, когда служба удаленного реестра указывает на исполняемый файл или файл сценария. Это может указывать на попытку перемещения в сторону посредством удаленного изменения конфигурации.

registry where 

event.action == "modification" and 

user.id : ("S-1-5-21*", "S-1-12-*") and 

 process.name : "svchost.exe" and 

// The regsvc.dll in call stack indicate that this is indeed the 
// svchost.exe instance hosting the Remote registry service

process.thread.Ext.call_stack_summary : "*regsvc.dll|rpcrt4.dll*" and

 (
  // suspicious registry values
  registry.data.strings : ("*:\\*\\*", "*.exe*", "*.dll*", "*rundll32*", 
  "*powershell*", "*http*", "* /c *", "*COMSPEC*", "\\\\*.*") or
  
  // suspicious keys like Services, Run key and COM
  registry.path :
         ("HKLM\\SYSTEM\\ControlSet*\\Services\\*\\ServiceDLL",
          "HKLM\\SYSTEM\\ControlSet*\\Services\\*\\ImagePath",
          "HKEY_USERS\\*Classes\\*\\InprocServer32\\",
          "HKEY_USERS\\*Classes\\*\\LocalServer32\\",
          "H*\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\*") or
  
  // potential attempt to remotely disable a service 
  (registry.value : "Start" and registry.data.strings : "4")
  )

Этот пример соответствует случаю, когда значение реестра ключа запуска изменяется удаленно через службу удаленного реестра:

Заключение

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.