Files
reverse-skills-max/our-skills/reverse-winui3-app/SKILL.md

5.1 KiB

name, description
name description
reverse-winui3-app Use when reverse engineering a WinUI 3 / .NET 8 MSIX-packaged Windows application — covers ILSpy decompilation, PRI/XBF extraction, and pkg-compiled Node.js backend identification

Reverse Engineering WinUI 3 + pkg-compiled Node.js Applications

When to use

  • Target is a Windows MSIX-packaged app (WinUI 3 / Windows App SDK)
  • App has a separate backend executable compiled with pkg
  • Need to extract C# source, XAML (XBF), and JS backend code

Phase 1: Locate and identify the target

# Find running process
Get-Process <name> | Select Id,Path
# Or search MSIX install
Get-ChildItem "C:\Program Files\WindowsApps" -Directory -Filter "*<name>*"

Identify app type by inspecting install directory:

  • coreclr.dll, hostfxr.dll, *.dll (managed) → .NET / WinUI 3
  • VibeProxy.exe (small, ~400KB) = native launcher; VibeProxy.dll (large) = main logic
  • Watchers\*.exe (large, 100MB+) = likely pkg-compiled Node.js backend

Phase 2: Decompile .NET frontend (ILSpy)

# Install ILSpy CLI
dotnet tool install --global ilspycmd

# Decompile with BAML support and reference path
ilspycmd "target.dll" -p -o "output_dir" --decompile-baml -r "install_dir"

# Fix csproj: LangVersion=preview, add HintPath for all references
# Copy dependency DLLs to libs/ folder (include large ones >15MB separately)
# Fix CS0136 (duplicate variable names) by renaming
# Build: dotnet build -c Release

Key limitations:

  • WinUI 3 XAML compiles to XBF (not BAML) — --decompile-baml won't extract it
  • Unknown result type comments in 60+ files = WinRT projection IL artifacts
  • Smoke-launch fails: WinRT activation requires MSIX package identity

Phase 3: Extract XBF from resources.pri

# Find makepri.exe in Windows SDK
# Dump PRI to XML (detailed format for Base64 data)
makepri.exe dump /if "install_dir\resources.pri" /of "dump.xml" /dt detailed /o
# Extract XBF binaries from PRI dump XML
[xml]$xml = Get-Content "dump.xml" -Raw
$xbfNodes = $xml.SelectNodes("//NamedResource[contains(@name,'.xbf')]")
foreach ($node in $xbfNodes) {
    $name = $node.GetAttribute("name")
    $uri = $node.GetAttribute("uri")
    $pathPart = $uri -replace "ms-resource://[^/]+/Files/", ""
    $candidate = $node.SelectSingleNode("Candidate")
    if ($candidate.GetAttribute("type") -eq "EmbeddedData") {
        $b64 = $candidate.SelectSingleNode("Base64Value")
        $bytes = [System.Convert]::FromBase64String($b64.InnerText)
        [System.IO.File]::WriteAllBytes($outPath, $bytes)
    }
}

XBF v2.1 format notes:

  • Header: magic(4) + metaSize(uint32) + nodeSize(uint32) = 12 bytes
  • Metadata at byte 12: version(2x uint32) + 6 table offsets (uint64, relative to byte 12) + hash (64 bytes)
  • Table entries are 12 bytes each (not 8 as in v1.0)
  • Node encoding differs from v1.0 — no existing tool supports v2.1
  • Best path to XAML: runtime-hook LoadComponent in live app, serialize visual tree

Phase 4: Identify and extract pkg-compiled backend

Identify pkg binary

PE header: NOT .NET (no CLR header)
Search for: "process.versions.pkg", "pkg/prelude", "/snapshot/"
NODE_SEA_FUSE:0 = disabled (stock Node, not SEA)
pkg version found in binary (e.g., '6.22.0')

Extract via runtime unpacking

pkg extracts itself to %TEMP%\<ServiceName>\<package-name>\<version>\dist at runtime. Check if the app has been run:

Get-ChildItem "$env:TEMP" -Directory -Filter "*<ServiceName>*"
Get-ChildItem "$env:TEMP\*<ServiceName>*\*" -Recurse

If found, copy the dist directory — it contains:

  • server.js / server-ws.mjs (entry points, readable)
  • .build/next/ (Next.js standalone build)
  • .build/next/server/chunks/ (minified but readable JS, 3000+ files)
  • .build/next/server/app/api/ (API routes)
  • package.json (identifies upstream package + version)

Match with upstream

Check package.json name + version against GitHub:

  • If MIT/Apache licensed → full source available upstream
  • Clone the repo for complete TypeScript source (2,600+ .ts/.tsx files)
  • Compare extracted dist with git clone to find local modifications

Phase 5: Full inventory checklist

Item Source Method
C# source VibeProxy.dll ILSpy decompile
XBF (compiled XAML) resources.pri makepri dump + extract
Assets/icons MSIX install dir Copy
Locales MSIX install dir Copy (check all dirs)
config.yaml Watchers/ Copy
Backend dist %TEMP% (runtime) Copy after app run
Backend source GitHub (if MIT) git clone
Build verification dotnet build Must be GREEN
Sourcemaps .build/next/ Usually absent in prod

Common pitfalls

  • Don't claim "full source" without building and smoke-testing
  • XBF v2.1 ≠ BAML — --decompile-baml produces nothing for WinUI 3
  • SEA fuse :0 means stock Node.js, not SEA — look for process.versions.pkg instead
  • 210MB gzip trailing data in PE = pkg payload, not SEA blob
  • Sourcemaps in production Next.js builds are typically empty ({"sources":[],"sections":[]})
  • WinUI 3 apps cannot launch standalone without MSIX registration