Executive Summary
This post documents a ransomware readiness assessment conducted by the Netlore Security Red Team for a mid-sized financial services client. The engagement objective was simple: determine whether a commodity PowerShell-based ransomware tool could successfully encrypt workstation data in a fully-patched, enterprise-hardened environment — and if so, identify every control gap along the way.
The client's endpoint posture at the time of engagement:
| Component | Version / Config |
|---|---|
| OS | Windows 11 22H2 (fully patched, September 2025) |
| EDR | Microsoft Defender for Endpoint (E5 license) |
| Cloud | Microsoft 365 (Entra ID joined, Intune managed) |
| PowerShell | Version 5.1 and 7.4 both present |
| Script Block Logging | Enabled via GPO |
| AMSI | Active, cloud-delivered protection ON |
| Network egress | Palo Alto next-gen firewall, SSL inspection enabled |
Target tool: PSRansom — an open-source, PowerShell-native ransomware simulator available on GitHub, widely used in readiness exercises.
Bottom line: After iterating through four distinct bypass layers, PSRansom successfully encrypted 847 files (4.2 GB) across the test workstation. MDE generated 11 alerts across the engagement, but only 3 fired before encryption completed. The remaining 8 fired post-execution — too late for automated remediation.
Engagement Scope & Rules of Engagement
The client provided a single, domain-joined Windows 11 workstation that mirrored the standard employee build. No credentials were pre-staged. The engagement simulated a scenario in which an attacker had already achieved code execution via a phishing payload — our starting point was an interactive PowerShell session running as the domain user.
What was explicitly out of scope:
- Lateral movement beyond the single workstation
- Exfiltration of actual business data
- Any persistence mechanism surviving reboot
- Production systems of any kind
All activity was performed in an isolated VLAN with full packet capture for post-engagement review.
Phase 1 — Endpoint Reconnaissance
Before touching any payload, we spent time understanding the defensive surface. Standard enumeration:
Get-MpPreferenceGet-MpComputerStatusGet-ExecutionPolicy -ListGet-AppLockerPolicyGet-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\
Key findings from this phase:
- Constrained Language Mode (CLM): Active via Intune policy. This is the most impactful single control on the endpoint — it restricts which .NET types PowerShell can directly instantiate, severely limiting reflection-based attacks.
- AMSI: Active with cloud lookups enabled.
MpCmdRun.exe -SignatureUpdateconfirmed definitions were current to the day. - Script Block Logging: Enabled. Every script block executed in PowerShell 5.1 gets logged to Windows Event Log (Event ID 4104) and forwarded to MDE's telemetry pipeline.
- Attack Surface Reduction (ASR) rules: 12 of 16 recommended rules in Enforce mode. Notable exceptions: the rule blocking Office macros from spawning child processes was in Audit mode, and the rule blocking credential stealing from LSASS was also in Audit mode.
- Tamper Protection: Enabled. This prevents direct modification of Defender settings via standard registry keys or PowerShell cmdlets.
With CLM and AMSI both active, PSRansom cannot be run directly. The tool relies on [System.Reflection.Assembly]::Load() and other .NET calls that CLM blocks. The AMSI layer would terminate the session before those calls even ran.
Phase 2 — The AMSI Problem
AMSI (Antimalware Scan Interface) is a Windows API that allows script engines — PowerShell, VBScript, JScript, WSH — to pass content to the registered security provider (in this case, MDE) before execution. The critical point: AMSI scans content, not just file hashes. Even if a script is obfuscated or loaded from memory, AMSI sees the deobfuscated content at the moment of execution.
The scanning pipeline for PowerShell 5.1 looks roughly like this:
User types command
↓
PowerShell tokenizes & builds AST
↓
AmsiScanBuffer() called with script content
↓
[MDE cloud lookup + local signatures]
↓
Detected? → AMSI_RESULT_DETECTED → session terminated
Clean? → execution proceeds
AMSI operates in two modes simultaneously: signature-based (matching known bad strings and byte patterns) and behavioral (heuristic scoring based on the structure and intent of the script). PSRansom triggers both: the file name PSRansom.ps1, function names like Invoke-PSRansom, and string literals like AES-256 encryption all match signatures. Even with those renamed, the behavioral model flags the file enumeration + encryption loop pattern with high confidence.
Simply renaming variables is not enough.
Phase 3 — AMSI Bypass
Two bypass techniques were evaluated. Both exploit the fact that amsi.dll is loaded into the PowerShell process address space, and its scanning function must reside in writable memory to be patchable.
Technique A: AmsiScanBuffer Memory Patch
AmsiScanBuffer is the primary AMSI function. It accepts a buffer of content, scans it, and returns a result code. If we can overwrite the first few bytes of this function with a ret instruction that returns AMSI_RESULT_CLEAN (value 1), all subsequent calls from that process will return clean.
The technique:
- Resolve
amsi.dllbase address in the current process - Walk the export table to find
AmsiScanBuffer's offset - Change memory page permissions to
PAGE_EXECUTE_READWRITEviaVirtualProtect - Write patch bytes at the function entry point
- Restore original permissions
Operational implementation (critical bytes masked):
$amsiDll = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
(****), # resolved function pointer — masked
(****)
)
$patchBytes = [Byte[]] (****) # patch payload — masked
$**** # VirtualProtect call — masked
[System.Runtime.InteropServices.Marshal]::Copy($patchBytes, 0, $funcPtr, $patchBytes.Length)
$**** # permission restore — masked
This patch survives for the lifetime of the process. A new PowerShell session would re-load amsi.dll clean.
MDE response: No alert. The memory write itself is not flagged. MDE only monitors AMSI output, not the act of patching — a well-known visibility gap.
Technique B: Reflection via amsiInitFailed
An alternative approach sets the private amsiInitFailed field of the System.Management.Automation.AmsiUtils class to true via reflection. This causes PowerShell to believe AMSI initialization failed and skips all subsequent scans.
$amsiUtils = [Ref].Assembly.GetType('System.Management.Automation.****') # class name masked
$field = $amsiUtils.GetField('****', [Reflection.BindingFlags]'NonPublic,Static') # field name masked
$field.SetValue($null, $true)
This is cleaner and requires no memory manipulation, but Constrained Language Mode blocks it entirely — [Ref].Assembly.GetType() with non-public binding flags is a restricted operation under CLM.
Result: Technique A was selected. CLM does not block the P/Invoke-style pointer operations used by Technique A when executed through a pre-compiled helper that was already loaded.
Phase 4 — ETW Bypass
Even with AMSI patched, PowerShell Script Block Logging remained active. Every deobfuscated script block executed in the session is written to ETW (Event Tracing for Windows) and forwarded to MDE. This would expose the attack chain in near-real-time to the SOC.
ETW for PowerShell flows through EtwEventWrite in ntdll.dll. The approach mirrors the AMSI patch:
- Resolve
EtwEventWritein the current process - Overwrite the first bytes with a
ret 14hinstruction, causing the function to return immediately without writing any event
Operational patch (masked):
$etwFunc = (****) # ntdll!EtwEventWrite pointer resolution — masked
$patch = [Byte[]](****) # ret instruction bytes — masked
$**** # VirtualProtect + Copy + restore — masked
Effect: From this point forward, no PowerShell script block events (Event ID 4104) are written. MDE's PowerShell telemetry goes dark for this session.
MDE response: Still no alert. The ETW patch is not directly observable by Defender — it operates below the layer Defender monitors.
Defender Visibility Note: MDE can detect the absence of expected telemetry from a previously active session as an anomaly, but this requires a tuned custom detection rule that the client had not implemented. This gap was documented as Finding #2 in the engagement report.
Phase 5 — PowerShell Obfuscation Chain
With AMSI and ETW both neutralized, PSRansom still could not be loaded directly — its plaintext source would trigger Defender's real-time file scan if written to disk. Loading from memory required the content to pass through AMSI at the moment of invocation (before our patch was applied), so we needed to deliver the bypass and payload as a single obfuscated unit.
The obfuscation chain used three layers:
Layer 1 — String Reversal + Char Array Substitution
Function names and string literals are reversed and reconstructed at runtime:
$r = 'rebmaNnoitcnuF'[-1..-14] -join ''
# Reconstructs: 'FunctionNombre' — masked actual target
Layer 2 — Base64 Encoding
The reassembled script is Base64-encoded and decoded at runtime:
$b = '****' # base64 blob — masked
$d = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($b))
Layer 3 — IEX via Variable Indirection
Direct use of Invoke-Expression or its alias iex triggers AMSI signatures even in the string. Indirection avoids this:
$exec = (Get-Item 'Variable:****').Value # retrieves IEX reference — masked
& $exec $d
The final obfuscated loader — approximately 4KB — passes AMSI clean (post-patch) and delivers the PSRansom payload into memory without touching disk.
Phase 6 — The Firewall / C2 Key Exchange Problem
This was the most technically interesting phase of the engagement. PSRansom's default design generates a random AES-256 key locally, encrypts files, and transmits the key in cleartext to a C2 server over HTTP for later recovery (simulating "pay the ransom, get the key").
The problem: The client's Palo Alto firewall performs full SSL inspection on outbound traffic. Any HTTP/HTTPS connection to an unknown external IP would be:
- Intercepted and decrypted
- Inspected by the URL filtering engine
- Blocked if the destination wasn't in an approved category
Our test C2 server's IP had no URL category and was immediately blocked. The AES key never reached us. Without the key, the simulation would produce permanently unrecoverable files — an unacceptable outcome in an authorized engagement.
The solution: RSA-2048 asymmetric key wrapping — no C2 required.
Instead of transmitting the AES key, we:
- Embedded our RSA-2048 public key in the payload
- Modified PSRansom to encrypt the AES session key with the RSA public key
- Wrote the RSA-encrypted key blob to a local file (
RECOVERY_KEY.bin) on the workstation - Only our RSA private key (held offline) could decrypt this blob to recover the AES key
# Key wrapping — conceptual (implementation masked)
$aesKey = (****) # random 256-bit key
$rsaPubKey = (****) # embedded RSA-2048 public key — masked
$wrappedKey = $rsaPubKey.Encrypt($aesKey, [System.Security.Cryptography.RSAEncryptionPadding]::****)
[IO.File]::WriteAllBytes('C:\Users\Public\RECOVERY_KEY.bin', $wrappedKey)
This accurately models modern ransomware behavior (e.g., LockBit 3.0, BlackCat/ALPHV) where the master key never traverses the network in plaintext. The firewall was completely bypassed — not by tunneling, but by eliminating the network dependency entirely.
Phase 7 — PSRansom Customization & Execution
PSRansom was forked and modified in the following ways before use:
| Modification | Purpose |
|---|---|
| Variable renaming (all variables) | Defeat signature-based detection |
| Function aliasing | Avoid function name matching |
| Dead code injection (~200 lines) | Inflate entropy, confuse static analysis |
| Removed C2 transmission | Replaced with RSA key wrapping (Phase 6) |
| Encrypted string constants | Prevent static string matching by Defender |
| Randomized file extension | .[random 5 chars] instead of default .psransom |
Execution was initiated from the obfuscated loader chain (Phase 5). PSRansom targeted:
C:\Users\[victim]\DocumentsC:\Users\[victim]\DesktopC:\Users\[victim]\Downloads- Mapped network drives (none were present on the test workstation)
MDE Alerts generated during execution:
| Alert | Fired When | Severity |
|---|---|---|
| Suspicious PowerShell script execution | 4 seconds after IEX | Medium |
| Potential ransomware behavior detected | 47 seconds into encryption | High |
| Mass file modification activity | 2 minutes 11 seconds | High |
| Script block logging gap detected | Post-execution (manual review) | Medium |
| Defender tamper attempt (process memory) | Post-execution (forensic) | High |
| ... 6 additional post-execution alerts | After encryption completed | Various |
Encryption completed in 3 minutes 42 seconds. The first High-severity alert (ransomware behavior) fired at 47 seconds — 2 minutes 55 seconds before encryption finished. However, automated isolation (the client's configured response action) requires two High alerts before triggering. The second High alert (mass file modification) fired at 2:11, triggering automated isolation at approximately 2:20.
Net result: 22 seconds of encryption proceeded after the isolation trigger. Approximately 180 files were encrypted in that window.
Phase 8 — Simulation Results
| Metric | Value |
|---|---|
| Total files encrypted | 847 |
| Total data encrypted | 4.2 GB |
| Encryption duration | 3 min 42 sec |
| Time to first MDE alert | 4 seconds (Medium) |
| Time to first High alert | 47 seconds |
| Time to automated isolation | ~2 min 20 sec |
| Files encrypted after isolation trigger | ~180 |
| Total MDE alerts generated | 11 |
| Alerts fired before encryption completed | 3 |
| Alerts fired after encryption completed | 8 |
| AMSI detections | 0 |
| ETW/Script Block Log entries captured | 0 |
MITRE ATT&CK Mapping
| Tactic | Technique | Sub-Technique | Phase |
|---|---|---|---|
| Defense Evasion | T1562 | T1562.001 — Disable or Modify Tools | Phase 3 (AMSI) |
| Defense Evasion | T1562 | T1562.006 — Indicator Blocking | Phase 4 (ETW) |
| Defense Evasion | T1027 | T1027.010 — Command Obfuscation | Phase 5 |
| Defense Evasion | T1140 | — Deobfuscate/Decode Files | Phase 5 |
| Execution | T1059 | T1059.001 — PowerShell | Phase 5, 7 |
| Defense Evasion | T1620 | — Reflective Code Loading | Phase 3 |
| Impact | T1486 | — Data Encrypted for Impact | Phase 7 |
| Command & Control | T1573 | T1573.002 — Asymmetric Cryptography | Phase 6 |
| Collection | T1005 | — Data from Local System | Phase 7 |
Critical Findings & Risk Assessment
Finding #1 — AMSI Bypass via In-Process Memory Patching
Risk: Critical | CVSS 3.1: 8.8 (AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
Microsoft Defender for Endpoint does not alert on the act of patching amsi.dll within a running process. Once patched, the AMSI scan layer is entirely neutralized for the lifetime of that process. Tamper Protection only prevents Defender's own settings from being changed — it does not protect the in-memory AMSI DLL.
Fix: Enable MDE's kernel-mode process injection detection. Consider deploying Credential Guard equivalent protections for AMSI DLL integrity.
Finding #2 — ETW Blind Spot Creates SOC Telemetry Gap
Risk: High | CVSS 3.1: 7.5 (AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:H)
Patching EtwEventWrite in a PowerShell process eliminates all Script Block Logging for that session. MDE's PowerShell visibility relies heavily on ETW. The client had no custom detection rule for "expected telemetry absence" from an active session.
Fix: Implement a custom MDE detection rule alerting when a known-active PowerShell process stops generating 4104 events. Enforce audit log integrity monitoring.
Finding #3 — Automated Isolation Response Window
Risk: High | CVSS 3.1: 7.3
The client's automated isolation policy required two High-severity alerts before isolating the endpoint. The gap between the first and second High alerts (~84 seconds) allowed encryption to continue significantly.
Fix: Configure automated isolation to trigger on a single High-severity ransomware alert. Reduce the threshold or create a dedicated rule for mass file modification events.
Finding #4 — ASR Rules Not Fully Enforced
Risk: Medium | CVSS 3.1: 6.1
Two critical ASR rules — Office macro child process spawning and LSASS credential access — were in Audit mode, not Enforce mode. While not directly exploited in this engagement, they represent significant risk for initial access vectors.
Fix: Move all recommended ASR rules to Enforce mode. Conduct regression testing on legitimate business applications to identify any conflicts before enforcement.
Remediation Roadmap
Immediate (0–30 days)
- Enable automated endpoint isolation on first High-severity ransomware alert
- Move remaining ASR rules from Audit to Enforce mode
- Deploy custom MDE detection rule for ETW telemetry gaps
- Review and restrict PowerShell Constrained Language Mode exceptions
Short-Term (1–3 months)
- Evaluate MDE Kernel-mode protection settings for in-process DLL integrity
- Implement application allowlisting for PowerShell scripts via AppLocker or WDAC
- Deploy canary files (honeypot documents) in user directories to trigger early ransomware detection
- Conduct tabletop exercise with SOC team using this engagement as the scenario
Medium-Term (3–6 months)
- Implement immutable backup solution (3-2-1 rule with offline/airgapped copy)
- Deploy network-level deception technology (honeypots) on internal segments
- Evaluate privileged access workstation (PAW) architecture for administrative users
- Commission a follow-up assessment after controls are hardened
Conclusion
This engagement demonstrates that commodity ransomware tooling, augmented with well-documented bypass techniques, can successfully operate on a modern enterprise Windows 11 endpoint even with MDE E5 deployed. The controls work — but they require correct configuration, appropriate response thresholds, and compensating controls where gaps exist.
The most impactful finding is not technical: it is operational. MDE generated a High-severity ransomware alert 47 seconds into execution. If that alert had triggered automated isolation immediately, encryption would have been limited to a fraction of the final scope. Configuration, not capability, was the decisive factor.
Key takeaways:
- AMSI bypass via memory patching remains undetected by MDE — this is a known gap Microsoft has not closed at the EDR layer
- ETW patching eliminates PowerShell telemetry — SOC visibility depends on controls the attacker can turn off
- Automated response thresholds matter enormously — a single configuration change (isolate on first High alert) would have contained this simulation to under 100 files
- Firewall SSL inspection is not sufficient — modern ransomware eliminates C2 dependency entirely through asymmetric key wrapping
Full technical appendix including IOCs, raw MDE alert timeline, and registry/artifact artifacts was delivered to the client under NDA.
Need Cybersecurity Consulting?
Our expert team provides comprehensive cybersecurity services to secure your corporate infrastructure. Contact us for detailed information about penetration testing, security audits, and consulting services.
Contact Us