5.1 KiB
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 3VibeProxy.exe(small, ~400KB) = native launcher;VibeProxy.dll(large) = main logicWatchers\*.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-bamlwon't extract it Unknown result typecomments 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
LoadComponentin 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-bamlproduces nothing for WinUI 3 - SEA fuse
:0means stock Node.js, not SEA — look forprocess.versions.pkginstead - 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