131 lines
5.1 KiB
Markdown
131 lines
5.1 KiB
Markdown
---
|
|
name: reverse-winui3-app
|
|
description: "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
|
|
|
|
```powershell
|
|
# 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)
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
```powershell
|
|
# 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:
|
|
```powershell
|
|
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
|