Windows thick-client / MSIX security auditing

TCPK -- Thick Client Pentest Kit

Evidence over assertion. A scanner that proves its findings.

TCPK is a portable, dependency-light PowerShell engine for auditing Windows desktop applications -- MSIX, MSI, ClickOnce, and portable apps; managed .NET, native PE, and Electron alike. It is built around one opinion: a string match is a lead, not a finding. Every dangerous-API hit is lifted to IL with Mono.Cecil and run through a bounded source-to-sink taint pass before it is allowed to call itself "Confirmed", every score is the real FIRST.org CVSS v4.0 number derived from an attack-archetype vector, and every finding carries CWE, MITRE ATT&CK, and OWASP TASVS provenance.

v1.8.0 178 cmdlets PowerShell 5.1+ WinForms GUI + MCP server CVSS v4.0 (computed) OWASP TASVS v1.8 CycloneDX SBOM No external runtime
TCPK GUI: target selection, live audit progress, findings table, and tabs for Recon, Exploit/CVE, SBOM, DLL Mitigation Matrix, DLL Signing, and Logs.
The WinForms console: live per-check progress, streaming findings, and posture tabs (Recon, SBOM, DLL Mitigation Matrix, DLL Signing). The same engine drives the CLI and the MCP server.

00TL;DR

Point TCPK at an installed app or an .msix. It collects findings from ~130 checks across a dozen buckets, kills the false positives deterministically by reading the IL, scores what survives with a faithful CVSS v4.0 implementation, correlates co-occurring weaknesses into exploit chains, and writes an analyst-grade HTML + Excel report plus machine-readable JSON and a CycloneDX SBOM.

Import-Module .\TCPK\TCPK.psd1

