Thick Client Pentest Checklist

The thick-client (desktop app) checklist turned into a how-to-test field guide for .NET, Java, native and Electron apps: architecture and recon, static binary analysis, insecure local storage, traffic interception (proxy-aware and non-proxy-aware protocols), client-side auth/authz, injection to the backend, DLL hijacking, memory analysis, IPC and GUI manipulation — each with the scenario, the real tool (dnSpy, JADX, ProcMon, Echo Mirage/mitm_relay, Frida, Wireshark), the steps, the finding, and

LazyHackers.in — Checklist

🖥️ Thick Client Pentest Checklist

Desktop apps (.NET / Java / native / Electron): scenario · tool · steps · the finding · the fix

☰   How to use this guide

A thick client runs logic on the user's machine, so the threat model is different from web: the attacker owns the binary, the memory and the local files. Two architectures matter — 2-tier (client talks straight to the database) and 3-tier (client → app server → DB). 2-tier is the dangerous one: decompile the client, recover the DB connection string, and you may have direct database access. This guide turns every item into how-to-test for .NET, Java, native and Electron apps; the backend it talks to is still an API/DB, so pair with the API checklist.

Work four streams: static (decompile/strings), storage (registry/files), traffic (proxy + non-proxy protocols), and runtime (memory/hooking/GUI). Each section ends with a coverage table.
Test your own build or a scoped engagement on a VM you control. Decompilation and instrumentation are for authorised assessment only.
# Identify the tech stack first (drives the toolset)
file App.exe ; strings -n 8 App.exe | grep -iE 'mscorlib|java|electron|Qt|connectionstring'
#  .NET  -> dnSpy / ILSpy / dotPeek (+ de4dot if obfuscated)
#  Java  -> JD-GUI / JADX / procyon ;  native -> Ghidra / IDA / x64dbg
#  Electron -> npx asar extract app.asar out/  (then it is just JS)

0   Architecture & recon

Determine the architecture and tech stack, and map where the client talks to (DB directly? app server? cloud?).

# Watch what the app opens and connects to at launch
procmon.exe                       # filter: Process Name is App.exe (file/registry/network)
# Active connections + listening ports
netstat -anob | findstr App.exe
TCPView.exe
⚑ Report as: “Two-tier architecture — client connects directly to the database”
🛡 Fix: Prefer a 3-tier design (client → app server → DB) so the client never holds DB credentials or trust; never expose the database directly to clients; treat the client as fully untrusted.

Architecture & recon — full coverage

Checklist itemHow to testReport as
Tech stack identified (.NET/Java/native/Electron)file + stringsStack identified
Architecture (2-tier vs 3-tier)procmon/netstatArchitecture mapped
Endpoints/DB/services it connects toTCPView/WiresharkConnectivity mapped
Files/registry touched at runtimeProcMonLocal footprint mapped
Installer/update mechanism reviewedinspect installer/updaterUpdate mechanism reviewed

1   Static binary analysis

Decompile and read. Hardcoded secrets, connection strings, API keys and crypto material fall straight out — especially in managed (.NET/Java) and Electron apps.

# .NET: decompile + search the whole assembly
dnSpy App.exe                     # (de4dot App.exe first if obfuscated)
# Java: JADX over the jar
jadx -d out App.jar
# Electron: extract and grep the JS
npx asar extract resources/app.asar app_src && grep -rEi 'password|apikey|secret|http' app_src
# Quick wins from raw strings
strings -n 8 App.exe | grep -iE 'connectionstring|server=|pwd=|password=|api[_-]?key|BEGIN .*KEY'
⚑ Report as: “Hardcoded database connection string / secret recovered from the client binary”
🛡 Fix: Never ship secrets/connection strings/keys in the client; fetch per-session credentials from the server after authentication; if managed code, obfuscation slows but doesn't stop recovery — don't rely on it; keep crypto keys server-side.

