Windows IR: системный подход. Часть 2 — изоляция, процессы, службы


Это вторая часть материала, посвященного Windows Incident Response. В первой части рассказал про подготовку, общие принципы расследования, фиксацию состояния хоста и анализ сетевого состояния. В этой части — изолируем хост, чтобы прекратить возможную угрозу, не потеряв данные, а затем идём разбирать процессы и службы. Именно здесь у найденной в демонстрационной лабе сетевой активности будет установлен процесс-источник.
Шаг 4. Изоляция
Изоляцию зараженного хоста надо производить не позднее, чем после снятия сетевых данных и при наличии достаточных оснований.
Что я имею в виду. Во-первых, если существуют достаточные подозрения, что машина была скомпрометирована, изоляцию лучше произвести как можно скорее. Отсрочку можно сделать только ради оперативного анализа сетевых соединений, потому что после проведения изоляции смотреть в netstat будет нечего. С другой стороны, как было продемонстрировано в предыдущем разделе (в первой части), netstat может не отражать реального положения дел. Если соединение с C2 непостоянное, netstat не покажет ничего подозрительного.
Намного информативнее опираться на логи межсетевых экранов: периметрового и хостового, а изоляцию проводить как можно скорее.
Во-вторых, достаточные основания для изоляции. Вряд ли коллеги будут в восторге, если ИБ будут резать сеть при каждом чихе. Чтобы мотивировать свое решение по изоляции хоста, у команды реагирования должны быть причины. В этом качестве могут выступать алерты в SIEM/EDR, сработки антивируса или IDS/IPS.
В-третьих, необходимо заранее определить способ изоляции. Если перед вами физический компьютер, можно хоть кабель выдернуть из него — не совсем энтерпрайзное решение, но эффективно. Для виртуальных машин адаптер выключается на уровне гипервизора, а аналитик продолжает работать через консоль. Самый надежный и удобный способ для работы аналитика — изоляция средствами EDR. Все современные EDR поддерживают режим сетевой изоляции, и в то же время позволяют гибко настроить исключения в ней, если необходимо. Еще один способ — изоляция на сетевых устройствах: Запретить трафик на межсетевом экране (на уровне L3) и на коммутаторе (на уровне L2). На МЭ это делается через Deny правило any-any, с исключением для соединений с средствами безопасности. На свитче – через помещение в карантинный VLAN.
Изоляция средствами локального файерволла не может считаться надежной. Если атакующий имеет привилегии администратора, он может отключить его и снять изоляцию.
Сравнение способов изоляции
Способ изоляции | Удобство для аналитика | Эффективность против ВПО без привилегий | Устойчивость к привилегиям Администратора/SYSTEM | Риски и ограничения |
Физический (Отключение кабеля) | 🔴 Низкое (Теряется удаленный доступ к ОС) | 🟢 Эффективно | 🟢 Эффективно | Требует личного присутствия у компьютера. Неприменимо для удаленщиков |
На уровне гипервизора (Для ВМ) | 🟡 Среднее (Работа только через консоль ВМ) | 🟢 Эффективно | 🟢 Эффективно | Требуются права администратора на гипервизоре |
Средствами EDR | 🟢 Высокое | 🟢 Эффективно | 🟡 Средне (При привилегиях системы возможен обход) | Риск обхода через продвинутые техники (например, BYOVD — атаку на драйверы ядра) |
Сетевой уровень (VLAN + Правила МЭ) | 🟢 Высокое (Если настроены исключения для ИБ) | 🟢 Эффективно | 🟢 Эффективно (Атакующий не контролирует сетевые устройства) | Ошибки в настройках могут привести к полной потере управления хостом |
Локальный брандмауэр Windows | 🟢 Высокое | 🟢 Эффективно | 🔴 Неэффективно | Злоумышленник с правами админа может одной командой отключить локальный МЭ |
Перед началом изоляции запишем файл с помощью команды:
$IsolationStarted = Get-Date
@(
"Isolation time: $($IsolationStarted.ToString('o'))"
"Reason: %ВСТАВЬТЕ ПРИЧИНУ ИЗОЛЯЦИИ%"
"Method: %МЕТОД ИЗОЛЯЦИИ%"
) | Set-Content "$CaseRoot\isolation.txt"После начала изоляции надо проверить, что сеть отсутствует.