# full static + posture audit, reports to .\out\App
Invoke-TcpkAudit -Target 'C:\Program Files\WindowsApps\Contoso.App_1.0.0_x64__abc' `
                 -OutDir .\out\App -Acknowledge

# add local-only AI triage (Ollama); never leaves the box
Invoke-TcpkAudit -Target 'C:\Apps\Contoso' -Acknowledge -EnableLlm

# CI gate: non-zero exit if anything HIGH or worse survives verification
Invoke-TcpkAudit -Target 'C:\Apps\Contoso' -Acknowledge -FailOn HIGH

01Why thick clients are their own threat model

Web apps keep their logic on the server. Thick clients ship it to the attacker. The moment an MSIX lands on disk, the adversary owns a copy of your binaries, your config, your embedded URLs, your trust decisions, and -- far too often -- your secrets. The interesting questions are not "is there an injection point on a form" but:

  • What did you bake into the artifact? Private keys, cloud account keys, connection strings, JWTs, hardcoded credentials -- readable by anyone who downloads the installer.
  • What trust does it grant the OS and other processes? Custom URI schemes (app://), file-type associations, COM servers, named pipes, RPC interfaces, services, scheduled tasks -- each one an attacker-reachable entry point.
  • Does it validate what it is told? A URI handler that hands its argument to Process.Start; a BinaryFormatter.Deserialize over a stream from disk; a certificate callback that return trues.
  • Is the binary even defended? ASLR/DEP/CFG/GS, Authenticode, strong naming -- or stripped and replaceable in a user-writable directory.

TCPK is organized around those questions, not around a generic OWASP web checklist bolted onto a desktop app.

02Design principles

1. Evidence over assertion

A regex that finds BinaryFormatter proves the type is referenced, not that Deserialize() is invoked, reachable, and fed by external input. TCPK refuses to print a confident verdict it cannot back with IL. The confidence model is first-class output.

2. Data-driven detection

Secret patterns, dangerous call-sites, and deserialization sinks live in Data/secrets.json as regex rules with severity + CWE. Adding a detection is a JSON edit, not a code change. Rules are compiled once and prefiltered with a cheap literal.

3. Local-first, air-gap friendly

Zero third-party PowerShell modules, no Excel install (the workbook is written byte-by-byte), no internet required. The optional AI layer defaults to local Ollama and refuses to send decompiled IL to a cloud provider unless you explicitly open the gate.

4. Honest scoring

The displayed CVSS v4.0 number is computed by a faithful port of the FIRST.org reference algorithm from an attack-archetype vector, so a LOCAL-only issue is never mislabelled with a network attack vector, and the severity badge always agrees with the computed band.


03Architecture

TCPK is a single PowerShell module plus three front-ends (CLI, WinForms GUI, MCP server) over a shared engine. The module auto-loads its class, private helpers, and public cmdlets; the function name equals the file basename; public cmdlets are auto-exported.

TCPK/
  TCPK.psd1                 # manifest: ModuleVersion, ReleaseNotes, exports
  TCPK.psm1                 # dot-sources Classes/ -> Private/ -> Public/
  Classes/
    TcpkFinding.ps1         # [TcpkFinding] -- the universal output object
  Private/                  # _-prefixed helpers (not exported)
    _Finding.ps1            # New-TcpkFinding factory + CVSS archetype map
    _Cvss40.ps1            # FIRST.org CVSS v4.0 base-score engine
    _Decompile.ps1         # Mono.Cecil IL bridge (callsites, taint, TLS verdicts)
    _Attack.ps1            # MITRE ATT&CK technique map
    _Tasvs.ps1             # OWASP TASVS + Desktop App Top 10 map
    _VerifyHint.ps1        # per-rule manual re-validation playbook
    _PeReader.ps1          # raw PE parser (DllCharacteristics, load-config, ...)
    _Llm.ps1              # multi-provider LLM client (local-first, cloud-gated)
    _Strings.ps1, _UriSink.ps1, _Msix.ps1, _Xlsx.ps1, ...
  Public/                   # one cmdlet per file, grouped by bucket
    Discovery/  Manifest/  OsIntegration/  Credentials/  Runtime/
    Network/  WebView2/  Logging/  Memory/  AntiDebug/  Verify/
    Recon/  Report/  Exploit/  Llm/
    Invoke-TcpkAudit.ps1   # the top-level orchestrator
  Data/
    secrets.json           # rules[], callsite_patterns[], deser_tokens[]
    cvss/cvss40-lookup.json# 270-entry macrovector -> base-score table
    cve/catalog.json       # curated component CVE catalog
    checklist/thick-client-checklist.json  # 55-case methodology
Start-TCPKGui.ps1          # WinForms console
Start-TcpkMcpServer.ps1    # Model Context Protocol server for AI agents

The finding object

Every public cmdlet returns [TcpkFinding] instances. Report generators are the only code that knows about formatting; everything else passes typed objects down the pipeline. This is the contract:

class TcpkFinding {
    [string]   $Module       # bucket: static | manifest | os | creds | runtime |
                             #         network | webview2 | logging | memory |
                             #         antidebug | exploit | chain | meta
    [string]   $RuleId       # stable dotted id, e.g. 'callsites.command-execution'
    [string]   $Severity     # INFO | LOW | MEDIUM | HIGH | CRITICAL
    [string]   $Confidence   # Confirmed | Inferred | Unverified | Skipped
                             #   (refined at verify time to 'Confirmed (IL)',
                             #    'Likely-FP (IL)', 'Confirmed (LLM)', ...)
    [string]   $Title
    [string]   $Description
    [string]   $File         # primary path / key the finding applies to
    [string]   $Evidence     # observed value (secrets redacted before storing)
    [string[]] $Affected     # aggregation: every occurrence of the SAME rule
    [string[]] $Cwe          # e.g. 'CWE-798'
    [string]   $Impact       # else derived from Severity at report time
    [string]   $Cvss         # explicit v4.0 vector (e.g. a real NVD vector) or
                             #   assigned by attack archetype at report time
    [string]   $Fix
    [string]   $Timestamp    # ISO-8601 UTC
}

A representative finding, post-verification, serialized to findings.json:

{
  "Module": "static",
  "RuleId": "callsites.command-execution",
  "Severity": "MEDIUM",
  "Confidence": "Confirmed (IL)",
  "Title": "Process / shell execution call site in Updater.dll",
  "File": "C:\\Apps\\Contoso\\Updater.dll",
  "Evidence": "Process.Start, ProcessStartInfo",
  "Cwe": ["CWE-78"],
  "Impact": "Meaningful weakness that aids an attack chain ...",
  "Cvss": "",
  "Fix": "Allow-list the argument before it reaches the process-launch sink.",
  "Description": "... [TCPK IL: reachable call where external input reaches the
    sink (1 call site) -- the method reads an external source (file / registry /
    network / IPC / HTTP request) or a caller parameter flows into the argument.
    Treat as a real injectable path and review.]"
}

Detection buckets

Checks are grouped A-L. The CLI runs them in order; the GUI runs the same set and streams each result live.

BucketModuleWhat it interrogatesExamples
A · Static binarystaticPE imports/exports, strings, secrets, endpoints, deserialization, dangerous call-sites, TLS bypass, XXE, WCF, reflection, P/Invoke, packers, Electron, unsafe CRT, CSV injection, single-file extractionsecrets.*, callsites.*, deser.*, tls-bypass.*, csv.formula-injection-risk
B · MSIX manifestmanifestAppxManifest capabilities, protocol/file-type extensions, appExecutionAlias shadowing, COM, full-trustmsix.protocol-handler, msix.alias-shadowing
C · OS integrationosRegistry footprint, autostart, services, scheduled tasks, UAC, COM servers, DLL search order, file associationsprotocol-handler, uac.*, service.*
D · Credential storagecredsPEM/PFX key material, DPAPI blobs, Credential Manager, browser/Electron token stores (App-Bound vs DPAPI)keymaterial.*, browser.cookie-key-dpapi
E · Runtime (live)runtimeProcess mitigations, loaded-module signatures, listening ports, handles, window/GUI inspection, token, child processes, in-memory secrets (requires -ProcessName)process.*, ports.*, memsecret.*
F · NetworknetworkBackend endpoints, cleartext schemes, TLS posture, gRPC/SignalR channels, self-hosted listenersscheme.cleartext-http, rpc.grpc-insecure-credentials
G · WebView2webview2Navigation targets, virtual-host mapping, host-object bridge, web-message handlers, script injectionwebview2.add-host-object
H · Logging/telemetryloggingPII in logs, telemetry sinks, ETW providerslog.*, pii.*
I · Memory hygienememoryWER/pagefile exposure, secret-in-memory residency, dump policymem.*, wer.*
J · Anti-tamperantidebugAnti-debug, timing checks, self-integrity, packing/obfuscationantidebug.*, packer.*
K · Exploit (gated)exploitPoC template generation for confirmed findings -- behind Enable-TcpkExploit -AcknowledgeK01..K06
L · Verify / ReconchainIL proof (Confirm-*), target profile, attack surface, exploit-chain correlation, SBOMchain.uri-handler-sink-rce

04The audit pipeline

Invoke-TcpkAudit is a deterministic, ordered pipeline. The ordering matters: deduplication and false-positive killers run before scoring; IL verification runs before the LLM so the model and the reports inherit the proven verdicts; aggregation runs after the LLM so per-file judgments are preserved; the per-occurrence snapshot is taken before aggregation so recon and attack-surface views see every endpoint individually.

collect buckets A-J run every check -> List[TcpkFinding] | triage Resolve-TcpkFindings: dedupe + false-positive killers + correlation | IL verify Confirm-TcpkCallsiteUsage: Mono.Cecil reachability + source->sink taint | -> 'Confirmed (IL)' / 'Likely-FP (IL)' (no model, deterministic) | LLM (opt) Invoke-TcpkLlmCodeJudgment: advisory '(LLM)' verdict, never changes severity | local-only unless -AllowCloudLlm | snapshot $findingsFull = un-aggregated copy (recon/attack-surface read this) | aggregate Resolve-TcpkFindings -AggregateOnly: same RuleId+Sev+Conf -> 1 + Affected[] | enrich CVE match -> target profile -> attack surface -> SBOM -> exploit chains | report HTML + Excel + JSON + Markdown + CycloneDX SBOM + sidecars

Any check that throws becomes a single meta.cmdlet-failed INFO finding rather than aborting the run -- a partial audit is always better than no audit.

05The confidence ladder

Severity answers "how bad if real". Confidence answers "how sure it is real". TCPK treats them as orthogonal and surfaces both. A finding climbs (or falls) the ladder as evidence accrues:

ConfidenceMeaningProduced by
InferredA substring / regex / type reference matched. The name is present; reachability and data-flow are unproven.Static scanners (bucket A-J)
ConfirmedA structural fact verified without a decompiler (a PFX loaded with an empty password; a registry value exists; a port is listening).Direct probes
Confirmed (IL)Mono.Cecil proved the API is invoked, the enclosing method is reachable, and external input (a source API or a caller parameter) reaches the sink.Confirm-TcpkCallsiteUsage
Likely-FP (IL)The IL says the API is never actually invoked (string-only match), or is only ever called with constant arguments. Severity is dropped to INFO / one notch.Confirm-TcpkCallsiteUsage
Confirmed (LLM) / Likely-FP (LLM) / Uncertain (LLM)Advisory second opinion from a model that read the decompiled method. Adjusts confidence, never severity.Invoke-TcpkLlmCodeJudgment

This is the whole point of the tool. Most scanners stop at Inferred and call it CRITICAL. TCPK makes the cost of a false positive explicit and then spends deterministic IL analysis to drive it down.

06The IL verification engine

The bridge in _Decompile.ps1 loads Mono.Cecil (which ships with ILSpy) and reads method bodies directly. The core primitive, Get-TcpkCallsiteUsage, answers three questions a substring scan cannot:

  1. Is the API actually invoked? A call / callvirt / newobj to the sink, or did the rule match a mere string/type reference?
  2. Is the enclosing method reachable? public / virtual / entry-point / event-handler-shaped / has any in-assembly caller.
  3. Does external input reach the dangerous argument? A bounded backward scan of the IL preceding the call, classifying the argument as constant (ldstr/ldc), dynamic (ldarg/ldloc/ldfld), or tainted.

Interprocedural source-to-sink taint

"Tainted" is the signal that turns a maybe into a finding -- and it follows external input across method boundaries, not just within the sink's own method. A call site is tainted when:

tainted = methodHasSource                  the sink's method itself reads a source
       OR sourceOrTaintedReturningCall      inline:  Process.Start(ReadConfig())
       OR taintedLocal                      via local: var x = ReadConfig(); Process.Start(x)
       OR taintedField                      cross-method carrier:
                                              Configure() { _cmd = ReadConfig(); }
                                              Run()       { Process.Start(_cmd); }
       OR (callerParameter AND reachable)    a reachable method's own parameter feeds it

methodHasSource = the method calls one of:
  System.IO.File::Read*/Open*, StreamReader::Read*, ReadAllText/Bytes,
  Console::Read*, Environment::GetEnvironmentVariable/GetCommandLineArgs,
  Registry::GetValue/OpenSubKey, WebClient/HttpClient downloads,
  ReadAsStringAsync, NamedPipe/PipeStream::Read, Socket::Receive,
  SqlDataReader, HttpRequest::get_Form/get_QueryString/get_InputStream, ...

A "tainted-returning method" (cached, 3-hop fixpoint over the call graph) is a
value-returning method that transitively reads a source. A "tainted field" is one
assigned such a value anywhere in the assembly. Both are PRECISE -- direct
assignment from a known source only -- so recall rises without new false positives.

The classifier is deliberately biased safe: it only calls an argument constant when no dynamic load appears in the window, so a real bug is never demoted on a miss. The decision matrix for an injection-class sink:

IL observationVerdictEffect
API never invoked (call/newobj absent)Likely-FP (IL)severity -> INFO
Called only with constant argsLikely-FP (IL)severity -1 notch
Reachable + external input reaches sinkConfirmed (IL)"real injectable path"
Reachable + dynamic, no proven sourceunchangedreview note, no over-claim

Managed and native sinks

The engine matches both managed BCL sinks by declaring type and P/Invoke sinks by method name (so a native WinExec/CreateProcess/ShellExecute is treated like Process.Start). A declared-but-never-called P/Invoke capability (a keyboard hook, screen grab, or token impersonation that was copied in wholesale but never invoked) is correctly demoted to INFO. Deserialization is confirmed when an unsafe formatter's Deserialize() / ReadObject() is actually invoked -- and strengthened when the stream is tainted.

# standalone IL confirmation over a single assembly
Confirm-TcpkDeserialization -Dll .\Updater.dll
# Dll          Formatter        In                       Op    Target
# Updater.dll  BinaryFormatter  Updater.Cache::Load      call  Deserialize

# the audit pipeline runs this automatically; you can also pipe findings:
$findings | Confirm-TcpkCallsiteUsage
Why this matters. Pattern matching on a 100 MB Electron bundle that mentions -----BEGIN RSA PRIVATE KEY----- in a UI placeholder, or registers a normal app:// URI handler, will scream CRITICAL all day. The IL/structural layer is what separates "the string is here" from "the bug is here".

07The CVSS v4.0 engine

A CVSS v4.0 base score is not a closed-form formula -- it is a 270-entry macrovector lookup plus a severity-distance interpolation. _Cvss40.ps1 is a faithful port of the FIRST.org reference calculator (cvss_score.js / cvss_lookup.js / max_severity.js / max_composed.js), so the number is derived from the vector, never guessed. The lookup table ships verbatim.

# the score is computed, and validated against the FIRST.org reference:
Get-TcpkCvss40Score -Vector 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'
# Score   Rating     MacroVector
# 9.3     Critical   000200

Findings are not scored by a hand-typed number; they are scored by an attack archetype -- a representative 11-metric vector chosen for the bug family. This is what stops a local-only weakness from being mislabelled with a network attack vector:

ArchetypeVector (abridged)ScoreFor
net-rceAV:N VC:H VI:H VA:H9.3unsafe deserialization, poisoned update
live-credentialAV:N VC:H VI:H9.3cloud/API/SCM/SSH credential, network read+write
net-mitmAV:N VC:H VI:H VA:N8.7TLS cert/hostname bypass
embedded-keyAV:L VC:H VI:H8.5private key / unprotected keystore in the artifact
local-privescAV:L PR:L VC:H VI:H VA:H7.xwritable ACL / hijack / service path
shipped-secretAV:L VC:H VI:N6.9confidentiality-only secret on disk
weak-cryptoAV:N AC:H AT:P VC:L VI:Llowweak hash / mode / RNG

For genuinely mixed families (raw call-sites, registry), TCPK refuses to fabricate a vector and prints "assign exact CVSS v4.0 vector per finding" instead -- and for credential findings it picks the archetype by severity tier, so the badge and the computed band always agree.

08Exploit-chain correlation

Most checks score a single condition in isolation. Real compromise is usually a chain. Get-TcpkExploitChains raises a finding above its parts when the preconditions co-occur -- and, crucially, it only chains links that survived verification (it skips anything already demoted to Likely-FP, and a chain is only as real as its weakest link).

chain.unsigned-update-writable  (CRITICAL)
  = updater applies payloads with NO signature check
  + a standard user can write to a directory the app loads from

chain.web-host-bridge-rce       (CRITICAL)
  = WebView2 loads loosely-scoped / external content
  + a .NET<->JS host bridge is exposed to it

chain.uri-handler-sink-rce      (CRITICAL)
  = the OS hands attacker-controlled URIs/files to the app
  + activation input reaches a REAL RCE sink
    (command-execution / deserialize / path-build -- not a weak call-site)

The narrative, contributing conditions, and a per-link verify playbook are emitted with the finding so the analyst can confirm each link and then prove the path end-to-end.


09Detection catalog (selected)

Detections are data-driven. A dangerous call-site rule is a JSON object; Test-TcpkCallsites matches its patterns, and Confirm-TcpkCallsiteUsage later verifies any hit:

{
  "id": "command-execution",
  "title": "Process / shell execution call site",
  "severity": "MEDIUM",
  "cwe": ["CWE-78"],
  "patterns": ["Process.Start","ProcessStartInfo","ShellExecute",
               "CreateProcess","WinExec","cmd.exe","UseShellExecute"]
}

A secret rule requires a real body, not just a header marker (so UI placeholders do not false-positive):

{
  "id": "pem-private-key",
  "title": "PEM-encoded private key",
  "severity": "HIGH",
  "pattern": "-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----[A-Za-z0-9+/=\\s\\\\]{100,6000}-----END (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----",
  "cwe": ["CWE-321","CWE-798"]
}
ClassRepresentative rulesCWE
Hardcoded secretsAWS/Azure/GCP keys, connection strings, PEM/SSH/PuTTY keys, GitHub/Slack/Stripe tokens, JWTsCWE-798, CWE-321
Injection sinkscallsites.command-execution, sql-command-construction, ldap-query, ssrf-request-build, path-traversal-build, nosql-command-constructionCWE-78/89/90/918/22
Unsafe deserializationdeser.binaryformatter, netdatacontractserializer, soapformatter, typenamehandlingCWE-502
Transport securitytls-bypass.cert-callback-accepts-all, scheme.cleartext-http, rpc.grpc-insecure-credentialsCWE-295, CWE-319
Crypto misuseweak-symmetric-crypto (DES/3DES/RC2/RC4/ECB), weak-hash-md5-sha1, weak-rng, base64-as-encryptionCWE-327/328/338
Parser / archivexxe.*, zipslip.*, csv.formula-injection-riskCWE-611/22/1236
Native memorynative.unsafe-crt (strcpy/sprintf/gets + SDL-banned lstrcpyW/StrCpyW/_snprintf family)CWE-120/134/787
Capability abusekeylogging / screen capture / clipboard / token impersonation / COM elevation / UAC-bypass registryCWE-250/269

TCPK transparently unpacks what it audits: it extracts .NET single-file (PublishSingleFile) bundles and re-scans the assemblies inside, cracks Electron ASAR archives and shipped Java jar/war/ear, parses MSIX manifests, and fingerprints Tauri and Flutter desktop apps.

10Binary hardening + code-signing matrices

Posture is reported as a matrix, not as noise. Get-TcpkPeHardening parses the PE directly (no dumpbin) and reports per-DLL exploit mitigations; missing mitigations are posture, not per-DLL findings, because a missing canary is defense-in-depth, not an exploitable bug on its own.

Get-TcpkPeHardening -Path .\out\extracted | Format-Table DLL,ASLR,DEP,CFG,GS,Status
# DLL            ASLR  DEP   CFG   GS    Status
# app.dll        Yes   Yes   Yes   Yes   HARDENED
# legacy.dll     Yes   Yes   No    No    PARTIAL
# vendor.dll     No    Yes   No    No    WEAK

Columns: ASLR, DEP, CFG, HighEntropyVA, SafeSEH, GS (the /GS stack cookie, read from the load-config SecurityCookie), ForceIntegrity. The signing matrix (Get-TcpkSigningMatrix) is information-only and timestamp-aware:

StatusMeaning
SIGNED / CATALOGvalid Authenticode (embedded / catalog)
EXPIRED-TScert expired but counter-signed by a timestamp authority -- still valid (amber)
EXPIREDexpired, no timestamp (red)
UNSIGNED / TAMPERED / UNTRUSTEDno signature / hash mismatch / chain does not build

Each row carries the signer CN, digest algorithm, valid-from, and expiry. Both matrices render in the GUI, the HTML report, and dedicated Excel sheets, with JSON sidecars (hardening.json, signing.json).

11SBOM + CVE

TCPK emits a CycloneDX SBOM (sbom.cdx.json): every shipped component with name, version, publisher, SHA-256, and a correct purl (pkg:nuget/... derived from deps.json for managed components, native for the rest) -- consumable by Dependency-Track, Grype, or OSV. Matched CVEs from the curated catalog are embedded as a CycloneDX vulnerabilities[] array linked to the affected component bom-ref, and surfaced in the report. An optional -OnlineCve switch additionally queries the OSV API (off by default; sends only the package name + version, fails closed with no network) and merges those CVEs into the same vulnerabilities[].

Get-TcpkCveMatches -Path .\out\extracted | Format-Table Status,Severity,Cve,Package,ShippedVersion,FixedVersion
# Status      Severity  Cve              Package          Shipped  Fixed
# Vulnerable  HIGH      CVE-2024-21907   Newtonsoft.Json  12.0.0   13.0.1

Managed (NuGet) matches are version-compared and reliable; native/embedded matches are reported as Present / PossiblyEmbedded so you verify the embedded build before treating them as confirmed.

12The AI verification layer (optional, local-first)

The LLM stage is an advisory second reader, not the source of truth -- the deterministic IL pass already ran. It annotates code-construct findings with a (LLM) verdict and reasoning; it never changes severity. It is model-agnostic:

ProviderDialectNotes
ollama (default)localfully offline; nothing leaves the box
claudeanthropiccloud (gated)
openai / gemini / grok / deepseekopenai chat-completionscloud (gated); free-text model field
customopenai-compatibleany self-hosted endpoint
The cloud gate. Sending decompiled IL to a cloud model can breach an engagement NDA. TCPK skips a cloud provider by default; you must pass -AllowCloudLlm (CLI) or confirm the warning dialog (GUI) to send anything off-box. The local Ollama path needs no gate.
# offline triage with a local model:
Invoke-TcpkAudit -Target 'C:\App' -Acknowledge -EnableLlm
# explicitly permit a cloud model (IL leaves the machine):
Invoke-TcpkAudit -Target 'C:\App' -Acknowledge -EnableLlm -AllowCloudLlm

13ATT&CK, OWASP TASVS, and the 55-case checklist

Every finding is mapped, at report time, to a MITRE ATT&CK technique and to OWASP TASVS v1.8 + the Desktop App Security Top 10. Mapping is a pure prefix lookup over the RuleId, so it stays in sync with the [TcpkFinding] schema. Findings are also auto-correlated to a 55-case thick-client methodology (TC01-TC55); the auto-status is TCPK's read, and the tester sets the final PASS/FAIL -- a NO-FINDINGS status is explicitly not a pass.

Get-TcpkAttackText 'callsites.command-execution'   # T1059 Command and Scripting Interpreter
Get-TcpkTasvsText  'secrets.aws-access-key-id'      # TASVS-STORAGE ...; DA3 Sensitive Data Exposure

14Reporting outputs

One audit, many consumers. The output directory is self-contained:

FileAudienceContents
index.htmlanalystseverity-grouped findings, live filter/search, rule summary, recon, CVE, hardening + signing matrices, SBOM; computed CVSS + ATT&CK + TASVS + verify playbook per finding
intel.htmlanalyst / triageself-contained offline "program intelligence" dashboard: severity + confidence with the evidence ladder explained, a classified recon endpoint map, and filterable per-finding cards -- one file, no server, no CDN
report.xlsxreviewer / triageSummary, Findings, Checklist, DLL Hardening, DLL Signing, CVEs, Recon, SBOM sheets (pure-PowerShell, no Excel needed)
findings.jsontooling / CIthe full typed finding pipeline
sbom.cdx.jsonsupply-chainCycloneDX components + vulnerabilities[]
hardening.json / signing.jsonpostureper-DLL mitigation + code-signing matrices
profile.json / attack-surface.jsonreconapp identity + every endpoint, port, handler, COM server, pipe, file association
exploits.jsonred teamcorrelated chains + exploit plan
run.jsonl / run.logaudit trailstructured + human run log

15Usage

CLI

Import-Module .\TCPK\TCPK.psd1

# full audit (MSIX, install dir, or single binary)
Invoke-TcpkAudit -Target 'C:\Path\App.msix' -OutDir .\out\App -Acknowledge

# include live-process checks (ports, handles, in-memory secrets)
Invoke-TcpkAudit -Target 'C:\Apps\Contoso' -ProcessName Contoso -Acknowledge

# run a single check in isolation
Test-TcpkSecrets       -Path 'C:\Apps\Contoso'
Get-TcpkSigningMatrix  -Path 'C:\Apps\Contoso'
$findings | Confirm-TcpkCallsiteUsage      # IL-verify a finding set
Get-TcpkCvssVector $finding                # the computed v4.0 vector

GUI

# recommended launcher (handles STA + module-path resolution)
.\TCPK.bat
# or directly
powershell -STA -ExecutionPolicy Bypass -File .\Start-TCPKGui.ps1

Pick a target, click Run Audit, watch the progress bar and findings stream in. Tabs expose Recon/Target, Exploit/CVE, SBOM, DLL Mitigation Matrix, DLL Signing, and the live log. The AI provider, model (free-text), and key are configured inline.

Web control panel

# loopback-only (127.0.0.1), token-gated, discovery-only -- prints a URL+token, opens your browser
.\TCPK-WebUI.bat

The same audit, driven from the browser: live progress, pause/resume/cancel, and result tabs (Findings, Recon, SBOM, DLL Mitigation, DLL Signing, Logs, Reports) with report downloads. Every /api/* call requires a per-session X-TCPK-Token header, the Host header is checked, and the gated exploit bucket is never reachable -- so a web page you happen to visit cannot drive it (no localhost CSRF / DNS-rebind).

MCP server (for AI agents)

# expose TCPK's cmdlets as Model Context Protocol tools
powershell -File .\Start-TcpkMcpServer.ps1

16Install

  • Windows PowerShell 5.1+ (the GUI is WinForms; the engine also runs under PowerShell 7).
  • No third-party PowerShell modules. Reporting needs nothing extra.
  • Optional: ILSpy (for the bundled Mono.Cecil that powers IL verification) and Ollama (for fully-local AI triage).
# clone / unzip, then:
Import-Module .\TCPK\TCPK.psd1
Get-Command -Module TCPK | Measure-Object   # 178 cmdlets
Invoke-TcpkAudit -Target '<app>' -OutDir .\out -Acknowledge

17Authorization & ethics

Authorized testing only. TCPK prints an authorization banner and requires -Acknowledge (CLI) before it runs. The exploit / PoC bucket is gated separately behind Enable-TcpkExploit -Acknowledge and emits templates, not weapons. Audit outputs can contain real secrets extracted from a target and are excluded from version control by default -- never commit out/. Use it on your own software, a contracted engagement, written bug-bounty scope, or a lab.