Static binary analysis — full coverage

Checklist itemHow to testReport as
Hardcoded connection stringdnSpy/stringsHardcoded connection string
Hardcoded secrets/API keys/credsdecompile + grepHardcoded secret
Hardcoded crypto keys/IV/saltdecompileHardcoded crypto material
Sensitive logic in client (should be server)review decompiledSensitive logic on client
No/weak obfuscation (managed code)decompile clarityNo code obfuscation
Debug strings / internal endpointsstringsDebug info in binary
Binary not signed / signature not checkedsigntool verifyUnsigned/unverified binary
Vulnerable bundled library (CVE)inventory libsVulnerable dependency

2   Local data storage

Thick clients scatter data across the registry, config files, app-data and DPAPI. Exercise the app, then inspect what it wrote.

# Diff the registry + filesystem around an action (e.g. login)
regshot          # 1st shot -> do the action -> 2nd shot -> compare
# Where data lands
%APPDATA% %LOCALAPPDATA% ProgramData ; *.config / *.ini / *.xml / *.sqlite
# DPAPI-protected blobs (decrypt in the user's context)
# (mimikatz dpapi::* or SharpDPAPI in a lab) — confirms reversible local "protection"
⚑ Report as: “Sensitive data stored insecurely locally (plaintext config / registry / weakly-protected)”
🛡 Fix: Don't store secrets/credentials/PII on the client; if unavoidable, use OS-protected stores (DPAPI per-user, Credential Manager) and understand they're reversible in the user's context; clear sensitive data on logout; never store passwords recoverably.

Local storage — full coverage

Checklist itemHow to testReport as
Sensitive data in config files (plaintext)inspect *.config/.iniPlaintext config storage
Sensitive data in registryregshot/regeditSensitive data in registry
Sensitive data in local DB/filesinspect appdataInsecure local storage
Credentials cached, not cleared on logoutlogout + re-inspectCredentials persist
Weak/reversible local protection (DPAPI/XOR)analyse protectionReversible local protection
Sensitive data in logs/tempinspect logs/tempSensitive data in logs
Insecure file permissions on app dataicaclsWeak file permissions

3   Traffic interception

Half of thick clients aren't proxy-aware and many use raw TCP/TLS rather than HTTP. The test is can you see and modify the traffic — via a proxy, a forced proxy, or a TCP interceptor.

  1. HTTP + proxy-aware: point the app at Burp (system proxy / config) and read the traffic.
  2. HTTP but not proxy-aware: force it through Burp with Proxifier or an invisible/transparent proxy + hosts redirect.
  3. Non-HTTP (raw TCP/TLS): use a TCP interceptor (Echo Mirage, mitm_relay) or Wireshark to capture; modify in the relay.
  4. Check TLS: no validation / accepts self-signed / no pinning means trivial MITM.
# Force a non-proxy-aware app through Burp
#   Proxifier rule: App.exe -> 127.0.0.1:8080   (Burp invisible proxy + hostname mapping)
# Raw TCP/TLS interception
mitm_relay.py -l 0.0.0.0 -p 8080 -r tcp:9999:realserver:9999   # relay + Burp
# or Echo Mirage to hook send/recv; Wireshark to confirm cleartext
⚑ Report as: “Sensitive data over cleartext / no TLS validation / no certificate pinning (MITM)”
🛡 Fix: TLS for all traffic with proper certificate validation; pin where feasible; never accept self-signed/invalid certs; don't send sensitive data in the clear; treat all client→server input as untrusted on the server.

Traffic interception — full coverage

Checklist itemHow to testReport as
Cleartext trafficWiresharkCleartext transmission
Not proxy-aware (forced proxy needed)Proxifier + BurpTraffic interceptable
Raw TCP/TLS protocolEcho Mirage/mitm_relayNon-HTTP traffic interception
No TLS validation / accepts self-signedpresent bad certBroken TLS validation
No certificate pinningMITM succeedsNo certificate pinning
Weak TLS version/cipherinspect handshakeWeak TLS
Sensitive data in requests/URLsinspect trafficSensitive data in transit