Да, кстати. Изоляция не завершает сеансы — она отрезает их от клиентов. Сеанс продолжает существовать вместе со всеми запущенными в нём процессами, но работа юзеров встанет мгновенно. Это событие с немедленным влиянием, звонки начнутся через минуту. К этому моменту должно быть согласовано, кто уведомляет пользователей и владельца сервиса. Аналитик, который объясняет по телефону сотрудникам, почему у них пропала работа, расследование в это время не ведёт.
Шаг 5. Процессы
К этому моменту известно, что у нас есть некие обращения по IP от неустановленного процесса. В других случаях может не быть ничего, но сеть нельзя мусолить бесконечно, да? Для начала запишем полную инвентаризацию процессов в файл командой:
Полный список процессов
$Processes = Get-CimInstance Win32_Process
$ProcessInventory = foreach ($Process in $Processes) {
$Owner = Invoke-CimMethod -InputObject $Process `
-MethodName GetOwner -ErrorAction SilentlyContinue
$Parent = $Processes |
Where-Object ProcessId -eq $Process.ParentProcessId |
Select-Object -First 1
[pscustomobject]@{
Name = $Process.Name
ProcessId = $Process.ProcessId
ParentProcessId = $Process.ParentProcessId
ParentName = $Parent.Name
User = if ($Owner.ReturnValue -eq 0) {
"$($Owner.Domain)\$($Owner.User)"
} else { $null }
CreationDate = $Process.CreationDate
ExecutablePath = $Process.ExecutablePath
CommandLine = $Process.CommandLine
}
}
$ProcessInventory | Sort-Object ProcessId |
Export-Csv "$CaseRoot\processes.csv" -NoTypeInformation -Encoding UTF8
С этим уже можно работать. Это полный список процессов с PID, PPID, владельцами и так далее. В принципе этот CSV в сочетании с фильтрами можно использовать для поиска необычных процессов. Одна беда – на боевой терминалке он будет огромный. Учитывая, что пользователей может быть с десяток, а какие-нибудь браузеры вполне могут держать каждую открытую вкладку как отдельный процесс — вывод будет на сотни процессов.
Нужен быстрый фильтр. На данной стадии анализа, пока нет никаких конкретных признаков вредоносного процесса, можно использовать фильтр на основе пути, по которому лежит исполняемый файл. Так мы уберем шум, исключив процессы из C:\Windows, C:\Program Files и C:\Program Files (x86).
Список процессов без шума
$SuspiciousInsideSystem = '^C:\\Windows\\(Temp|Tasks|Debug|Registration|System32\\Tasks|System32\\spool)\\'
Get-CimInstance Win32_Process | ForEach-Object {
$Proc = $_
$Owner = Invoke-CimMethod -InputObject $Proc -MethodName GetOwner -ErrorAction SilentlyContinue
$Parent = Get-CimInstance Win32_Process -Filter "ProcessId = $($Proc.ParentProcessId)" -ErrorAction SilentlyContinue
[pscustomobject]@{
Name = $Proc.Name
ProcessId = $Proc.ProcessId
ParentProcessId = $Proc.ParentProcessId
ParentName = $Parent.Name
User = if ($Owner.ReturnValue -eq 0) { "$($Owner.Domain)\$($Owner.User)" } else { $null }
CreationDate = $Proc.CreationDate
ExecutablePath = $Proc.ExecutablePath
CommandLine = $Proc.CommandLine
}
} |
Where-Object {
$_.ExecutablePath -and (
$_.ExecutablePath -match $SuspiciousInsideSystem -or (
$_.ExecutablePath -notmatch '^C:\\Windows(?:\\|$)' -and
$_.ExecutablePath -notmatch '^C:\\Program Files(?:\\|$)' -and
$_.ExecutablePath -notmatch '^C:\\Program Files \(x86\)(?:\\|$)'
)
)
} |
Sort-Object ExecutablePath |
Format-Table Name, ProcessId, ParentName, User, CreationDate, ExecutablePath -AutoSize
Каталоги со слабыми правами внутри c:\windows типа Temp возвращаются в выборку через переменную $SuspiciousInsideSystem.

Нам возвращается три процесса. Два из них — это наши оболочки, а третий – то, что мы искали.
C:\ProgramData\WindowsHealthMonitor\WindowsHealthMonitor.exe — каталог, доступный на запись, имя мимикрирует под системный компонент. Компонента Windows с таким названием не существует.
ParentName = services здесь невероятно полезная штука. services.exe это Service Control Manager, и он порождает только процессы служб. Значит, перед нами не программа, запущенная пользователем, а служба. Списка служб мы ещё не смотрели, но уже знаем, что искать там есть что.
CreationDate = 25.08.2026 0:21:36 — процесс создан через десять минут после того, как a.sokolova вошла в систему.
Фильтрация по каталогам — не панацея
Это неплохая попытка оперативно сократить шум, не зная, как выглядит то, что мы ищем. Однако вредоносный файл может лежать в Program Files, если установщик какой-то программы выдал кривые ACL на папку. Или файлов может вообще не быть (уже), а вредоносная нагрузка живет в памяти легитимного процесса. Но это лучше, чем ничего.
Еще одна маленькая заметка – список процессов «без шума» может быть больше, чем ожидается. В Appdata живет куча легитимного софта — Телега, например. Так что фильтрованный вывод не указывает на вредоносы со 100% вероятностью. Это первый проход, так сказать.
Фильтрация по цифровой подписи
Можно использовать как вторая независимая проверка или в случае, если первая проверка не дала результата.
Get-CimInstance Win32_Process |
Where-Object ExecutablePath |
ForEach-Object {
[pscustomobject]@{
Name = $_.Name
ProcessId = $_.ProcessId
Path = $_.ExecutablePath
Signature = (Get-AuthenticodeSignature -LiteralPath $_.ExecutablePath -ErrorAction SilentlyContinue).Status
}
} |
Where-Object Signature -ne 'Valid' |
Sort-Object Path |
Format-Table -AutoSize
Ложные срабатывания будут — часть легитимного ПО не подписана, — но на сервере их обычно немного, и просмотреть список целиком несложно. Да, атакующие могут использовать подписанное вредоносное ПО, и эта проверка не найдет такое.
Дерево процессов
C:\Kit\SysinternalsSuite\PsList64.exe -accepteula -t 
'C:\Kit\SysinternalsSuite\PsList64.exe' -accepteula -t |
Tee-Object "$CaseRoot\pslist-tree.txt"
Эта команда сохранит вывод в файл. Tee-Object печатает на экран и одновременно пишет в файл. Читаем вывод.

Видим уже упомянутый выше WindowsHealthMonitor. Он стоит в ряду с легитимными службами, маскируясь под одну из них. Также видим, что в дереве два комплекта csrss / winlogon / explorer:

Это означает, что у нас два пользовательских сеанса. Первый — наш: explorer 4276 породил cmd-ir, тот powershell-ir, а тот — только что запущенный pslist64. Второй — сеанс a.sokolova: explorer 8952 с деревом msedge под ним. Тот самый PID 8724, который держал два десятка соединений на 443 порт, мы смотрели его в разделе с сетью. Отдельно стоит LogonUI (PID 6252) — экран входа. Он согласуется с тем, что сеанс a.sokolova отключён: клиент отвалился, система показала экран блокировки.
Однако, тем не менее, пока не получается найти других подозрительных процессов помимо WindowsHealthMonitor. Вот еще одна команда на вывод более подробной информации о подозрительном процессе:
$SuspectProcess = Get-CimInstance Win32_Process `
-Filter "Name='WindowsHealthMonitor.exe'"
$SuspectProcess |
Select-Object Name, ProcessId, ParentProcessId,
CreationDate, ExecutablePath, CommandLine |
Format-List

Ну это мы уже видели выше. Посмотрим на эту службу через services.msc.

Тоже ничего интересного. Вернемся в Powershell и выполним еще несколько команд:
$SuspectPath = 'C:\ProgramData\WindowsHealthMonitor\WindowsHealthMonitor.exe'
Get-Item -LiteralPath $SuspectPath |
Select-Object FullName, Length, CreationTimeUtc, LastWriteTimeUtc | Format-List
Get-AuthenticodeSignature -LiteralPath $SuspectPath
Get-FileHash -LiteralPath $SuspectPath -Algorithm SHA256

Смотрим метаданные, подпись и хэш. Время создания и изменения совпадают до секунды: файл не редактировали после появления, его положили готовым. Размер — 5632 байта, 5.5 Кб – маловато для службы. Статус NotSigned нетипичен для легитимного windows ПО. Хэш пойдёт в IOC и понадобится для поиска того же файла на других хостах. Данные можно записать в файл:
Get-FileHash -LiteralPath $SuspectPath -Algorithm SHA256 |
Export-Csv "$CaseRoot\suspect-hash.csv" `
-NoTypeInformation -Encoding UTF8
Get-AuthenticodeSignature -LiteralPath $SuspectPath |
Export-Clixml "$CaseRoot\suspect-signature.xml"
Содержимое каталога
У нас есть подозрительные исходящие пакеты, есть подозрительный процесс. Они вроде бы совпадают по времени, но прямых доказательств пока нет. Продолжим расследование, проверив каталог, в котором находится подозрительный файл:
Get-ChildItem -LiteralPath (Split-Path $SuspectPath) -Force |
Select-Object Name, Length, CreationTime, LastWriteTime, Attributes |
Format-Table -AutoSize

Флаг -Force позволит видеть файлы с атрибутом «Скрытый». Видим, что все три файла созданы в одну секунду, то есть это автоматическое развертывание, а не установка вручную. service.log, видимо, продолжает перезаписываться, это видно по LastWriteTime.
Смотрим внутрь.

Вот и связь. Адрес 192.168.100.11:8080 — тот самый, который дал журнал брандмауэра. Теперь адрес привязан к конкретному файлу на сервере.
Загруженные модули
C:\Kit\SysinternalsSuite\ListDlls64.exe -accepteula 7336 |
Tee-Object "$CaseRoot\listdlls.txt"
С помощью утилиты ListDlls посмотрим загружаемые динамические библиотеки. Это особенно полезно, если легитимный процесс какой-либо программы начинает вести себя подозрительно. Это может быть результатом атаки DLL-Hijacking. В нашем выводе нет подозрительных библиотек из несистемных каталогов, но зато есть mscoreei.dll из каталога .NET.

То есть это дотнетовское приложение. В реальности это знание оказалось бы ценным для вашего специалиста по реверс-инжинирингу.
Хэндлы
C:\Kit\SysinternalsSuite\Handle64.exe -accepteula -p 7336 |
Tee-Object "$CaseRoot\handles.txt"Список хэндлов показывает, к каким объектам операционной системы процесс сейчас имеет открытый доступ. Для расследования это поможет понять, с чем процесс фактически взаимодействует, даже если по имени и пути процесса ничего подозрительного не видно. Это могут быть файлы, ключи реестра, другие процессы etc. В нашем случае — ничего подозрительного. Даже нет хэндлов на сеть и service.log, хотя файл явно пишется. Весьма вероятно, что работа производится очень быстро, и хэндл не держится открытым.
Строки
C:\Kit\SysinternalsSuite\Strings64.exe C:\ProgramData\WindowsHealthMonitor\WindowsHealthMonitor.exe |
Tee-Object "$CaseRoot\strings.txt"

С помощью Strings можно посмотреть строки исполняемого файла. Это первый шаг анализа без полноценного reverse engineering — быстро и не требует специальных знаний.

В коде виден адрес условного C2 (еще раз).
В реальности код вредоносного файла может быть обфусцирован, и Strings покажет абракадабру. Но даже при применении обфускации есть шанс вытащить какие-то читаемые куски, если искать по ключевым словам.
Что уже установлено
Соединение теперь привязано к файлу на диске. Файл лежит в ProgramData, в каталоге с еще двумя файлами, и работает как служба. Все три файла появились 25 августа в 00:21:36, в одну и ту же секунду. Осталось выяснить, кто и когда создал эту службу. Мы знаем, что за десять минут до этого пользователь a.sokolova вошла на систему, но прямых доказательств нет.
Для оперативного реагирования можно уже сейчас блокировать адрес C2 на периметре (если ещё не) и добавлять хэш файла WindowsHealthMonitor в правила запрета запуска программ. Продолжим поиски и проверим другие службы на хосте.
Шаг 6. Службы
Родитель services.exe уже сказал, что перед нами служба. Теперь посмотрим в сторону служб более основательно. Понятие службы не тождественно понятию процесса. Остановленная служба не будет порождать процесс, и в тасклисте его видно не будет — а затем служба стартанет и запустит процесс. Вообще, службы — один из основных способов закрепления, на них нужно обращать внимание.
Начнем с той, что уже нашли:
sc.exe qc WindowsHealthMonitor
Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\WindowsHealthMonitor
Эти две команды, в общем-то, отдают одно и то же.

Но берут данные из разных мест. sc.exe спрашивает Service Control Manager через API, Get-ItemProperty читает реестр напрямую.
Что нам говорят поля? Старт=2 (AUTO_START) — служба запускается при загрузке системы, до входа любого пользователя. LocalSystem - максимальные права в системе.
Просмотр всех служб
Get-CimInstance Win32_Service |
Select-Object Name, State, StartMode, StartName, PathName |
Sort-Object Name |
Export-Csv "$CaseRoot\services.csv" -NoTypeInformation -Encoding UTF8
или (если запись в файл не нужна)
Get-CimInstance Win32_Service |Select-Object Name,State,StartMode,StartName,PathName | Sort-Object Name | Format-Table -AutoSize
Тут, как и с процессами, проблему представляет количество служб. Мы используем тот же подход, что и в анализе процессов – фильтрацию по пути исполняемого файла для быстрого выявления необычных служб в подозрительных директориях.
Фильтр по директориям
Get-CimInstance Win32_Service | Where-Object {
$_.PathName -match 'ProgramData|AppData|\\Users\\|\\Temp\\'
} | Format-Table Name, State, StartMode, StartName, PathName -AutoSize

Пять служб, из них три – это бегущий на хосте Дефендер. Последняя в списке — уже выявленная вредоносная WindowsHealthMonitor . RDSMaintenance это что-то новое.
Проверка подписи файла
Get-AuthenticodeSignature C:\ProgramData\RDSMaintenance\RDSMaintenance.exeИзвлечение хэша
Get-FileHash C:\ProgramData\RDSMaintenance\RDSMaintenance.exeПросмотр информации о службе
sc.exe qc RDSMaintenance
Файл не подписан, стартует с правами системы по требованию. Хэш мы фиксируем и забираем. Все это выглядит не очень легитимно, но делать вывод о вредоносности пока рано.
Просмотр каталога службы
Посмотрим на файл еще раз и поищем, что лежит рядом.
Get-ChildItem C:\ProgramData\RDSMaintenance\ -Force
Размер exe – 5 килобайт. Рядом с exe лежит файл .cs (C#), созданный в то же время, и время создания не вписывается в предполагаемый таймлайн инцидента. Лезем внутрь через Get-Content – ничего подозрительного. Служба раз в минуту пишет heartbeat в лог. Ни сети, ни запуска процессов, ни работы с реестром. Проверяем уже сам исполняемый файл через Strings – и тоже ничего подозрительного.
В общем, это пустышка.
В любой живой инфраструктуре найдётся что-то подобное: администратор написал скрипт для сбора статистики, повесил его службой или задачей, проверил, что работает, и забыл. Через год администратор сменился, документации не осталось, а безымянный экзешник в ProgramData продолжает исправно писать лог, который никто не читает. Батник для ротации бэкапов, навайбкоженный агент, костыль для интеграции двух систем — вариантов масса. Сдать эту находку в отчет все равно надо, но в IOC она не пойдет, а просто как замечание по гигиене инфраструктуры. Мысль здесь в том, что в «боевой» инфре таких артефактов будет много. Чтобы не проводить мини-расследование по каждому скрипту непонятного происхождения, терпеливые люди ведут baseline — эталонный список того, что на системе работает. Во время IR заниматься этим поздно, но по его итогам вполне реально пропушить такую задачу инфраструктурщикам. Правда, вести такую документацию непросто – можно ниасилить.
Время создания служб
Здесь мы забежим немного вперед, к логам, чтобы установить время, когда службы были установлены. У нас есть дата создания исполняемых файлов этих служб, но оно необязательно должно быть таким же, поэтому проверяем, выполняя поиск по событию 7045:
Get-WinEvent -FilterHashtable @{
LogName = 'System'
Id = 7045
} | Where-Object {
$_.Message -match 'RDSMaintenance|WindowsHealthMonitor'
} | Format-List TimeCreated, Message

Или по 4697, если включен расширенный аудит:
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4697
} | Where-Object {
$_.Message -match 'RDSMaintenance|WindowsHealthMonitor'
} | Format-List TimeCreated, Message25.08.2026 00:21:36 — установлена Windows Health Monitor. Та же секунда, в которую созданы файлы в каталоге. Служба и полезная нагрузка появились одновременно, значит, развёртывались одним действием, а не собирались по частям.
17.08.2026 23:44:53 — установлена RDS Maintenance Service. За неделю до инцидента, тип запуска «Вручную».
Кстати, это материал для простого и эффективного корреляционного правила в SIEM по итогам инцидента, если атакующие создают вредоносные службы. Cобытие 7045 включено по умолчанию, и даже дохлый аудит должен его забирать. Корреляционное правило может быть настроено на исполняемый файл службы по путям Appdata, ProgramData, Users, Temp — если дополнить правило условием «запуск от LocalSystem», вредоносную службу можно поймать в момент установки.
Итог по службам
Найден один механизм закрепления: служба WindowsHealthMonitor с автозапуском, работающая от LocalSystem и обращающаяся к C2. Установлена 25.08.2026 в 00:21:36.
Вторая служба, похожая по формальным признакам, к инциденту отношения не имеет и уходит в отчёт как замечание по гигиене инфраструктуры.
Заключение второй части
В этой части мы рассмотрели проблемы изоляции зараженного хоста, анализ процессов и служб. В лабе найден первый механизм закрепления: служба WindowsHealthMonitor, работающая от LocalSystem и обращающаяся к адресу, найденному в первой части. Заодно проверили похожую по формальным признакам RDSMaintenance — и это оказался безобидный, хоть и небрежный артефакт инфраструктуры, а не находка по делу. Но на этом закрепление может не заканчиваться: службы — не единственный способ закрепиться в системе. В следующей части — задачи планировщика, автозапуск через реестр и пользователи, а также журналы, которые сложат всё найденное в единую хронологию. Предыдущая часть — тут.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ нового VDS — HABRFIRSTVDS.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.