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

В предыдущем блоге этой серии я объяснил подходы и проблемы обнаружения сетевых маяков.



В этом посте я разработаю подход и максимально решу проблемы с помощью KQL и создам запросы для Sysmon, Palo Alto и Microsoft Defender для Endpoint. Вы также сможете изменить их в соответствии со своей средой.

Продолжая тот же пример, маяк CS с 15-минутным режимом сна и 25% джиттером, мы можем вычислить следующие значения для маяка (эти значения можно рассчитать путем анализа временных дельт):
MinBeaconSleep = 675s
MaxBeaconSleep = 900 с
AvgBeaconSleep = 787,5 с
MinStdev (BeaconSleep) = 0 с
MaxStdev (BeaconSleep) = MaxBeaconSleep - AvgBeaconSleep = 112,5 с

Используя информацию выше, мы можем рассчитать приблизительное / точное соотношение джиттера. Как? Что ж, это основная математика: у нас есть уравнение с несколькими переменными.

CalculatedJitter = (MaxStdev (BeaconSleep) / AvgBeaconSleep) * 100 = 14,2%

Причиной того, что рассчитанный джиттер меньше сконфигурированного джиттера, является поведение маяка Cobalt Strike Beacon. Джиттер в Cobalt Strike сдвигает среднее время ожидания маяка влево от настроенного значения ожидания. Если бы это был маяк Empire, рассчитанный джиттер составлял бы 25%.

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

Разработка KQL-запроса

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

  • Исходное имя пользователя / Исходное имя хоста
  • IP-адрес назначения / имя хоста назначения
  • Порт назначения
  • Отметка времени

Мы можем использовать информацию requestURL или URLHostname для журналов прокси, если захотим. Использование информации об исходном IP-адресе не рекомендуется, поскольку назначения IP-адресов во время VPN-подключений не кэшируются в DHCP. Следовательно, вы можете увидеть одно устройство с несколькими назначенными ему разными IP-адресами, что нарушит логику обнаружения.

Логика

For each Source-Destination-Port pair:
    sort the Timestamp ascending
    calculate time difference between each Timestamp
    calculate the stdev, avg, min, and max of the time deltas
    calcualte the jitter by using the stddev and avg time delta
    If jitter < [threshold]
        display the details/generate an alert
  • Минимальные и максимальные значения дельты времени могут использоваться для повышения точности, если результатов слишком много (разница времени маяка должна быть между MinBeaconSleep и MaxBeaconSleep, но могут быть некоторые всплески).
  • Если к одному месту назначения подключено более X пользователей / компьютеров, это, скорее всего, не вредоносный маяк (например, обновление Windows).
  • Если в журналах указан только IP-адрес места назначения, результаты могут быть расширены несколькими способами, например, путем присоединения к другим событиям, имеющим информацию об IP-адресе и имени хоста.

С помощью KQL мы можем помещать TimeGenerated, SentBytes и ReceivedBytes в списки с помощью make_set / make_list. Затем мы можем отсортировать значения отметок времени с помощью array_sort_asc (использование сериализации и сортировки не работает, когда данные большие). Поскольку у нас есть массив, мы можем использовать его длину, чтобы применить некоторую фильтрацию:

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

Далее мы можем использовать mv-apply для массива временных меток set_TimeGenerated, чтобы выполнить подзапрос для каждой пары соединений. Этот подход также улучшает производительность запросов. В подзапросе мы можем выполнить первый шаг анализа маяка, используя только что определенный нами JitterThreshold:

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

Теперь у нас есть все кандидаты на маяки на основе определенных пороговых значений. Затем мы можем отфильтровать маяки на основе порогового значения CompromisedDeviceCountMax. Если к одному и тому же месту назначения передается несколько устройств / пользователей, скорее всего, маяки не являются вредоносными.

Мы помещаем результаты в новую переменную PotentialBeacons для дальнейшего анализа.

Далее мы найдем соединения, которые не могут быть маяком. Мы будем использовать TimeDeltaList и list_SentBytes:

Для анализа выбросов мы использовали series_outliers. Функция выполняет собственный анализ Тьюки, который допускает наличие некоторых выбросов в данных. Вдобавок к этому, если слишком много выбросов, соединение не может быть маяком. Поскольку устройство может быть выключено или не подключено к сети какое-то время, временные дельты могут иметь скачки, но эти скачки не должны происходить часто. Поэтому мы поставили условие OutlierCountMax; принять больше шипов.

Поскольку теперь у нас есть PotentialBeacons, ImpossibleBeaconsByTimeDelta и ImpossibleBeaconsBySentBytes, мы, наконец, можем получить все настоящие маяки, удалив ImpossibleBeaconsByTimeDelta. и ImpossibleBeaconsBySentBytes из PotentialBeacons:

Пример результата (отредактированный) и как читать данные:

Обновление: мы можем пойти дальше и провести исторический анализ хоста / IP-адреса назначения, чтобы получить информацию о распространенности для определения приоритетов расследования.

Я разработал запросы для Palo Alto FW (Azure Sentinel), Sysmon (Azure Sentinel) и Microsoft Defender for Endpoint / Microsoft 365 Defender. Вы можете найти запросы в моем репозитории на GitHub. Они работают очень быстро (90 миллионов событий анализируются за 20 секунд) и могут обнаруживать маяки с высоким джиттером, например, 90%.



Как использовать запросы

Сначала нам нужно определить границы маяков, которые вы хотите обнаружить. Определение границ на основе поведения маяка Империи охватывает Cobalt Strike и другие.

Охота только с джиттером

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

Охота с джиттером и интервалом сна

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

Пример: маяки, которые находятся в спящем режиме не менее 15 минут (900 секунд) с джиттером% 25

  • JitterThreshold = 25
  • TimeDeltaThresholdMin = 900 - (900 * 25/100) = 675 = 11 минут 15 секунд

При желании мы хотим установить верхнюю границу интервала сна:

  • TimeDeltaThresholdMax = 900 + (900 * 25/100) = 1125 = 18 минут 45 секунд.

На основе этих значений мы можем отфильтровать результаты.

Вывод

Обнаружить маяки C2 сложно, но не невозможно (просто необходимы некоторые статистические знания). Маячки с высокой конфигурацией джиттера, например 100%, труднее обнаружить, но все же возможно, если у вас есть время для анализа результатов.

Вы можете автоматизировать анализ результатов несколькими способами, такими как использование Logic Apps, обогащение данных оценкой VT, использование Jupyter Notebook и т. Д.

Хотя на этом серия заканчивается, я расскажу о конкретном сценарии C2, используя метод, который я объяснил. Если вы видите ошибку или хотите что-то спросить о методе, напишите мне в Twitter.

Хорошей охоты!