# consoleinject.ps1 - type lines into the console of another process (windows). # # The G-106 hidden-console recipe's injection half, committed for test use (G-118 # item 11 console-reopen coverage in src/tests/shell/testsuites/punkexe/tclshcmd.test): # detach from our own console (FreeConsole), attach to the target pid's console # (AttachConsole), open CONIN$ and write key-down/key-up KEY_EVENT records for each # character of each line plus Enter (VK_RETURN + CR) after every line. Events are # buffered by the console, so injection may precede the target's console read. # # The target pid may be ANY process attached to the target console (e.g. a wrapper # that launched the real target with redirected std handles in that console). # # usage: powershell -NoProfile -ExecutionPolicy Bypass -File consoleinject.ps1 -TargetPid -TextFile # stdout: 'INJECT-OK lines=N' on success, 'INJECT-FAIL rc=<1|2|3> lasterr=' on failure # (rc 1 attach failed, 2 CONIN$ open failed, 3 WriteConsoleInput failed); exit code 0/1. param( [Parameter(Mandatory=$true)][int]$TargetPid, [Parameter(Mandatory=$true)][string]$TextFile ) $ErrorActionPreference = 'Stop' Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public static class PunkConsoleInject { [DllImport("kernel32.dll", SetLastError=true)] public static extern bool FreeConsole(); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool AttachConsole(uint dwProcessId); [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern IntPtr CreateFileW(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct KEY_EVENT_RECORD { public int bKeyDown; public ushort wRepeatCount; public ushort wVirtualKeyCode; public ushort wVirtualScanCode; public char UnicodeChar; public uint dwControlKeyState; } [StructLayout(LayoutKind.Explicit)] public struct INPUT_RECORD { [FieldOffset(0)] public ushort EventType; [FieldOffset(4)] public KEY_EVENT_RECORD KeyEvent; } [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern bool WriteConsoleInputW(IntPtr hConsoleInput, INPUT_RECORD[] lpBuffer, uint nLength, out uint lpNumberOfEventsWritten); // returns 0 ok; 1 attach failed; 2 conin open failed; 3 write failed. lastErr = GetLastError of the failing call. public static int Inject(uint targetPid, string[] lines, out int lastErr) { lastErr = 0; FreeConsole(); if (!AttachConsole(targetPid)) { lastErr = Marshal.GetLastWin32Error(); return 1; } IntPtr h = CreateFileW("CONIN$", 0xC0000000u, 0x3u, IntPtr.Zero, 3u, 0u, IntPtr.Zero); if (h == (IntPtr)(-1)) { lastErr = Marshal.GetLastWin32Error(); return 2; } foreach (string line in lines) { string full = line + "\r"; INPUT_RECORD[] recs = new INPUT_RECORD[full.Length * 2]; int i = 0; foreach (char c in full) { ushort vk = (c == '\r') ? (ushort)0x0D : (ushort)0; KEY_EVENT_RECORD kd = new KEY_EVENT_RECORD(); kd.bKeyDown = 1; kd.wRepeatCount = 1; kd.wVirtualKeyCode = vk; kd.wVirtualScanCode = 0; kd.UnicodeChar = c; kd.dwControlKeyState = 0; KEY_EVENT_RECORD ku = kd; ku.bKeyDown = 0; recs[i].EventType = 1; recs[i].KeyEvent = kd; i++; recs[i].EventType = 1; recs[i].KeyEvent = ku; i++; } uint written; if (!WriteConsoleInputW(h, recs, (uint)recs.Length, out written)) { lastErr = Marshal.GetLastWin32Error(); return 3; } } return 0; } } '@ $lines = @(Get-Content -LiteralPath $TextFile) $lastErr = 0 $rc = [PunkConsoleInject]::Inject([uint32]$TargetPid, [string[]]$lines, [ref]$lastErr) if ($rc -ne 0) { Write-Output "INJECT-FAIL rc=$rc lasterr=$lastErr" exit 1 } Write-Output "INJECT-OK lines=$($lines.Count)" exit 0