Reconnaissance & Unpacking¶
Before you can attack a desktop application you have to know what it is, how it is built, and where its logic actually lives. Thick clients ship their business logic to the endpoint, which means the bundle on disk is a decompiler's buffet: framework runtimes, packed source, embedded configuration, hardcoded endpoints, and secrets that a web app would have kept server-side. This chapter is about that first pass over an installed application: identifying the framework, prying the container open, and recovering readable code and config from whatever archive, bytecode, or native binary the vendor shipped.
Techniques covered:
- Framework fingerprinting (Electron / Tauri / CEF / WebView2 / .NET / Java / native)
- Unpacking Electron (
asar,app.asarvsapp.asar.unpacked, main/renderer/preload) - Decompiling .NET assemblies
- Decompiling Java bytecode
- Reversing native binaries (Ghidra / IDA / radare2 / DIE)
- Extracting embedded resources, config, and secrets
- Installer inspection (MSI / NSIS / Squirrel)
Framework Fingerprinting¶
- What & why: Every subsequent tool choice depends on identifying the runtime. A
.NETapp wants ILSpy; an Electron app wantsasar; a Qt binary wants Ghidra. Vendors rarely advertise the framework, but the install tree, imported libraries, and embedded strings do. Fingerprint first so you are not running a Java decompiler against a Go binary. - How to test:
- Start with the install directory layout and file inventory. On Windows the default roots are
%LOCALAPPDATA%\Programs\,%ProgramFiles%,%ProgramFiles(x86)%, and per-user%LOCALAPPDATA%. On macOS inspect*.app/Contents/; on Linux check/opt,/usr/lib, and AppImage mounts. - Enumerate the tree and look for signature files:
- Inspect the main executable's imports and strings:
- Use Detect It Easy (DIE) for a one-shot classification of packer, compiler, and framework:
- Framework notes (the tells):
- Electron:
resources/app.asar(orresources/app/), a bundledchrome_100_percent.pak,icudtl.dat,v8_context_snapshot.bin,ffmpeg.dll/libffmpeg,LICENSES.chromium.html, and an executable that is really a renamed Electron stub.stringsshowsnode_modules,Electron Framework, Node/Chromium version banners. - Tauri: a single relatively small native binary (Rust) with no bundled Chromium. On Windows it depends on
WebView2Loader.dlland the Edge WebView2 runtime; on Linux it linkslibwebkit2gtk.stringsshow Rust panic messages,cargo,tao/wrysymbols, and noapp.asar. - CEF (Chromium Embedded Framework):
libcef.dll/libcef.so/Chromium Embedded Framework.framework,cef.pak,cef_100_percent.pak,devtools_resources.pak. Distinct from Electron: no Node runtime and noapp.asar. - WebView2: dependence on
Microsoft.Web.WebView2.Core.dlland the Evergreen runtime (msedgewebview2.exechild process). Often a .NET or C++ host, check the host binary separately. - .NET: presence of
*.dllmanaged assemblies,*.deps.json,*.runtimeconfig.json(.NET Core/5+), or amscoree.dllimport (.NET Framework). Self-contained apps shipcoreclr.dll,clrjit.dll,hostfxr.dll,hostpolicy.dll. Single-file publishes are a nativeapphostwith the assemblies embedded. - Java:
*.jar/*.war, a bundledjre/jdk/runtimedirectory,.jnlp, or a native launcher plusapp/*.cfg(jpackage/javafx).stringsshowMETA-INF/MANIFEST.MF, class names,JavaVM. - Native (C/C++/Qt/Delphi): no managed runtime files. Qt shows
Qt5Core.dll/Qt6Core,qt.conf,platforms/qwindows.dll. Delphi/C++Builder binaries carryBorland/Embarcadero/DelphiRTTI strings and DFM form resources. Go binaries carry thego:buildidstring andruntime.symbols. - Impact: None directly; this is orientation. But misidentifying the framework wastes the whole engagement.
- Remediation: Fingerprinting cannot be prevented and is not a vulnerability. Vendors should assume the framework is known and not rely on obscurity of the runtime.
Unpacking Electron (asar)¶
- What & why: Electron apps package the entire application (main process, renderer, preload scripts,
node_modules) into anasararchive: a simple, unencrypted, concatenated file format with a JSON header. This is source code, not compiled bytecode. Extracting it typically yields the complete JavaScript logic, IPC channel names,nodeIntegration/contextIsolationsettings, and any secrets left in the bundle. - How to test:
- Locate the archive (usually
resources/app.asar; sometimes an unpackedresources/app/directory instead). - Extract with the official
@electron/asartool (formerlyasar): - No Node available? The format is trivial: the header is a JSON pickle at the start of the file.
strings resources/app.asar | lesswill already surface file paths, URLs, and inline secrets even without a full extract. - After extraction, read
package.jsonformain(the main-process entrypoint), then traceBrowserWindowcreation to findwebPreferencesandpreloadpaths. - Grep the tree for the security-relevant flags and IPC surface:
- Framework notes:
app.asarvsapp.asar.unpacked: files that must exist on the real filesystem (native.nodeaddons, binaries spawned by the app) are excluded from the archive and shipped inresources/app.asar.unpacked/. Always inspect both; native modules there may themselves be reverse-engineering targets.- Fuses / integrity: newer Electron builds may enable ASAR integrity checking and the
RunAsNode/EnableNodeCliInspectArgumentsfuses. Integrity is a hash check on the archive; you can still read it, you just cannot trivially repack-and-run without matching the header hash. Checkstringson the main binary for the fuse wire to read fuse state. - Minified/bundled renderers: many apps ship webpack/vite output. Use
js-beautifyand any shipped.mapsourcemaps (grep -rl sourceMappingURL) to recover readable source. - This technique is Electron-specific. Tauri does not use asar; its web assets are compiled into the Rust binary (see native reversing) or served from an embedded store.
- Impact: Full recovery of client-side logic; discovery of hardcoded API keys, backend endpoints, unsafe IPC handlers, and misconfigured
webPreferencesthat enable RCE from renderer content. - Remediation: Do not treat asar as a security boundary; it is not encrypted. Keep secrets server-side, enable
contextIsolationandsandbox, disablenodeIntegration, enable ASAR integrity and relevant Electron fuses, and strip sourcemaps from production builds.
Decompiling .NET Assemblies¶
- What & why: .NET compiles to CIL (MSIL) bytecode carrying rich metadata, so decompilation reconstructs near-original C#/VB source including method bodies, resource names, and string literals. This is one of the highest-yield thick-client targets: config, connection strings, license logic, and crypto keys are frequently recoverable in full.
- How to test:
- Confirm it is managed and read the metadata:
- GUI decompilers give the best code recovery: ILSpy (open source, cross-platform, plus the AvaloniaILSpy build), dnSpy / dnSpyEx (decompile and live-debug/edit-and-continue on Windows), and JetBrains dotPeek.
- For obfuscated assemblies, identify the obfuscator with DIE or de4dot's detection, and attempt cleanup with de4dot before decompiling:
- Hunt strings and resources across all assemblies:
- Framework notes:
- .NET Framework vs Core/5+: Framework assemblies import
mscoree.dll; Core apps ship*.deps.json/*.runtimeconfig.json. Both decompile identically. - Single-file publish: the assemblies are bundled into the native
apphost. Extract them first with a bundle extractor or dump from memory once running, then decompile the recovered DLLs. - ReadyToRun / AOT: R2R images still contain the IL; Native AOT compiles straight to native code and must be treated as a native binary (Ghidra/IDA), not decompiled to C#.
- Embedded resources: managed resources live in
.resourcesstreams; ILSpy/dnSpy expose them, or useresourcer/manualResourceReaderparsing. - Impact: Recovery of full application logic, hardcoded credentials and connection strings, license/auth bypass insight, and editable assemblies (dnSpy) for runtime tampering during dynamic testing.
- Remediation: Never store secrets in managed strings or resources; use a real secret store and server-side validation. Commercial obfuscation (control-flow flattening, string encryption) raises cost but does not stop a determined analyst; treat it as speed-bump, not defense.
Decompiling Java Bytecode¶
- What & why: Like .NET, Java compiles to metadata-rich bytecode that decompiles cleanly back to source. Thick-client Java (Swing/JavaFX/SWT) ships as JARs, often inside a bundled JRE, and commonly contains embedded config, endpoints, and keystores.
- How to test:
- A JAR is a ZIP; list and extract it:
- Decompile with a real decompiler. CFR and Procyon are strong command-line choices; JD-GUI and the Fernflower-based tools give a GUI:
- Bulk workflows: jadx handles JARs (and DEX for Android-style bundles) with
jadx app.jar -d out/andjadx-gui. - Inspect a single class or the constant pool without full decompilation:
- Framework notes:
- jpackage / Java-native launchers: apps built with
jpackageship a native launcher plusapp/*.jarand anapp/*.cfgpointing at the main module. Reverse the launcher only to find the JARs; the JARs are the real target. - Fat/shaded JARs and Spring Boot: dependencies are nested; extract fully and decompile the vendor packages, not the framework libraries.
- Obfuscation (ProGuard): identifiers are stripped/renamed; decompilation still succeeds but naming is lost. Focus on string constants and control flow.
- Keystores: look for
.jks/.p12/.keystorein the bundle and inspect withkeytool -list -v -keystore file.jks. - Impact: Full source recovery, exposed endpoints and credentials, insight into client-side auth/licensing, and extractable signing/TLS keys.
- Remediation: Keep secrets out of the bundle, use server-side authorization, and apply obfuscation as a cost-raiser only. Do not ship production keystores with recoverable passwords.
Reversing Native Binaries¶
- What & why: C/C++/Rust/Go/Delphi/Qt binaries have no metadata-rich bytecode, so you get assembly and a decompiler's best-effort pseudocode rather than source. Tauri's web assets, native Electron addons (
.node), and traditional desktop apps all land here. Yield is lower and slower than managed decompilation but still exposes strings, endpoints, embedded resources, and logic. - How to test:
- Triage before deep reversing:
- Full analysis in a decompiler: Ghidra (free, excellent decompiler, batch via
analyzeHeadless), IDA Pro/Free, Binary Ninja, or radare2/rizin + Cutter: - Check for packing (UPX and others) and unpack before analysis:
- Framework notes:
- Tauri: the Rust binary embeds the frontend web assets and command handlers.
stringsand the decompiler reveal registeredtauri::commandnames (the IPC attack surface) and any embedded HTML/JS. Rust symbols are verbose but present unless stripped. - Go: stripped Go binaries still recover function names via the
pclntab; use Ghidra scripts orGoReSymto restore symbols. - Qt: signals/slots and translation strings help map UI to logic; embedded
.qml/resources sit in the Qt Resource System (see next section). - Delphi/C++Builder: use RTTI-aware helpers (e.g. IDR / Ghidra Delphi scripts) to recover class/form structure and DFM resources.
- Native Electron addons:
.nodefiles inapp.asar.unpackedare standard shared objects; reverse them as native binaries. - Impact: Recovery of endpoints, embedded assets, algorithm/crypto logic, and IPC command surfaces; identification of memory-safety bugs for deeper exploitation.
- Remediation: Strip symbols, but understand reversing is not preventable. Move trust decisions server-side, validate all IPC/command inputs, and do not embed secrets. Anti-tamper/packing raises cost only.
Extracting Embedded Resources, Config, and Secrets¶
- What & why: Independent of the language, desktop apps carry configuration and assets in the bundle:
.env-style files, JSON/YAML/XML config, license files, TLS keys, certificate pins, feature flags, and hardcoded backend URLs. This is often the fastest path to impact and should be run in parallel with decompilation. - How to test:
- Broad content sweep across the extracted/installed tree:
- Catalog config and credential-bearing file types:
- .NET: read
App.config/*.dll.config,appsettings.json, and embedded.resources; checksettingsclasses in the decompiled source for defaults. - Qt Resource System: resources compiled into the binary can be carved; look for the
qres/.rccmarkers, or extract.rccfiles with Qt'srcc-aware tooling. - Windows PE resources: dump icons, manifests, version info, and embedded blobs with Resource Hacker or
wrestool -x app.exe. - Run a dedicated secret scanner over the whole tree for coverage and entropy detection:
- Framework notes: Electron/Java configs are usually plaintext files in the archive; native/Qt configs may be compiled into the binary and must be carved; .NET keeps them in
.config/appsettings/managed resources. Some apps "encrypt" config with a key that is itself in the binary, recover the key via decompilation, then decrypt. - Impact: Exposure of backend endpoints (expands the network attack surface), live API keys and credentials, private keys, and pinning/feature-flag logic that can be bypassed.
- Remediation: No secret should live in a distributed binary. Use server-side auth, short-lived tokens fetched at runtime, and OS keystores (DPAPI, Keychain, libsecret) rather than files. Assume anything in the bundle is public.
Installer Inspection (MSI / NSIS / Squirrel)¶
- What & why: Installers ship the same payload you will unpack, and they also encode install-time behavior: custom actions, elevated services, registry keys, scheduled tasks, and update URLs. Inspecting the installer both recovers the payload without running it and surfaces privilege-escalation and supply-chain issues (writable install paths, unsigned updates).
- How to test:
- MSI (Windows Installer): MSI is a structured-storage database of tables. Read it without installing:
On Windows use Orca (MS Windows SDK) or lessmsi (
# Linux/macOS msiinfo tables installer.msi msiinfo export installer.msi CustomAction # inspect custom actions msiextract installer.msi # extract payload files 7z l installer.msi # 7-Zip also lists/extracts CABslessmsi x installer.msi) to browse tables and dump files. Scrutinize theCustomAction,InstallExecuteSequence,ServiceInstall, andRegistrytables for deferred actions running as SYSTEM. - NSIS: self-extracting; 7-Zip can list and extract the packaged files, and the script logic is recoverable:
For the install script itself, use a maintained NSIS extractor where available; otherwise inspect
stringsfor install paths and download URLs. - Squirrel (used by many Electron apps): the release is a set of NuGet-format
.nupkgfiles plus aRELEASESmanifest, delivered from an update feed..nupkgis a ZIP:Theunzip -l MyApp-1.2.3-full.nupkg unzip MyApp-1.2.3-full.nupkg -d squirrel_out/ cat RELEASES # SHA1 + filename + size of each packagelib/net45/(or app) folder holds the actual Electron/app.asarpayload. Note the update URL from the app config; unauthenticated/HTTP feeds are a supply-chain finding. - macOS
.pkg/.dmg: expand flat packages withpkgutil --expand-full app.pkg out/and readScripts/preinstall/postinstall; mount DMGs and inspect the.appbundle. - Framework notes: Squirrel is the Electron-world default and directly exposes the asar payload and update channel. MSI is common for .NET and native enterprise apps and is where SYSTEM-level custom actions live. NSIS wraps everything from games to native apps. Tauri uses its own bundler producing MSI/NSIS on Windows and
.dmg/.appon macOS, so the same tooling applies. - Impact: Payload recovery without execution; discovery of privilege-escalation vectors (weak install-dir ACLs, SYSTEM custom actions, unquoted service paths) and insecure/unsigned update channels enabling supply-chain compromise.
- Remediation: Sign installers and every update package, serve update feeds over HTTPS with signature verification, install to protected directories with correct ACLs, minimize and audit elevated custom actions, and never place secrets or writable service binaries in user-writable locations.