Files
remote/tools/text_target.ps1
T

110 lines
4.5 KiB
PowerShell
Raw Normal View History

# 2b V6/V7 proof target: a dedicated WinForms window we own.
# ASCII-only source (PS 5.1 decodes BOM-less .ps1 as GBK; see personal lessons).
#
# Launched by text_injection_proof.ps1. Shows a topmost empty TextBox, writes
# its current text to ResultFile (UTF-8 with BOM) on every change, exits when
# StopFile appears. The proof injects through the remote-control pipeline and
# polls ResultFile - so "what arrived on the controlled desktop" is reported
# by the receiving application itself, not by a screenshot or OCR.
param(
[Parameter(Mandatory = $true)][string]$PidFile,
[Parameter(Mandatory = $true)][string]$ResultFile,
[Parameter(Mandatory = $true)][string]$StopFile,
# SendInput lands on whatever is FOREGROUND. When the injector is driven
# from another machine (phone -> agent -> this desktop), nothing in this
# process keeps our window on top, so an unrelated window could eat the
# injection. With this switch the form re-asserts foreground every tick and
# the caller still asserts foreground before injecting.
[switch]$GrabForeground
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Event handler scriptblocks resolve script-scoped vars; make that explicit
# instead of relying on the param locals staying on a visible scope chain.
$script:PidFile = $PidFile
$script:ResultFile = $ResultFile
$script:StopFile = $StopFile
$script:lastWritten = $null
$form = New-Object System.Windows.Forms.Form
$form.Text = "RemoteTextTarget"
$form.ClientSize = New-Object System.Drawing.Size(420, 140)
$form.TopMost = $true
$form.StartPosition = "Manual"
$form.Location = New-Object System.Drawing.Point(80, 80)
$tb = New-Object System.Windows.Forms.TextBox
$tb.Multiline = $true
$tb.Font = New-Object System.Drawing.Font("Segoe UI", 16.0)
$tb.Dock = "Fill"
$form.Controls.Add($tb)
$script:GrabForeground = $GrabForeground
if ($script:GrabForeground) {
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class OwnForeground {
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, IntPtr pid);
[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a, uint b, bool f);
// Deliberately NO keybd_event(VK_MENU). The usual 'tap ALT to beat the
// foreground lock' trick corrupts exactly what we measure: after ALT, Windows
// treats the next character as a menu MNEMONIC, so the first injected
// character is eaten. Measured 2026-10-30: ALT re-asserting every 60ms turned
// three injected characters into only 'b' in the target.
// Attaching input queues is a lock workaround that synthesises no key input.
public static bool Claim(IntPtr h) {
uint fgThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
uint myThread = GetCurrentThreadId();
AttachThreadInput(myThread, fgThread, true);
bool ok = SetForegroundWindow(h);
AttachThreadInput(myThread, fgThread, false);
return ok && GetForegroundWindow() == h;
}
public static IntPtr Now() { return GetForegroundWindow(); }
}
"@
}
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 150
$timer.Add_Tick({
if (Test-Path $script:StopFile) {
$form.Close()
return
}
if ($script:GrabForeground) {
# Re-assert only when actually lost (unconditional claiming every tick
# would fight the user for the desktop outside a proof run).
if ([OwnForeground]::Now() -ne $form.Handle) {
[void]$form.Activate()
[void][OwnForeground]::Claim($form.Handle)
}
}
if ($tb.Text -ne $script:lastWritten) {
$script:lastWritten = $tb.Text
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($script:ResultFile, $tb.Text, $enc)
}
if (-not $tb.Focused) { [void]$tb.Focus() }
})
$form.Add_Shown({
$form.Activate()
[void]$tb.Focus()
# Report BOTH pid and window handle: a proof caller resolving the handle via
# Get-Process races with window creation and can read 0. Handle is stable
# here because the form is realized by the time Shown fires.
[System.IO.File]::WriteAllText($script:PidFile, "$PID`n" + $form.Handle.ToInt64())
})
$timer.Start()
[void]$form.ShowDialog()
$timer.Stop()