4   Client-side auth/authz & GUI manipulation

Thick clients love to enforce auth/roles in the UI. Anything the client decides can be flipped — enable disabled controls, edit the response, or patch the check.

# Re-enable disabled buttons / unhide admin controls
#   Window finder tools (e.g. ManageEngine/UISpy/Window Detective) -> toggle Enabled/Visible
# Patch a client-side check in .NET (dnSpy: edit IL/C#, e.g. isAdmin() -> return true)
#   dnSpy -> Edit Method -> save module
# Or tamper the server response in Burp:  {"role":"user"} -> {"role":"admin"}
⚑ Report as: “Authorization enforced client-side only — disabled control / patched check grants access”
🛡 Fix: Enforce all authentication and authorization on the server for every action (the client is untrusted); never gate privileges on a UI flag or a client-side boolean; re-validate every request server-side from the session/role.

Client-side auth/authz & GUI — full coverage

Checklist itemHow to testReport as
AuthZ enforced client-side onlypatch check / tamper responseClient-side authorisation
Disabled/hidden controls re-enableableWindow finderClient-side control bypass
Client-side check patchablednSpy editPatchable security check
Response tamper grants accessflip in BurpResponse-tampering bypass
No server-side re-validationcall backend directlyMissing server-side validation
Hardcoded/backdoor credentialsdecompileBackdoor credential

5   Injection to the backend

In 2-tier apps the client builds SQL directly — SQLi hits the DB straight away. In 3-tier, the same injection classes apply to the app-server protocol. Intercept and inject.

# 2-tier: capture the client's DB traffic, then inject in the query path
#  (e.g. search box ->  ' OR 1=1--   ; or sqlmap against the captured request)
sqlmap -r captured-request.txt -p param --batch
# Direct DB access with the recovered connection string (2-tier):
#  mssqlclient.py / sqlcmd with the creds pulled in S1 -> full DB if over-privileged
⚑ Report as: “SQL injection to backend database via thick-client input (2-tier direct DB)”
🛡 Fix: Parameterise all queries server-side; in 2-tier apps, move to 3-tier so clients never hold DB creds or run raw SQL; least-privilege DB accounts; validate input on the server.

Injection to backend — full coverage

Checklist itemHow to testReport as
SQL injection (direct DB / via server)intercept + sqlmapSQL injection
Direct DB access via recovered credsconnect with credsDirect database access
Over-privileged DB accountreview grantsExcessive DB privileges
Command injectioninject in paramsOS command injection
Other injection (LDAP/XPath/etc.)intercept + injectInjection
No server-side input validationsend raw requestMissing input validation

6   DLL hijacking & binary planting

If the app loads DLLs from a writable or search-order location, a planted DLL runs in its context — local privilege escalation or persistence.

# Find DLLs loaded from missing/writable paths
procmon.exe   # filter: Result is NAME NOT FOUND, Path ends with .dll
# Check writable app/install dirs
icacls "C:\Program Files\App"   # look for (M)/(F) for Users/Authenticated Users
⚑ Report as: “DLL hijacking / insecure library loading from a writable path”
🛡 Fix: Load DLLs from absolute, protected paths; apply safe DLL search order; restrict write access to install/app directories; sign and verify loaded modules; don't run from user-writable locations.

DLL hijacking & binary planting — full coverage

Checklist itemHow to testReport as
DLL loaded from missing/writable pathProcMon NAME NOT FOUNDDLL hijacking
Writable install/app directoryicaclsInsecure directory permissions
Unsafe DLL search orderreview loadingUnsafe library loading
Loaded modules unsigned/unverifiedcheck signaturesUnverified module loading
Service binary path writable/unquotedreview serviceService path hijack

7   Memory & runtime analysis

