diff --git a/.gitignore b/.gitignore index cf40000..d08c0a5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ src/WinDbgMCP.Server/appsettings.json # Frida temp scripts frida_script_*.js + +# Staged guest-side binaries downloaded by scripts/setup.ps1 +guest-tools/ diff --git a/README.md b/README.md index 15a7855..4ced137 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ All without the LLM ever needing direct access to WinDbg, a terminal, or the VM | [frida-tools](https://frida.re/) *(optional)* | User-mode instrumentation | | [WinDbg Preview](https://aka.ms/windbg) *(optional)* | Provides dbgsrv.exe for remote user-mode debugging | +> **New machine?** Run `pwsh -File scripts/setup.ps1` to build the server and auto-install/stage everything that can be automated (NuGet restore, frida-tools, a version-matched frida-server for the guest), plus a health check for VMware/WDK. Use `-CheckOnly` to diagnose without changing anything. See **[SETUP.md](SETUP.md)** for the full dependency breakdown — what's bundled vs. what you must provide. + ### 1. Clone & Build ```bash diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..36757e0 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,76 @@ +# Setup Guide + +This document explains what the WinDbg MCP server depends on, **what installs automatically vs. what you must provide**, and how to use `scripts/setup.ps1` to bootstrap a fresh machine. + +## TL;DR + +```powershell +# From the repo root, in PowerShell: +pwsh -File scripts/setup.ps1 # build + install/stage everything possible +pwsh -File scripts/setup.ps1 -CheckOnly # just diagnose, change nothing +``` + +The script is idempotent and non-destructive. Re-run it any time. + +## What's bundled vs. what isn't + +Only the **.NET/NuGet** layer is self-installing. `dotnet build` auto-restores the NuGet packages (`ClrDebug`, `Microsoft.Extensions.*`, `ModelContextProtocol`) — nothing to do there beyond having the .NET 8 SDK. Everything else is an **external tool the server shells out to**, and none of it ships in this repo. + +| Dependency | Needed for | Bundled? | How `setup.ps1` handles it | +|---|---|---|---| +| **.NET 8 SDK** | build & run | No (prereq) | Detected; link printed if missing | +| **NuGet packages** | the server itself | **Yes** — auto-restored | Restored during build | +| **frida-tools** (`frida`, `frida-ps` CLIs) | `umd_frida*` (host side) | No | **Installed via `pip install --user frida-tools`** | +| **frida-server.exe** | Frida agent (runs in guest) | No | **Downloaded (version-matched) into `guest-tools\`** | +| **vmrun.exe** | `vm_*` / `guest_*` | No | Detected; VMware Workstation install required | +| **cdb.exe** (WDK debuggers) | `umd_dbgsrv_*` | No | Detected; install hint printed | +| **dbgeng.dll** | kernel debugging (`kd_*`) | Ships with Windows | Used via P/Invoke; WDK version recommended for symbols | +| **dbgsrv.exe** | dbgsrv (runs in guest) | No | Reminder printed; stage into guest manually | +| **TTD.exe / tttracer** | `umd_ttd` (runs in guest) | No | Reminder printed; stage into guest manually | + +**Bottom line:** installing on a new machine does **not** automatically pull in Frida, VMware, or the WDK. `setup.ps1` automates the two things that *can* be automated (frida-tools on the host, frida-server for the guest) and clearly reports the rest. + +## Host prerequisites you must install yourself + +1. **.NET 8 SDK** — . Required to build and run. +2. **VMware Workstation Pro** — provides `vmrun.exe`. Required for all `vm_*` and `guest_*` tools. Without it, the server runs in **EXTERNAL-TARGET mode**: VM/guest tools are disabled but kernel debugging over KDNET still works (see [PR #1](https://github.com/memoryforensics1/windbg-mcp/pull/1)). +3. **Python 3.10+** — only needed for the user-mode Frida tools (`umd_frida*`). `setup.ps1` uses it to install frida-tools and to decompress the downloaded frida-server. +4. **Debugging Tools for Windows (cdb.exe)** — from the Windows SDK/WDK or WinDbg. Only needed for `umd_dbgsrv_*`. The server searches: + - `C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe` + - `C:\Program Files\Windows Kits\10\Debuggers\x64\cdb.exe` + - `C:\Debuggers\cdb.exe` + +Where the host looks for the `frida` CLI (auto-resolved by `FridaManager`): `%ProgramFiles%\Python31x\Scripts\`, `%LocalAppData%\Programs\Python\Python312\Scripts\`, then `PATH`. If you install Python elsewhere, make sure its `Scripts` directory is on `PATH`. + +## Guest-VM components (inside the target VM) + +These run **inside the Windows VM being debugged** and therefore cannot be bundled with the host server. `setup.ps1` stages frida-server into `guest-tools\` for you; transfer it with `guest_transfer_to_vm` once the VM is running. The server expects these known paths: + +| File | Guest path | Source | +|---|---|---| +| `frida-server.exe` | `C:\Tools\frida-server.exe` (port 27042) | Staged by `setup.ps1` into `guest-tools\` | +| `dbgsrv.exe` + `dbgeng.dll` + `dbghelp.dll` | `C:\Tools\DbgSrv\` (port 5064) | WDK / WinDbg Preview | +| `TTD.exe` | `C:\Tools\TTD\TTD.exe` | WinDbg Preview / WDK | + +Start frida-server in the guest, then verify: `guest_run_command("tasklist /FI \"IMAGENAME eq frida-server.exe\"")`. +Start dbgsrv when needed: `guest_run_command("start /b C:\Tools\DbgSrv\dbgsrv.exe -t tcp:port=5064")`. + +Also required in the guest: **KDNET enabled** (`bcdedit /dbgsettings net ...`) for kernel debugging — see the README's VM Setup section. + +## `scripts/setup.ps1` options + +| Flag | Effect | +|---|---| +| *(none)* | Build, install frida-tools, stage frida-server, create `appsettings.json`, run all checks | +| `-CheckOnly` | Diagnose only — no installs, downloads, or build. Prints a status report | +| `-SkipBuild` | Skip `dotnet build` (tooling only) | +| `-SkipFrida` | Skip frida-tools install and frida-server download | +| `-GuestArch ` | Guest OS architecture for the frida-server download (default `x86_64`) | + +The script ends with a summary table and exits non-zero only if a **blocking** prerequisite (.NET 8 / build) is missing. Missing optional components produce `WARN` rows, not failures. + +## After setup + +1. Edit `src/WinDbgMCP.Server/appsettings.json` (created from the example) — set `VmxPath`, guest credentials, and KDNET key. This file is git-ignored because it holds secrets. +2. Run the server: `dotnet run --project src/WinDbgMCP.Server/WinDbgMCP.Server.csproj` +3. Point your MCP client at it (see README → MCP Client Configuration). diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 new file mode 100644 index 0000000..32ba07f --- /dev/null +++ b/scripts/setup.ps1 @@ -0,0 +1,297 @@ +<# +.SYNOPSIS + One-shot setup / health-check for the WinDbg MCP server and its external dependencies. + +.DESCRIPTION + "Bundle what's possible" bootstrapper. It automates every prerequisite that + can legally and practically be automated, and clearly reports the ones that + must be installed by hand (VMware, WDK debuggers) or staged into the guest VM. + + What it does: + * Verifies the .NET 8 SDK, then restores + builds the server (NuGet packages + are the only truly "bundled" dependency - they auto-restore on build). + * Installs frida-tools on the HOST via pip (the frida / frida-ps CLIs the + server shells out to for umd_frida*). + * Downloads the matching frida-server.exe for the GUEST into .\guest-tools\ + so you can transfer it to C:\Tools\frida-server.exe inside the VM. + * Detects vmrun.exe (VMware), cdb.exe (WDK debuggers, for umd_dbgsrv_*), and + TTD, and tells you exactly what's missing and how to get it. + * Creates appsettings.json from the example if it doesn't exist yet. + * Prints a summary table (a "doctor" report). + + Nothing here is destructive. Re-run it any time; it is idempotent. + +.PARAMETER CheckOnly + Diagnose only - no installs, no downloads, no build. Just report status. + +.PARAMETER SkipBuild + Skip the dotnet restore/build step (useful when you only want the tooling). + +.PARAMETER SkipFrida + Skip installing frida-tools and downloading frida-server. + +.PARAMETER GuestArch + Architecture of the GUEST OS for the frida-server download. Default x86_64. + Valid: x86_64, x86, arm64. + +.EXAMPLE + pwsh -File scripts/setup.ps1 + Full setup: build, install frida-tools, stage frida-server, run checks. + +.EXAMPLE + pwsh -File scripts/setup.ps1 -CheckOnly + Health check only. Prints what's present and what's missing. +#> +[CmdletBinding()] +param( + [switch]$CheckOnly, + [switch]$SkipBuild, + [switch]$SkipFrida, + [ValidateSet('x86_64', 'x86', 'arm64')] + [string]$GuestArch = 'x86_64' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# --- Paths ------------------------------------------------------------------- +$RepoRoot = Split-Path -Parent $PSScriptRoot +$ServerDir = Join-Path $RepoRoot 'src\WinDbgMCP.Server' +$Csproj = Join-Path $ServerDir 'WinDbgMCP.Server.csproj' +$AppSettings = Join-Path $ServerDir 'appsettings.json' +$ExampleSettings = Join-Path $ServerDir 'appsettings.example.json' +$GuestToolsDir = Join-Path $RepoRoot 'guest-tools' + +# --- Reporting --------------------------------------------------------------- +$script:Report = [System.Collections.Generic.List[object]]::new() + +function Add-Result { + param([string]$Component, [ValidateSet('OK', 'WARN', 'FAIL', 'INFO')][string]$Status, [string]$Detail) + $script:Report.Add([pscustomobject]@{ Component = $Component; Status = $Status; Detail = $Detail }) +} +function Write-Head { param([string]$m) Write-Host "`n=== $m ===" -ForegroundColor Cyan } +function Write-Ok { param([string]$m) Write-Host " [ OK ] $m" -ForegroundColor Green } +function Write-Warn { param([string]$m) Write-Host " [WARN] $m" -ForegroundColor Yellow } +function Write-Bad { param([string]$m) Write-Host " [FAIL] $m" -ForegroundColor Red } +function Write-Info { param([string]$m) Write-Host " [INFO] $m" -ForegroundColor Gray } + +function Get-CommandPath { + param([string]$Name) + $c = Get-Command $Name -ErrorAction SilentlyContinue + if ($c) { return $c.Source } + return $null +} + +Write-Host "WinDbg MCP Server - setup / doctor" -ForegroundColor White +Write-Host "Repo: $RepoRoot" +if ($CheckOnly) { Write-Host "Mode: CHECK ONLY (no changes will be made)" -ForegroundColor Yellow } + +# --- 1. .NET SDK ------------------------------------------------------------- +Write-Head ".NET 8 SDK" +$dotnet = Get-CommandPath 'dotnet' +if (-not $dotnet -and (Test-Path 'C:\Program Files\dotnet\dotnet.exe')) { + $dotnet = 'C:\Program Files\dotnet\dotnet.exe' +} +if ($dotnet) { + $sdks = & $dotnet --list-sdks 2>$null + $has8 = $sdks | Where-Object { $_ -match '^8\.' } + if ($has8) { + Write-Ok "dotnet found: $dotnet (has .NET 8 SDK)" + Add-Result '.NET 8 SDK' 'OK' $dotnet + } else { + Write-Warn "dotnet found but no .NET 8 SDK installed. Installed: $($sdks -join ', ')" + Add-Result '.NET 8 SDK' 'WARN' 'dotnet present, .NET 8 SDK missing' + } +} else { + Write-Bad "dotnet not found. Install the .NET 8 SDK: https://dotnet.microsoft.com/download/dotnet/8.0" + Add-Result '.NET 8 SDK' 'FAIL' 'dotnet not on PATH' +} + +# --- 2. Build (restores NuGet packages) -------------------------------------- +Write-Head "Build server (restores NuGet - the only bundled dependency)" +if ($CheckOnly -or $SkipBuild) { + Write-Info "Skipped (CheckOnly/SkipBuild)." + Add-Result 'Build' 'INFO' 'skipped' +} elseif (-not $dotnet) { + Write-Bad "Cannot build without dotnet." + Add-Result 'Build' 'FAIL' 'no dotnet' +} else { + Write-Info "dotnet build $Csproj" + & $dotnet build $Csproj -c Release --nologo + if ($LASTEXITCODE -eq 0) { + Write-Ok "Build succeeded (NuGet packages restored)." + Add-Result 'Build' 'OK' 'restored + built' + } else { + Write-Bad "Build failed (exit $LASTEXITCODE)." + Add-Result 'Build' 'FAIL' "exit $LASTEXITCODE" + } +} + +# --- 3. Python + frida-tools (HOST) ------------------------------------------ +Write-Head "Frida host tooling (frida-tools via pip)" +$python = Get-CommandPath 'python' +if (-not $python) { $python = Get-CommandPath 'py' } +$fridaCli = Get-CommandPath 'frida' +$fridaVersion = $null + +if ($SkipFrida) { + Write-Info "Skipped (-SkipFrida)." + Add-Result 'frida-tools (host)' 'INFO' 'skipped' +} elseif (-not $python) { + Write-Warn "Python not found. Install Python 3.10+ from https://python.org, then re-run. (Only needed for umd_frida*.)" + Add-Result 'frida-tools (host)' 'WARN' 'python missing' +} else { + if ($fridaCli -and -not $CheckOnly) { + Write-Info "frida CLI already present: $fridaCli" + } + if (-not $fridaCli -and -not $CheckOnly) { + Write-Info "Installing frida-tools (pip install --user frida-tools)..." + & $python -m pip install --user --upgrade frida-tools + if ($LASTEXITCODE -ne 0) { Write-Warn "pip install frida-tools failed; user-mode Frida tools will be unavailable." } + $fridaCli = Get-CommandPath 'frida' + } + if ($fridaCli) { + try { $fridaVersion = (& $fridaCli --version 2>$null).Trim() } catch { $fridaVersion = $null } + Write-Ok "frida CLI: $fridaCli (v$fridaVersion)" + Add-Result 'frida-tools (host)' 'OK' "v$fridaVersion" + } elseif ($CheckOnly) { + Write-Warn "frida CLI not found (run without -CheckOnly to install)." + Add-Result 'frida-tools (host)' 'WARN' 'not installed' + } else { + Write-Warn "frida CLI still not found after install. Check that Python Scripts dir is on PATH." + Add-Result 'frida-tools (host)' 'WARN' 'install did not expose CLI' + } +} + +# --- 4. frida-server for the GUEST ------------------------------------------- +Write-Head "Frida guest agent (frida-server.exe -> guest-tools\)" +if ($SkipFrida) { + Write-Info "Skipped (-SkipFrida)." + Add-Result 'frida-server (guest)' 'INFO' 'skipped' +} elseif (-not $fridaVersion) { + Write-Warn "Unknown frida version (host frida-tools not installed) - cannot match frida-server. Install frida-tools first." + Add-Result 'frida-server (guest)' 'WARN' 'no version to match' +} else { + $stagedServer = Join-Path $GuestToolsDir 'frida-server.exe' + if (Test-Path $stagedServer) { + Write-Ok "Already staged: $stagedServer" + Add-Result 'frida-server (guest)' 'OK' 'staged' + } elseif ($CheckOnly) { + Write-Warn "Not staged yet (run without -CheckOnly to download frida-server v$fridaVersion)." + Add-Result 'frida-server (guest)' 'WARN' 'not staged' + } else { + if (-not (Test-Path $GuestToolsDir)) { New-Item -ItemType Directory -Path $GuestToolsDir | Out-Null } + $xzName = "frida-server-$fridaVersion-windows-$GuestArch.exe.xz" + $url = "https://github.com/frida/frida/releases/download/$fridaVersion/$xzName" + $xzPath = Join-Path $GuestToolsDir $xzName + Write-Info "Downloading $url" + try { + Invoke-WebRequest -Uri $url -OutFile $xzPath -UseBasicParsing + # Decompress .xz using Python's lzma (Python is guaranteed present - frida-tools needs it). + Write-Info "Decompressing (python lzma) -> $stagedServer" + $py = @" +import lzma, shutil, sys +with lzma.open(sys.argv[1]) as fin, open(sys.argv[2], 'wb') as fout: + shutil.copyfileobj(fin, fout) +"@ + $tmpPy = Join-Path $GuestToolsDir '_unxz.py' + Set-Content -Path $tmpPy -Value $py -Encoding utf8 + & $python $tmpPy $xzPath $stagedServer + Remove-Item $tmpPy -Force -ErrorAction SilentlyContinue + Remove-Item $xzPath -Force -ErrorAction SilentlyContinue + if (Test-Path $stagedServer) { + Write-Ok "Staged frida-server v$fridaVersion -> $stagedServer" + Write-Info "Transfer it into the guest as C:\Tools\frida-server.exe (guest_transfer_to_vm), then run it." + Add-Result 'frida-server (guest)' 'OK' "v$fridaVersion staged" + } else { + Write-Warn "Decompression produced no file. Extract $xzName manually." + Add-Result 'frida-server (guest)' 'WARN' 'decompress failed' + } + } catch { + Write-Warn "Download/stage failed: $($_.Exception.Message)" + Write-Info "Manually grab frida-server-$fridaVersion-windows-$GuestArch.exe.xz from github.com/frida/frida/releases" + Add-Result 'frida-server (guest)' 'WARN' 'download failed' + } + } +} + +# --- 5. VMware vmrun (HOST) -------------------------------------------------- +Write-Head "VMware vmrun (VM & guest tools)" +$vmrunCandidates = @( + 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe', + 'C:\Program Files\VMware\VMware Workstation\vmrun.exe', + 'C:\Program Files (x86)\VMware\VMware VIX\vmrun.exe' +) +$vmrun = $vmrunCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 +if (-not $vmrun) { $vmrun = Get-CommandPath 'vmrun' } +if ($vmrun) { + Write-Ok "vmrun found: $vmrun" + Add-Result 'vmrun (VMware)' 'OK' $vmrun +} else { + Write-Warn "vmrun not found. Install VMware Workstation Pro for vm_*/guest_* tools." + Write-Info "Without it the server runs in EXTERNAL-TARGET mode: kernel debugging over KDNET still works." + Add-Result 'vmrun (VMware)' 'WARN' 'not installed (KDNET-only mode)' +} + +# --- 6. cdb.exe / WDK debuggers (HOST) --------------------------------------- +Write-Head "WDK debuggers (cdb.exe - for umd_dbgsrv_*)" +$cdbCandidates = @( + 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe', + 'C:\Program Files\Windows Kits\10\Debuggers\x64\cdb.exe', + 'C:\Debuggers\cdb.exe' +) +$cdb = $cdbCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 +if ($cdb) { + Write-Ok "cdb.exe found: $cdb" + Add-Result 'cdb (WDK debuggers)' 'OK' $cdb +} else { + Write-Warn "cdb.exe not found. dbgsrv-based user-mode debugging (umd_dbgsrv_*) will be unavailable." + $winget = Get-CommandPath 'winget' + if ($winget) { + Write-Info "Install the Debugging Tools for Windows: winget install Microsoft.WinDbg (or the Windows SDK 'Debugging Tools' feature)." + } else { + Write-Info "Install the Windows SDK 'Debugging Tools for Windows': https://aka.ms/windbg" + } + Add-Result 'cdb (WDK debuggers)' 'WARN' 'not installed' +} + +# --- 7. appsettings.json ----------------------------------------------------- +Write-Head "Configuration (appsettings.json)" +if (Test-Path $AppSettings) { + Write-Ok "appsettings.json present." + Add-Result 'appsettings.json' 'OK' 'present' +} elseif ($CheckOnly) { + Write-Warn "appsettings.json missing (run without -CheckOnly to create from example)." + Add-Result 'appsettings.json' 'WARN' 'missing' +} elseif (Test-Path $ExampleSettings) { + Copy-Item $ExampleSettings $AppSettings + Write-Ok "Created appsettings.json from appsettings.example.json - edit VM path & credentials." + Add-Result 'appsettings.json' 'OK' 'created from example' +} else { + Write-Warn "appsettings.example.json not found; cannot create appsettings.json." + Add-Result 'appsettings.json' 'WARN' 'no example to copy' +} + +# --- Guest-side reminder ----------------------------------------------------- +Write-Head "Guest-VM components (must live INSIDE the VM - cannot be bundled here)" +Write-Info "frida-server.exe -> C:\Tools\frida-server.exe (staged in .\guest-tools if step 4 succeeded)" +Write-Info "dbgsrv.exe (+dbgeng.dll, dbghelp.dll) -> C:\Tools\DbgSrv\ (from the WDK / WinDbg Preview)" +Write-Info "TTD.exe -> C:\Tools\TTD\TTD.exe (from WinDbg Preview or the WDK)" +Write-Info "Transfer them with guest_transfer_to_vm once the VM is running." + +# --- Summary ----------------------------------------------------------------- +Write-Head "Summary" +$script:Report | Format-Table -AutoSize Component, Status, Detail | Out-Host + +$fails = @($script:Report | Where-Object { $_.Status -eq 'FAIL' }) +$warns = @($script:Report | Where-Object { $_.Status -eq 'WARN' }) +if ($fails.Count -gt 0) { + Write-Host "$($fails.Count) blocking issue(s). The server needs .NET 8 + a successful build at minimum." -ForegroundColor Red + exit 1 +} elseif ($warns.Count -gt 0) { + Write-Host "Core server is ready. $($warns.Count) optional component(s) missing - see WARN rows above." -ForegroundColor Yellow + exit 0 +} else { + Write-Host "All checks passed." -ForegroundColor Green + exit 0 +}