Repository navigation
Releases: uziii2208/PwnRM
Release list
v2.1.0
What's Changed
This release expands PwnRM from a post-exploitation shell into an elite operator platform — 5 new specialized attack modules, in-memory credential extraction bypassing EDR process creation telemetry, multi-vector coerced authentication, Server 2025 LAPS hunting, DACL privilege escalation analysis, polymorphic PowerShell AST obfuscation, hardened SOCKS5 multiplexing, 3 targeted security fixes, and a completely custom self-contained GitHub Actions CI/CD Security Audit Pipeline with zero third-party SAST dependencies.
New Modules — 5 Specialized Attack Capabilities
!vss — In-Memory VSS Shadow Copy Extractor [modules/vss.py]
Direct WMI / CIM reflection via [wmiclass]"Win32_ShadowCopy" to locate and copy active SAM, SYSTEM, and NTDS.dit credential hives from Volume Shadow Copies without invoking vssadmin.exe, ntdsutil.exe, or any other subprocess that generates EDR process creation telemetry. After extraction, the shadow copy is immediately deleted via .Delete() to eliminate forensic artifacts. This is the difference between an operator that leaves a smoking gun in Windows Event Log 4688 and one that doesn't.
!coerce — Multi-Vector Coerced Authentication Engine [modules/coerce.py]
Triggers NTLM authentication coercion from the remote target back to an operator-controlled listener (Responder / ntlmrelayx) via four distinct protocol vectors — all executed in-memory through the active PSRP session without staging any binary. Vectors: WebDAV HTTP UNC path (\\listener@80\share\dummy.txt) bypassing SMB signing requirements for ADCS ESC8 Web Enrollment relay attacks; MS-RPRN Print Spooler (\pipe\spoolss); MS-EFSR PetitPotam (\pipe\efsrpc); MS-DFSNM (\pipe\netdfs). Operator selects the appropriate vector based on available services and SMB signing posture on the target.
!laps — Windows LAPS & Server 2025 Hunter [modules/laps.py]
In-memory LDAP query covering both Legacy LAPS and Modern LAPS schemas in a single pass. Legacy LAPS: reads ms-Mcs-AdmPwd (cleartext local admin password) and ms-Mcs-AdmPwdExpirationTime (expiration timestamp) from DirectorySearcher. Modern LAPS (Windows Server 2025 / Windows 11 Azure-joined): queries msLAPS-Password, msLAPS-EncryptedPassword, and msLAPS-PasswordHistory — covering both cleartext and AES-256-CBC encrypted password blobs alongside full history. Results surface directly in the operator console and are ingested into the !loot pipeline.
!acl — Active Directory DACL Privilege Escalation Scout [modules/acl.py]
Audits discretionary access control lists on Tier-0 Active Directory objects: AdminSDHolder, Domain Admins, Domain Controllers, krbtgt, and linked GPOs. For each object, enumerates principals holding high-risk access rights — GenericAll, WriteDacl, WriteOwner, GenericWrite, User-Force-Change-Password — and flags them as privilege escalation vectors. All enumeration runs in-memory via DirectoryServices LDAP queries on the active PSRP session; no SharpACLs, BloodHound collection, or additional binary is staged.
!token — Process Token Hunter & In-Memory Impersonation [modules/token.py]
Enumerates process tokens across active logon sessions and performs privilege escalation triage by inspecting SeImpersonatePrivilege, SeAssignPrimaryTokenPrivilege, SeDebugPrivilege, and SeBackupPrivilege — flagging each with its current enabled/disabled state and the corresponding abuse primitive. Token duplication via DuplicateTokenEx reflection and named pipe client token coercion are surfaced as impersonation pathways, without requiring any binary drop to disk.
OPSEC & Core Improvements
Polymorphic PowerShell AST Command Obfuscation [core/opsec.py]
Added obfuscate_cmd() — a dynamic backtick and whitespace splitter for PowerShell cmdlet names that activates under stealth and hybrid-cloud OPSEC profiles. The function inserts randomized backtick sequences between cmdlet name characters (Invoke-Expression) while preserving variable names, parameters, and argument structure, defeating static ScriptBlockLogging signatures without breaking execution. Jitter sleep timing calculations across all profiles are now seeded from secrets.randbelow() rather than time.sleep(random.uniform(...)), making inter-request timing non-deterministic to statistical network anomaly detectors. OPSEC profile table updated with the new obfuscation column.
Thread-Safe SOCKS5 Proxy & Port Forwarding [core/tunnel.py]
Hardened Socks5Server and PortForwarder with threading.Lock() mutex guards on all connection tracking and socket descriptor operations, preventing race conditions during concurrent multi-session switches. Socket timeout guards are now applied on both ends of the relay preventing orphaned socket descriptors from accumulating in process memory on abrupt client disconnects.
WebSocketTransport — Public API Export [core/__init__.py, __init__.py]
WebSocketTransport is now exported from pwnrm.core and pwnrm package top-level __all__, making it accessible as a library transport for consumers integrating PwnRM programmatically.
bump_version.py — Hardened Repo Root Detection [src/release/bump_version.py]
Refactored find_repo_root() to check both __file__ parent and Path.cwd() as starting points, and tightens the root detection predicate to require both pyproject.toml and src/pwnrm/ to exist before declaring a match — eliminating false positives when the script is run from nested directories or via CI. cleanup_build_artifacts() now uses ignore_errors=True consistently and guards __pycache__ removal behind is_dir(). TARGETS expanded to include README.md Changelog badge for atomic version synchronization.
Security Hardening (Auditor Pass)
CWE-918 — SSRF / Proxy MitM via Ambient Environment [core/transports.py]
Enforced session.trust_env = False across all Transport base class instantiations. Previously, Python requests would honour HTTP_PROXY / HTTPS_PROXY environment variables, allowing a compromised operator environment to silently reroute WinRM traffic through an attacker-controlled proxy. With trust_env = False, ambient proxy configuration is fully ignored regardless of execution context.
CWE-208 / CWE-385 — Timing Side-Channel in CredSSP pubKeyAuth Verification [core/transports.py]
Replaced standard equality comparison (==) on the CredSSP Server-To-Client Binding hash verification with hmac.compare_digest() — a constant-time comparison that eliminates the byte-by-byte early-exit timing oracle that could leak information about the expected hash to an active MitM attacker.
CWE-532 — Credential Identifier Leakage into Logs [core/api.py]
Sanitized logging.info() call sites in create_transport() to remove raw credential variable references from structured log output. Transport authentication method is now logged descriptively without serializing any object that may hold credential material.
CWE-200 / CWE-522 — Expanded History Exclusion Pattern [shell/pwnshell.py]
HISTORY_EXCLUDE_PATTERN regex expanded to automatically suppress offensive tooling and credential keywords from .pwnrm_history: mimikatz, secretsdump, lsass, ccache, kirbi, dpapi — added alongside the existing -p, -H, --hash, --pfx-pass, password, secret, token patterns.
Custom CI/CD Security Audit Pipeline [.github/]
A fully self-contained GitHub Actions security audit pipeline with zero dependency on third-party SAST tools. All analysis is implemented directly in Python using ast, re, and subprocess.
Workflow (.github/workflows/security_audit.yml) — 5-job pipeline: setup → unit_and_regression (fail-fast) → custom_security_audit + dependency_audit (parallel) → report_and_annotate (always runs). Triggers: push to master/main/dev/release/*, pull request, weekly cron (Monday 03:00 UTC), and manual dispatch with audit_level input.
PowerShell Injection Auditor (.github/scripts/audit_ps_injection.py) — AST data-flow analyzer walking all ast.JoinedStr f-string nodes and string concatenations that feed into run_sync(), run_with_interrupt(), and run_command(). Traces variable origin up to 3 assignment hops and flags server-returned or user-supplied values that reach PS string interpolation without _pde(), _pse(), or str_b64() wrapping. Known-safe patterns (build_amsi_patch() output, secrets.token_hex() identifiers) are allowlisted.
Cryptographic Hygiene Auditor (.github/scripts/audit_crypto.py) — CryptoAuditVisitor AST walker validating CSPRNG usage (secrets vs random), secrets.randbelow() argument bounds (must be > 1), secrets.token_bytes() entropy floor (≥ 16 bytes), Fernet _SESSION_KEY lifecycle (no disk write, no log), and MD5 usage discrimination (file integrity = INFO, auth context = HIGH).
Filesystem & Permissions Auditor (.github/scripts/audit_filesystem.py) — Enforces atomic O_CREAT | O_EXCL file creation patterns for all writes under _PWNRM_DIR, verifies 0o600 chmod follows within 5 lines of os.open(), detects tempfile.mktemp() TOCTOU usage, checks __pycache__ git-tracking via git ls-files, and verifies symlink attack guard on history file creation.
XML Deserialization Safety Auditor (.github/scripts/audit_xml_safety.py) — Confirms defusedxml monkey-patches ET.fromstring and ET.parse on import, verifies defusedxml>=0.7.1 pinning in requirements.txt, and checks all ET.fromstring() call sites are wrapped in exception handlers catching defusedxml-specific exceptions.
Secret & Credential Exposure Auditor (.github/scripts/audit_secrets_exposure.py) — Regex scan across .py and .ps1 ...
v2.0.1
What's Changed
Security Hardening — Full Niche Operator Quality Pass (FIX-01 – FIX-09)
-
FIX-01 (CWE-400 / Silent Data Corruption): Fixed closure rebinding bug in
download()stream collector (pwnshell.py:788–822).bytearray += bytesinside the inner closure was rebinding the local parameter name rather than mutating the shared buffer — outerbufstayed empty across all chunks. Replaced with in-placebuf.extend(chunk)and added diagnostic separation between empty-stream and collection-failure error paths. -
FIX-02 (CWE-78 / CWE-88): Resolved PowerShell single-quote injection in
upload()user-supplieddstpath (pwnshell.py:631–674). Replaced all single-quoted PS strings with double-quoted equivalents escaped via_pde()and enforced-LiteralPathacross every file manipulation cmdlet (Add-Content,Move-Item,Remove-Item). -
FIX-03 (CWE-73 / CWE-22): Fixed client-side trust of server-returned
Resolve-Pathoutput for local filename derivation (pwnshell.py:709–745). Default local destination is now derived strictly fromargs[0](user-supplied RPATH viaPureWindowsPath(args[0]).name), not from the server-resolved path. Added CWD-escape check: resolveddstis validated againstPath.cwd()and rejected on traversal. -
FIX-04 (CWE-693): Replaced static 6-byte AMSI patch signature
b8 57 00 07 80 c3(ret 0x80070057) with dynamicbuild_amsi_patch()polymorphic NOP generator (commands.py:38–54,evasion.py:54–75). Stub prefix is selected at runtime fromxchg ax,ax/nop/ multi-byte NOP variants viasecrets.choice(). Eliminated hardcodedStart-Sleep -Seconds 1OPSEC anti-pattern, replaced withVirtualProtect RWX → Marshal::Copy → RXtransition without deterministic sleep. -
FIX-05 (CWE-330): Replaced non-CSPRNG Mersenne Twister
random.randint/randbyteswithsecretsmodule across all security-sensitive call sites (commands.py:56–95,spnego.py:140–155,transports.py:295–310). XOR keys now usesecrets.randbelow(254) + 1(range [1,254], never zero-key), CredSSP nonces usesecrets.token_bytes(32), and PS namespace/classname identifiers usesecrets.token_hex(). -
FIX-06 (CWE-611): Integrated
defusedxml.ElementTreeas a safe XML parsing layer incore/psrp.py:5–16. Implementation monkey-patchesET.fromstringandET.parsewith defusedxml equivalents on import, with graceful fallback to stdlib ET ifdefusedxmlis unavailable.defusedxml>=0.7.1added torequirements.txt. -
FIX-07 (CWE-22 / CWE-78): Reimplemented missing
_validate_remote_path()regression (pwnshell.py:727–748). Strict regex whitelist_SAFE_WINPATH(^[A-Za-z]:\\...) and_SAFE_UNCPATH(^\\\\...) blocking directory traversal sequences (..), shell metacharacters ($,`,;,&,`), and control characters. -
FIX-08 (CWE-400): Enforced
MAX_SYNC_BYTES = 1 * 1024 * 1024output ceiling inrun_sync()(pwnshell.py:298–315). Overflow triggers output truncation with a warning and active runspace interrupt viasignal— prevents OOM from anomalous or adversarially large remote command output. -
FIX-09 (CWE-295 / CWE-300): Implemented cryptographic
pubKeyAuthecho-back verification in CredSSP handshake (transports.py:318–350). Server-returnedpubKeyAuthblob is unwrapped and compared againstSHA256(b"CredSSP Server-To-Client Binding Hash\x00" + nonce + pubkey)— MitM relay without the private key is now detectable. Added CredSSP v5 protocol fallback for legacy domain configurations.
Operational Improvements (OPT-01 – OPT-08)
-
OPT-01: Eliminated
CtrlCHandlerre-entry race condition inrun_with_interrupt()(pwnshell.py:317–335). Handler now covers the entire generator stream duration rather than re-entering per-chunk, closing the window where a Ctrl+C between iterations would fall through to the default SIGINT handler. -
OPT-02: Upload progress display overhauled (
pwnshell.py:648–665). Progress is now calculated in-flight before chunk submission and rendered as a real-time percentage (Upload X/Y (Z%)), replacing the post-chunk counter that lagged one iteration behind actual transfer state. -
OPT-03:
help()refactored from a hardcoded static string into a structuredCOMMAND_REGISTRYdict (pwnshell.py:65–135). All commands with syntax and descriptions are sourced from a single registry — new commands added to the plugin system appear in help output automatically without manual string editing. -
OPT-04: Transcript log writes now pass through
strip_ansi()(pwnshell.py:270–295). Raw VT100/ANSI escape codes no longer pollute.logfiles — output is clean ASCII/UTF-8 readable in any editor or SIEM pipeline. -
OPT-05: Replaced colloquial comment explaining SPNEGO/Kerberos AP_REQ extraction as "hijack" with proper technical rationale citing RFC 4121 NegTokenInit unwrapping and MS-WSMV §3.2.5.1 GSS-API framing requirements (
transports.py:257–270). -
OPT-06: Hardcoded magic constant
0xe0088237in NTLM Type 1 flags defined as named constantNTLM_FLAGS_STANDARDwith comprehensive bitwise breakdown documentation (spnego.py:38–53). -
OPT-07: Sensitive download buffer zeroized on MD5 integrity failure (
pwnshell.py:810–822).buf[:] = b'\x00' * len(buf)executes immediately on checksum mismatch before error is surfaced — prevents credential material from lingering in process memory after a corrupt transfer. -
OPT-08: Introduced
HISTORY_EXCLUDE_PATTERNregex filter onprompt_toolkitFileHistory(pwnshell.py:55–65). Commands matchingpassword|secret|hash|token|pfx|ccache(case-insensitive) are not written to.pwnrm_history, preventing credential strings from persisting to disk across sessions.
Niche Source Audit Fixes (NICHE-01 – NICHE-02)
-
NICHE-01: Replaced bare
except:clause inutfstr()(utils.py:34–40) withexcept Exception:— preservesKeyboardInterruptandSystemExitpropagation during XML decode errors, fixing silently swallowed Ctrl+C in long enumeration runs. -
NICHE-02: Sanitized single-quote injection in SMB target array construction in
get_shares_ps()(shares.py:18–28). Target strings are now stripped and double-quote escaped viat.strip().replace('"', '"')before splicing into the@('TARGETS')` PowerShell array literal.
Architectural Additions (Tier 1–3)
-
Ephemeral Session Encryption: In-memory
Fernetkey (_SESSION_KEY = Fernet(Fernet.generate_key())) generated at process start and used to encrypt all serialized session metadata written to~/.pwnrm/sessions/. Key exists only in process memory — session files are unreadable after PwnRM exits, eliminating post-engagement forensic credential recovery from session artifacts. -
Dual ETW Telemetry Evasion:
!evasionnow patches bothEtwEventWriteandEtwEventWriteFullinntdll.dll. Windows 10 21H2+, Windows 11, and Server 2022/2025 route a significant subset of kernel ETW events throughEtwEventWriteFull— patching onlyEtwEventWriteleft a detectable telemetry path open on all post-21H2 targets. -
Structured Loot
MANIFEST.json(core/loot.py):LootManagernow maintains an operationalMANIFEST.jsonunder~/.pwnrm/loot/with per-artifact entries includingfilename,type,category,source_command, ISO 8601timestamp,sha256,target, andpath. Bothstore_credential()andstore_artifact()update the manifest atomically viaO_CREAT | O_WRONLY | O_TRUNCwith0o600permissions. -
Multi-Session Output Labeling:
fan_out_execoutput streams in!session exec-allare now prefixed with[S:{sid} | {host}]tags per line, disambiguating interleaved output across concurrent sessions. -
Plugin Integrity Verification:
ModuleManagernow callsverify_plugin_integrity()before dynamic import — validates that each*.pymodule file is owned by the current user (st_uid == os.getuid()) and is not world-writable (st_mode & stat.S_IWOTH == 0). World-writable plugin files are rejected with a warning. -
BloodHound CE v6 Meta Headers:
!bloodhoundin-memory LDAP enumeration output now includes correct BloodHound CE v6 metadata headers{"meta": {"type": "...", "count": N, "version": 6, "methods": 0}}— previously missing headers caused silent rejection or misparse on BloodHound CE ingestion. -
Playbook
on_failCleanup Hooks:PlaybookRunnerYAML DSL now supportscleanup_on_failkeys per step. On mid-playbook failure, registered cleanup commands are executed in reverse order against the active session to remove staged artifacts before the operator loses access. -
RBCD Inspection in
!kerberos: AddedmsDS-AllowedToActOnBehalfOfOtherIdentityattribute enumeration. Surfaces write-capable principals and flags RBCD abuse candidates alongside existing S4U2Self / constrained delegation checks. -
Enterprise CA Health Check in
!adcs: Added CA certificate expiration detection and CRL/AIA distribution point reachability probing. Offline CDP and AIA misconfiguration are flagged as strong indicators of unmaintained PKI — correlated with elevated ESC4/ESC7 probability in the output. -
Type Annotations (PEP 484): Comprehensive type annotations added across
core/api.py,core/credentials.py,core/psrp.py,core/runspace.py,core/session_mgr.py,core/transports.py, andcore/utils.py. All public method signatures now carryOptional,Union,Tuple,List,Callable, andGeneratorhints compatible with Python 3.10+ strict mode. -
Test Suite Expansion: 22 unit and security regression tests passing at 100% success rate (
pytest/unittest), up from 21 in v2.0.0.
Full Changelog: v2.0.0...v2.0.1
v2.0.0
What's Changed
Important
v2.0.0 is a full architectural overhaul. The PwnShell REPL, auth layer, module system, and output pipeline are not backward-compatible with v1.x configuration files or session artifacts.
Core Architecture — Multi-Session Manager & Jump Graph (session_mgr.py)
!session— Concurrent PSRP Runspace orchestration across heterogeneous targets: Prior to v2.0.0, PwnRM maintained a single activeRunspaceper process, requiring the operator to re-authenticate on context switches between pivots. The newSessionManagerholds an indexed pool ofpypsrp.RunspacePoolobjects keyed by session ID, each tagged with target metadata (hostname, auth type, auth material hash).!session listrenders the live pool with connection state;!session switch <id>performs a zero-reauth context switch by hot-swapping the activeRunspacePoolreference inPwnShell;!session exec-all <cmd>fans out a single command across all open sessions and aggregates structured results. Session metadata is serialized and persisted under~/.pwnrm/sessions/with strict0o700(POSIX) /icacls "%USERNAME%:(OI)(CI)F" /inheritance:r(Windows NTFS) permission enforcement, preventing multi-tenant credential leakage — consistent with the ACL hardening introduced in v1.2.1.
Tunneling — In-Band PSRP SOCKS5 Proxy & Port Forward Multiplexer (tunnel.py)
-
!socks— RFC 1928 compliant SOCKS5 server multiplexed through PowerShell streams: Conventional SOCKS pivoting requires a listener on the remote host (chisel, ligolo, etc.) and a firewall exception. PwnRM's SOCKS5 implementation runs a localasyncioserver bound to127.0.0.1:1080(configurable) and tunnels TCP frames through the existing PSRPstdout/stdinstream pair using a length-prefixed framing protocol. No listener socket is opened on the remote host; traffic appears as normal WinRMWSManFault-shaped XML fragments to network inspection. Implementation is RFC 1928 §3–§6 compliant:CONNECTmethod, IPv4/IPv6/FQDN address types, andAUTH_NONE/USERNAME_PASSWORDsub-negotiation (§4). -
!portfwd— Local-to-remote TCP port forwarding with lifecycle management:!portfwd <LPORT> <RHOST>:<RPORT>spawns anasyncioTCP listener onLPORTthat proxies connections through the active PSRP session toRHOST:RPORTon the target network segment.!portfwd listenumerates active forward rules with their bound port and target;!portfwd stop <id>cleanly drains theasynciotransport and closes the listener without disrupting the parent PSRP session.
Loot — Structured & Encrypted Loot Pipeline (loot.py)
!loot— Atomic loot ingestion with per-target namespacing: Credentials, Kerberos tickets (.ccache,.kirbi), certificates (.pfx,.pem), DPAPI master keys, and memory dumps are stored under~/.pwnrm/loot/<target>/in per-artifact-type subdirectories. File creation usesO_CREAT | O_EXCLatomic semantics to prevent TOCTOU overwrites, and all written files are immediatelychmod'd to0o600. The ingestion pipeline accepts raw bytes from any command module and performs MIME sniffing to auto-classify artifact type before persistence, removing the need for manual file-type specification per loot operation.
OPSEC — Profile & Jitter Engine (opsec.py)
!opsec— Behavioural profile switching with per-request timing jitter: Four operational profiles are implemented:stealth(high jitter, low throughput, legitimateMicrosoft WinRM ClientUser-Agent header, command fragment obfuscation),balanced(moderate jitter, no obfuscation),aggressive(minimal jitter, raw commands), andhybrid-cloud(tuned for Azure Arc / WinRM-over-HTTPS environments with cloud-specific User-Agent strings). Jitter is applied at the PSRP transport layer between requests using a per-profile(min_ms, max_ms)range sampled from a uniform distribution, making request cadence non-deterministic to timing-based EDR heuristics.
Modules — Pluggable Modular Engine (src/pwnrm/modules/)
BaseModule/ModuleManager— Hot-loadable plugin architecture: v2.0.0 introduces aBaseModuleabstract class exposingname,aliases,help_text,run(shell, args), and optionaltab_complete(shell, prefix)contract.ModuleManagerperforms filesystem discovery of*.pyfiles undersrc/pwnrm/modules/, imports them, and registers allBaseModulesubclasses into thePwnShellcommand dispatcher at startup. Third-party and user-authored modules drop into the modules directory with no changes to core — the loader handles registration, tab completion injection, and!helplisting automatically.
Exploitation — Full ADCS Engine ESC1–ESC16+ (adcs.py)
!adcs— Deep ADCS template vulnerability audit without dropping binaries: Implements in-memory LDAP enumeration of Certificate Authority configuration and certificate template ACLs to audit for ESC1 (enrollee-supplied Subject), ESC2 (Any Purpose EKU), ESC3 (Certificate Request Agent), ESC4 (template write ACL), ESC6 (EDITF_ATTRIBUTESUBJECTALTNAME2), ESC7 (CA officer ACL), ESC9/ESC10 (no-security-extension / weak certificate mapping), ESC13 (OID group link), ESC15 (schema version 1 + Application Policy EKU), ESC16 (disabledszOID_NTDS_CA_SECURITY_EXTglobally), and ESC17 (WSUS-to-ADCS abuse — enumeratesWUServerregistry value underHKLM\Software\Policies\Microsoft\Windows\WindowsUpdateto identify HTTP-only WSUS endpoints susceptible to MitM, where an attacker-controlled update package can trigger a certificate enrollment request to an internal CA under the machine account context, enabling domain privilege escalation). Enterprise CA discovery queriesCN=Certification Authorities,CN=Public Key Services,CN=Servicesin the configuration naming context. All enumeration runs through PowerShell LDAP queries on the active PSRP session; no SharpADCS, Certify, or Certipy binary is staged to disk.
Kerberos — Advanced Suite (kerberos.py)
!kerberos— AES-priority Kerberoasting, AS-REP roasting, Diamond Tickets, and Server 2025 dMSA/BadSuccessor inspection: Kerberoasting requests are issued withetype=18(AES256-CTS-HMAC-SHA1-96) preference before falling back to RC4, consistent with environments where RC4 is deprecated underMsds-SupportedEncryptionTypes. AS-REP roasting enumerates accounts withDONT_REQ_PREAUTHset via LDAP and pre-computes roastable target lists. Diamond Ticket workflow assistance provides step-by-step identity-swap guidance for environments where synthetic Golden/Silver Tickets trigger KDC anomaly detection on Defender for Identity. TheBadSuccessorinspection block enumeratesmsDS-ManagedAccount/ delegated Managed Service Account (dMSA) objects and surfaces misconfigured successor principal assignments introduced in Server 2025.
Cloud — Hybrid Entra ID / Azure AD PRT Pivot (entra.py)
!entra— Device join status inspection and WAM / Azure CLI / Az PowerShell credential cache extraction: Queriesdsregcmd /statusoutput via PSRP and parsesAzureAdJoined,DomainJoined, andEnterpriseJoinedfields to determine hybrid join topology. WhenAzureAdJoined: YES, the module enumerates Web Account Manager (WAM) token broker cache paths in the user profile forms-deviceidand PRT-adjacent token blobs. Azure CLI token cache (~/.azure/msal_token_cache.json) and Azure PowerShell credential cache (~\AppData\Roaming\Windows Azure PowerShell\TokenCache.dat) paths are identified and flagged for extraction via!loot.
Credential Hunting — Deep Credential & Token Hunter (creds.py)
!creds— DPAPI master key enumeration, browser credential store detection, and token privilege audit: Enumerates%APPDATA%\Microsoft\Protect\<SID>\for DPAPI master key files. Browser credential store detection covers Chrome, Edge, and BraveLogin Data(SQLite) andLocal State(AES-GCM key blob) paths under%LOCALAPPDATA%. Token privilege audit inspects the current runspace token forSeDebugPrivilege,SeImpersonatePrivilege, andSeTcbPrivilege, flagging each with its enable/disable state and a note on the relevant abuse primitive.
Evasion — Polymorphic Evasion & EDR Scout (evasion.py)
!evasion— Randomized polymorphic AMSI/ETW patching and EDR driver fingerprinting: AMSI bypass patchesAmsiScanBufferinamsi.dllusing a randomized stub (variable nop-sled length, randomized jmp displacement encoding) to avoid static signature matching on the patch bytes. ETW bypass patchesEtwEventWriteinntdll.dllwith aret/xor eax, eax; retstub selected at random. EDR driver detection queries\\.\device namespace and the SCM service list forcsagent.sys(CrowdStrike),SentinelAgent.sys(SentinelOne),WdFilter.sys(Windows Defender),CbDefenseW64.sys(Carbon Black), andcylance.sys(Cylance Protect), reporting detected products with their loaded driver path.
Reconnaissance — In-Memory BloodHound AD Graph Collector ([bloodhound.py](https://github.com/uziii2208/PwnRM/blob/master/src/pwnrm/modules/blood...
v1.2.6
What's Changed
Bug Fixes - SPNEGO / Authentication Layer spnego.py, mock_server.py
-
!spnego -
NoneResponseToken in Type 2 challenge causes auth failure (impacket compatibility break): When PwnRM negotiated NTLM through SPNEGO against a server whose SPNEGO stack was assembled via impacket's pyasn1 binding,NegTokenResp["ResponseToken"]returnedNonerather than the raw NTLM Type 2 bytes. This occurred because impacket'sSPNEGO_NegTokenResponly populates the field when the internal pyasn1 component path is written through the correct accessor — which differs between impacket versions — leaving the field unset despite the bytes being present in the DER envelope. Downstream,NTLMAuthChallenge(type2)receivedNone, producing an immediateTypeErrorand aborting the handshake. Fixed inspnego.py:64-71by scanning the rawdata_inbuffer for theNTLMSSP\x00signature whenResponseTokenisNone, extracting the challenge directly from the blob, and raisingSPNEGOError("SPNEGO: missing ResponseToken in Type 2 challenge")only if the signature is absent entirely. -
!mock_server - Fixed offset NTLM token detection breaks SPNEGO-wrapped NegTokenInit from impacket: The mock server's auth dispatch used a hardcoded
token[8:12]byte slice to identify NTLM message type, assuming tokens always arrive as raw NTLMSSP. When Linux impacket sends a SPNEGO-wrappedNegTokenInit, the NTLMSSP signature appears at a variable offset inside the ASN.1 envelope —token[8:12]reads the wrong bytes, the type check falls through, and the server rejects valid negotiation tokens. Simultaneously, the originalNTLM_CHALLENGEwas a hand-rolled byte stub that did not conform to MS-NLMP §2.2.1.2 — missing proper TargetInfo AV_PAIRS, a valid server challenge value, and a well-formed Version field — causing impacket's strict ASN.1 parser to reject the challenge during the mock test cycle. Fixed inmock_server.py:218-232by replacing the fixed-offset slice withtoken.find(b"NTLMSSP\x00")for signature detection, computingmsg_typerelative tosig_pos, and inserting a bare-SPNEGO fast path that issues a challenge and returns immediately when no NTLMSSP signature is found.NTLM_CHALLENGEwas rebuilt via_build_ntlm_type2()(conforming to MS-NLMP §2.2.1.2 withMsvAvNbDomainName+MsvAvEOLAV_PAIRS and a non-zeroServerChallenge) and wrapped in a fully hand-rolled DER SPNEGONegTokenRespvia_build_spnego_challenge()(RFC 4178 §4.2.2) to avoid impacket version-dependent field-assignment behavior. Affectedmock_server.py:49-110, 218-248.
Housekeeping
- Security advisory added to
HALL_OF_FAME.md, crediting @fuzt3r for new bug advisory - GHSA-xhjp-r3m9-54vj!
Full Changelog: v1.2.5...v1.2.6
v1.2.4
What's Changed
Critical Fix - PSRP Wire Format runspace.py
-
run_command- 100% pipeline failure on Windows Server 2025 WinRM (w:InternalError/w:InvalidSelectors): The custom MS-PSRP fragmentation layer in_fragment()encoded Runspace/Pipeline GUIDs as 32-byte ASCII hex strings instead of the 16-byte binary format required by MS-PSRP spec §2.2.1. Additionally,rsp:ShellIdin theCreaterequest body was hardcoded to"http://localhost/wsman"(the ResourceURI value) instead of a valid UUID, causing Windows to invalidate the shell immediately after creation. Combined, these two defects caused_create_pipeline()to receivew:InternalErroron first command dispatch andw:InvalidSelectorson all subsequent commands - 100% failure rate on Windows Server 2025. Windows Server builds may have been more lenient and partially functional. Fixed by replacing the custom PSRP implementation withpypsrpas the PSRP backend while retaining PwnRM's own SPNEGO/Kerberos/CredSSP/ClientCert transport layer for authentication. Affectedrunspace.pyentirely. -
__enter__- bare_receive()calls crash shell init on Windows Server 2025: Two_receive()calls in__enter__were issued without aCommandId, which Windows Server WinRM rejects withw:InternalErrorat the shell init stage. After the first receive failed, the shell was invalidated by the server - every subsequent request returnedw:InvalidSelectors. Resolved as part of the PSRP backend replacement.
Dependency
- Added
pypsrp>=0.8.0torequirements.txtandpyproject.toml.
Full Changelog: v1.2.1...v1.2.4
v1.2.1
What's Changed
Bug Fixes - Post-Exploitation Modules (adtriage.py, sessions.py, shares.py, pwnshell.py)
-
!adtriage/!sessions/!shares-[bool]::ParseFormatException (100% failure rate): Template substitution in all three resource loaders was replacing'__QUICK__'(with surrounding single quotes) instead of the inner token__QUICK__, stripping the quotes the.ps1template already provides. The emitted PowerShell was[bool]::Parse($false)/[bool]::Parse(True)depending on attempt - both are invalid inputs to the CLR static method, which strictly requires"True"or"False"as quoted string literals. Fixed by targeting__QUICK__(no quotes) in all three loaders so the template's existing single quotes are preserved, producing well-formed[bool]::Parse('True')/[bool]::Parse('False')output. Affectedadtriage.py:16,sessions.py:15,shares.py:18. -
!shares- Silent no-op on default invocation (@('')array clobber): When!sharesis called without explicit targets,shares.pysubstituted an empty string into@('__TARGETS__'), producing@('')- a one-element PowerShell array containing an empty string.$Targets.Countevaluated to1, satisfying theCount -gt 0branch and bypassing both AD domain controller discovery and local WMI share enumeration entirely - zero output, zero error. Fixed by replacing the entire@('__TARGETS__')expression with@()on the no-target path, correctly producing an empty array and allowing the intended discovery branches to execute. Affectedshares.py:18–25,shares.ps1:210. -
!amsi- Silent bypass failure via[ref]$nullinVirtualProtectout-parameter:amsi_bypass()passed[ref]$nullas thelpflOldProtectout-parameter toVirtualProtect.$nullis a read-only automatic variable in PowerShell - the CLR marshaler cannot write the returneduintinto it, throwingCannot set the Value property for a Reference object whose Value is not a variable. As a result,VirtualProtectfailed to markAmsiScanBufferas writable, the subsequentMarshal::Copyhit a write-protected page, and the bypass silently failed while the shell still printed[+] AMSI bypass applied.Fixed by declaring a typed mutable variable$old = [uint32]0before each call and passing[ref]$old. Affectedpwnshell.py:431–435. -
!netrun-TargetParameterCountExceptionon zero-argument invocation: When!netrunwas called with a URL and no trailing arguments, the no-args branch selectedEntryPoint.Invoke($null, $null). For any assembly with a standardMain(string[] args)entry point (one parameter), the CLR reflection API requires exactly one argument - passing$nullthrowsSystem.Reflection.TargetParameterCountException: Parameter count mismatch, aborting execution before the assembly runs. Fixed by dynamically inspectingEntryPoint.GetParameters().Length: zero-parameter entry points useInvoke($null, $null); one-parameter entry points useInvoke($null, [object[]](,[string[]]@())), supplying a valid empty string array. Affectedpwnshell.py:494.
Full Changelog: v1.2.0...v1.2.1
v1.2.0
What's Changed
New Features
-
!shares- SMB Share Scout: Self-contained share enumeration running entirely inside the remote PowerShell session. Discovers local shares viaWin32_Share, probes ~20 common UNC paths for read/write access, scans ACLs for overly permissive entries (Everyone,Authenticated Users), detects sensitive files in SYSVOL/NETLOGON including GPPcPasswordauto-detection (CVE-2014-1812), and reports open files and active SMB sessions. Supports-qfor quick local-only mode and explicit[HOST..]targeting with AD-based auto-discovery. -
!sessions- Session & Network Snapshot: Active logon session enumeration viaWin32_LogonSession/Win32_LoggedOnUserwith session type classification (Interactive/RDP/Network/Service). Covers RDP client MRU and saved credentials from registry, Kerberos ticket cache (klist) with TGT flagging, established TCP connections with process attribution and external IP detection, listening port inventory with service labels, named pipe exposure with sensitive pipe flagging (lsass,spoolss,samr,epmapper...), and SYSTEM-level scheduled tasks. Supports-qfor logon sessions + RDP MRU only.
Hardening (3584ce5)
run_sync- Memory Cap: Enforced a 1 MB output ceiling on synchronous command accumulation. Streams exceeding the limit are truncated and the runspace is interrupted to prevent OOM from anomalous remote endpoints.!download- Stream Guard:!downloadchunk accumulator now enforces a configurable memory cap (PWNRM_MAX_DL, default 256 MB). Overflow raises aRuntimeErrorand surfaces immediately rather than crashing silently.- SPNEGO/Kerberos - Exception Hardening: Replaced bare
except:clauses in NTLM and Kerberos unwrap routines withexcept Exception:to prevent silent swallowing of fatal decryption failures and RC4 keystream desynchronization. - Session Directory - Windows ACL:
_PWNRM_DIRnow explicitly strips inherited ACEs and grants exclusive(OI)(CI)Frights to the current user viaicaclson Windows, mirroring the existing0o700POSIX enforcement.
Refactor (64da5ba)
- Transport - Request Timeouts: Enforced an explicit
timeout=30across all transport adapters (BasicTransport,ClientCertTransport,SPNEGOTransport,KerberosTransport,CredSSPTransport) to prevent uninterruptible client hangs on unresponsive targets. - Utils - Certificate String Normalization:
get_server_certificate()now strips PEM headers and all whitespace in a singlere.sub()pass, correctly handling cross-platform line endings (\r\n) that the previousstr.replace()approach missed. - Utils -
load_pfx()Windows ACL: PFX temp directory now applies the sameicaclsexclusive-access hardening as_PWNRM_DIR, consistent across platforms.
Full Changelog: v1.1.1...v1.2.0
v1.1.1
What's Changed
Security Fix — (GHSA-jwc5-6vfh-r4h2, 9f5ad17)
- Transport — Redirect Blocking: Disabled HTTP redirect following (
allow_redirects=False,max_redirects=0) across all transport sessions. Any 3xx response from a WinRM server now raisesTransportErrorand aborts the connection instead of forwarding the request to the redirect target. - Transport — Unencrypted Response Passthrough:
_decrypted_response()now short-circuits and returns the raw response when no--Encrypted Boundarymarker is present, preventing a panic on plaintext SOAP faults. - Transport — CredSSP TLS Certificate Guard: Added an explicit check for peer certificate existence after TLS handshake in
CredSSPTransport._auth()before attempting to extract the public key. - Utils —
KRB5CCNAMEPath Prefix: Replacedlstrip("FILE:")withremoveprefix("FILE:")inload_kerberos_ccache()to avoid stripping leading characters that happen to match the charset{F,I,L,E,:}. - Utils — PFX Validation:
load_pfx()now raisesValueErrorif the parsed PFX is missing either a private key or a certificate, rather than propagating aNoneTypeerror downstream.
Fixes (f96a20d)
!netrun— TargetParameterCountException: Fixed invocation ofAssembly.EntryPoint.Invoke— arguments are now correctly wrapped as[object[]](,[string[]](...))when args are present, or$nullotherwise.- CredSSP — TLS Trailer Padding:
tls_trailer_lengthis now calculated against the original plaintext length instead of the post-encryption ciphertext length. _SAFE_WIN_PATH_RE: Relaxed regex to allow UNC paths (\\server\share\...) in addition to drive-letter paths, while retaining the PS injection character blocklist.!download— Malformed Base64: Wrapped stream chunk decoding intry/exceptto prevent client crash on malformed Base64 output from a rogue server.
Hardening (604bb53)
- Transcript Log:
start_log()now opens withO_CREAT|O_EXCL|O_WRONLYand randomizes the filename with a 4-byte urandom suffix. - History File:
.pwnrm_historycreation now unlinks any existing symlink at that path beforeO_CREAT|O_EXCLopen.
Maintenance (dca4f4c)
- Bump version to 1.1.1 across
pyproject.toml,__init__.py, andapi.py. - Banner updated to grayscale gradient.
- Credit to @phamthanha-a for reporting GHSA-jwc5-6vfh-r4h2.
Full Changelog: v1.1.0...v1.1.1
v1.1.0
What's Changed
Security Fixes (88b3125)
- ASN.1 / GSS-API Framing: Fixed BER/DER short-form length decoding in
krb5_mech_indep_token_decode—skipwas incorrectly calculated as2 + lengthinstead of2, causing malformed token parsing on any non-trivial Kerberos exchange. - CredSSP Auth Validation: Properly unwrap the server
pubKeyAuthtoken before comparing against the SHA-256 EPA binding hash. Previously the raw octet string was compared directly, making the MitM detection ineffective. - SPNEGO Empty Token Guard:
SPNEGOProxyNTLM.step()now short-circuits cleanly whendata_inis empty (server accepts auth on HTTP 200 without a negotiation token). - URL Parsing Hardening:
create_transport()now sanitizeshttp:///https://-prefixed targets viaurlparseto prevent host and port mangling when a full URL is passed as the target argument. - History File Permissions:
.pwnrm_historyis now created with0o600(orchmod-ed on first open) to prevent multi-tenant local credential leaks. - Remote Path Injection:
upload()anddownload()now validate the server-returned temp path via_validate_remote_path()before embedding it in PowerShell execution contexts, with a safe fallback toC:\Windows\Temp\. - Certificate Port Binding:
get_server_certificate()now defaults to port5986for HTTPS targets instead of443, preventing EPA channel binding desync on non-standard WinRM deployments. strip_ansi()Null Safety: Added aNoneguard — previously passed aNonevalue (e.g. from a missing XML element) would raiseTypeErrormid-session.
Core Fixes (3069e2f)
- PSRP Defragmentation: Raised the minimum valid fragment size in
_defragment()from 21 → 65 bytes (header 13B + payload prefix 20B + UUID fields 32B) to preventstruct.erroron short/malformed streams. - Empty Stream Elements:
b64decodenow receivess.text or ""instead of a bares.textto handle empty<rsp:Stream/>elements without crashing. - SOAP Action Null Check:
wsa:Actionelement existence is now validated before.textaccess — previously a malformed SOAP response missing the header caused an unhandledAttributeError. - PIPELINE_STATE Parsing:
PIPELINE_STATEinteger is now parsed with atry/exceptfallback to0— preventsTypeError/ValueErroron malformed server responses. - Terminal State Handling: Runspace
run_command()now recognizesCommandState/TerminatedandCommandState/Failedin addition toCommandState/Doneto prevent infinite receive loops on abnormal command termination. - Fragment Buffer Reset: Fragment buffer is now reset on
START_FRAGMENT(flags & 0b10) to prevent cross-message state poisoning. - SOAP Fault Detection: Added fallback detection via
s:Faultelement whenwsa:Actionheader is absent from the SOAP response.
Shell Fixes (9d6c6a2, 83e9064)
_PWNRM_DIRNameError: Defined_PWNRM_DIRat module level before thePwnShellclass — previously caused an immediateNameErrorcrash on any TTY session startup (FileHistory) and on!log.- Download Integrity Bypass: Added a
len(buf) < 32guard indownload()before the MD5 trailer slice — a rogue server returning exactly the 32-byte MD5(empty) hash would silently pass the integrity check and write a 0-byte file. Adjusted from< 33to< 32to correctly allow 0-byte file downloads. src_psStale Reference:src_psis now re-derived aftersrcis reassigned to the remote zip path in directory download mode — previouslyRemove-ItemandDownload-Remoteoperated on the original directory path instead of the generated.ziptemp file.- Empty Args IndexError (
!psrun,!upload,!netrun): All three commands now guard against emptyargsbefore anyargs[0]access, returning a usage hint instead of crashing withIndexError. - Backslash Normalization: Fixed over-escaped backslash replacement in
_importPathFix(\\\\\\\\→\\\\) — directory ZIP generation was producing malformed paths on the remote side.
Hardening (6db35ee)
- Session Directory Permissions:
~/.pwnrmis nowchmod 0o700at startup and transcript log files are opened with0o600— prevents other local users from reading command history or session transcripts containing credentials. - Transport Decryption Integrity: Decryption failures in
_decrypted_response()now raiseTransportErrorimmediately instead of silently logging and continuing, preventing stream desync.
Full Changelog: v1.0.5...v1.1.0
v1.0.5
What's Changed
Improvements & Hardening (db744d2)
- PowerShell Helpers: Added character escaping (
_pde) for server-provided paths embedded in PowerShell execution contexts. - Stream Handling: Added memory limits (max 256 object IDs / 32 MB) to in-flight PSRP fragment buffers to prevent resource exhaustion.
- XML Processing: Migrated inbound XML parsing to
defusedxmlfor safer document processing. - Path Validation: Added
_validate_remote_pathto enforce strict path format verification on remote server responses. - File Permissions: Hardened temporary certificate handling by enforcing restricted filesystem access (
0o700/0o600) during key extraction.
Dependencies (db744d2)
- Added
defusedxml>=0.7.1
Maintenance (b30ead6)
- Bump version to 1.0.5 and update distribution build artifacts.
Full Changelog: v1.0.4...v1.0.5