Sensitive data lingers in process memory; runtime hooking can change outcomes. Dump and search, then hook.

# Search process memory for secrets
#  Process Hacker -> right-click process -> Memory -> search for "password"/token
# Hook a method at runtime to flip an outcome
frida -p <pid> -l hook.js          # e.g. override IsLicensed()/IsAdmin() to return true
⚑ Report as: “Sensitive data recoverable from process memory / runtime hooking changes outcome”
🛡 Fix: Minimise sensitive data lifetime in memory (zero buffers after use, SecureString where applicable); never trust the client for security decisions — enforce server-side; treat hooking/patching as expected.

Memory & runtime — full coverage

Checklist itemHow to testReport as
Secrets readable in memoryProcess Hacker searchSensitive data in memory
Runtime hooking changes outcomeFrida hookClient-side decision bypass
Local data tamper alters logicedit in memory/fileClient data tampering
No anti-debug/anti-tamper (context)attach debuggerNo runtime protection

8   IPC & privilege issues

Local IPC (named pipes, COM, sockets, WCF) and privileged helper services are an attack surface for local privilege escalation.

⚑ Report as: “Insecure local IPC / privileged helper service abusable for local privesc”
🛡 Fix: Authenticate and authorise IPC endpoints; restrict named-pipe/COM ACLs; validate input to privileged services; run with least privilege; don't expose admin functions over unauthenticated local IPC.

IPC & privileges — full coverage

Checklist itemHow to testReport as
Insecure named pipe / COM / socket IPCenumerate + connectInsecure IPC
Privileged helper service abusablereview servicePrivileged-service abuse
IPC input not validatedinject via IPCIPC injection
App runs with excessive privilegecheck tokenExcessive privilege
Auto-update lacks integrity checkMITM updateInsecure update mechanism
Electron-specific checks

E   Electron-specific

Electron apps are Chromium + Node — extract the asar and you have the JS. The killer combos are nodeIntegration on with remote/untrusted content, and XSS becoming RCE through the Node bridge.

npx asar extract resources/app.asar app_src
grep -rEi 'nodeIntegration|contextIsolation|webSecurity|allowRunningInsecureContent|shell\.openExternal' app_src
# Open devtools if enabled; check for loadURL of remote/untrusted content
⚑ Report as: “Electron: nodeIntegration enabled with untrusted content (XSS → RCE)”
🛡 Fix: Disable nodeIntegration; enable contextIsolation and sandbox; keep webSecurity on; validate/allowlist navigation and shell.openExternal; never load untrusted remote content with Node access; ship asar integrity/signing.

Electron — full coverage

Checklist itemHow to testReport as
nodeIntegration enabledgrep confignodeIntegration enabled
contextIsolation disabledgrep configContext isolation off
webSecurity disabledgrep configwebSecurity disabled
Loads untrusted remote contentreview loadURLUntrusted content loaded
XSS → RCE via Node bridgeinject + test bridgeXSS to RCE
Unrestricted shell.openExternalreview usageCommand/URL execution
asar not integrity-checkedtamper asarNo asar integrity
DevTools enabled in productionopen devtoolsDevTools in production

✓   Coverage map & how to run it

Run all universal sections; add the Electron block for Electron apps. The biggest wins are 2-tier connection-string recovery (§1), client-side auth bypass (§4), and non-proxy traffic interception (§3).

SectionRun onFocus
Universal 0–8Every thick clientArchitecture, static, storage, traffic, auth/GUI, injection, DLL, memory, IPC
ElectronElectron appsnodeIntegration, context isolation, XSS→RCE, asar

Core principle: the client is fully attacker-controlled — binary, memory and local files. Recover what it hides, intercept what it sends (proxy-aware or not), and prove that every security decision is re-enforced on the server. The backend it talks to is still an API/DB — pair with the API checklist. Tick a box only when you've actually run the test.

Reactions

Related Articles